pi-spark 0.22.2 → 0.23.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,534 @@
1
+ # Changelog
2
+
3
+ ## [0.23.0](https://github.com/zlliang/pi-spark/compare/v0.22.3...v0.23.0) (2026-09-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **credits:** support provider peak/off-peak pricing ([f0dbf19](https://github.com/zlliang/pi-spark/commit/f0dbf19dca6f7a3eb290490052489eb0649680d1))
9
+
10
+ ## [0.22.3](https://github.com/zlliang/pi-spark/compare/v0.22.2...v0.22.3) (2026-09-10)
11
+
12
+
13
+ ### Features
14
+
15
+ * **credits:** add provider dashboard hyperlinks ([f7a7ec2](https://github.com/zlliang/pi-spark/commit/f7a7ec2b2394dd65e5c6ce20bc4b3d8fb5cc600e))
16
+ * **credits:** show reset times and convert Kimi balances to USD ([7c4a478](https://github.com/zlliang/pi-spark/commit/7c4a4782cecba8ecc557bfcf496b898347b5e2b5))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * correct cache probe config typing ([1695212](https://github.com/zlliang/pi-spark/commit/1695212c607163ebc2fbb721d6088377f36b9887))
22
+ * **credits:** update Codex usage settings link ([4f8d72c](https://github.com/zlliang/pi-spark/commit/4f8d72c6acbd519ab7850b8a086a09de5d83ca98))
23
+
24
+ ## [0.22.2](https://github.com/zlliang/pi-spark/compare/v0.22.1...v0.22.2) (2026-09-04)
25
+
26
+
27
+ ### Miscellaneous Chores
28
+
29
+ * trigger release 0.22.2 ([b8463a8](https://github.com/zlliang/pi-spark/commit/b8463a887b9be6c81d8d55df7b6a31df6acdb21f))
30
+
31
+ ## [0.22.1](https://github.com/zlliang/pi-spark/compare/v0.22.0...v0.22.1) (2026-09-04)
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * **auth:** pass environment to background requests ([a89c943](https://github.com/zlliang/pi-spark/commit/a89c943c4190b659ac4cf3ffec49eff315008f94))
37
+ * **credits:** enable credits status in RPC mode ([#72](https://github.com/zlliang/pi-spark/issues/72)) ([58e9cc1](https://github.com/zlliang/pi-spark/commit/58e9cc16bbae4a06cc9ea89e694f7723f6abcd7d))
38
+ * **credits:** simplify mode check ([a58acd1](https://github.com/zlliang/pi-spark/commit/a58acd1ca7830f4a03941ad90778a1b4f2ae715c))
39
+ * **model:** avoid authenticating fallback models ([3a6f6a1](https://github.com/zlliang/pi-spark/commit/3a6f6a12bbf180079cf09333842cf4159583cf7e))
40
+
41
+ ## [0.22.0](https://github.com/zlliang/pi-spark/compare/v0.21.1...v0.22.0) (2026-08-07)
42
+
43
+
44
+ ### ⚠ BREAKING CHANGES
45
+
46
+ * **fullscreen:** use Pi's native fullscreen mode
47
+
48
+ ### Code Refactoring
49
+
50
+ * **fullscreen:** use Pi's native fullscreen mode ([3fbc092](https://github.com/zlliang/pi-spark/commit/3fbc09234f4baff627624b99714fd8057e179e80))
51
+
52
+ ## [0.21.1](https://github.com/zlliang/pi-spark/compare/v0.21.0...v0.21.1) (2026-07-24)
53
+
54
+
55
+ ### Bug Fixes
56
+
57
+ * **credits:** extract token from Authorization header for OAuth providers ([06f7309](https://github.com/zlliang/pi-spark/commit/06f7309bd520e76947dc7a9523b6fc4e2855a779))
58
+ * **credits:** resolve OAuth tokens consistently ([e7fd9d8](https://github.com/zlliang/pi-spark/commit/e7fd9d8098cfaf96c79a7122b95d388016b6cefa))
59
+ * **presets:** skip unavailable presets when cycling ([72348da](https://github.com/zlliang/pi-spark/commit/72348da41a6037165a1d89381a98fa54e8209993))
60
+
61
+ ## [0.21.0](https://github.com/zlliang/pi-spark/compare/v0.20.0...v0.21.0) (2026-07-23)
62
+
63
+
64
+ ### Features
65
+
66
+ * **credits:** retry HTTP requests with Ky ([ee0a304](https://github.com/zlliang/pi-spark/commit/ee0a304de314a7950e9289ea2cb1618caf4d67bb))
67
+ * **editor:** add model thinking level indicator ([d27059d](https://github.com/zlliang/pi-spark/commit/d27059df79f77d4087fe5c20218e3f76385781ae))
68
+ * **themes:** add GitHub default themes ([40838ed](https://github.com/zlliang/pi-spark/commit/40838ed1b00be8b66da335eb8d29fa059b0bf51b))
69
+ * **write:** add scrolling tail previews ([7542dde](https://github.com/zlliang/pi-spark/commit/7542dde92ac0c3973f9b284001d530b4115ff21c))
70
+
71
+
72
+ ### Bug Fixes
73
+
74
+ * **footer:** include all session usage costs ([ed69b31](https://github.com/zlliang/pi-spark/commit/ed69b314bd1d56072b55804f64b7ed0f738675d8))
75
+ * **modes:** handle non-TUI sessions correctly ([58e0246](https://github.com/zlliang/pi-spark/commit/58e02460109ca2cb494bc870ef1b8be0d2140e4f))
76
+ * **themes:** improve muted colors and code fence contrast ([b8a85d9](https://github.com/zlliang/pi-spark/commit/b8a85d96cec879c59cfc2d3eb9aeee0f6b4efb72))
77
+ * **title:** generate after first turn ([5fbf21e](https://github.com/zlliang/pi-spark/commit/5fbf21e0c2a2489d0582171a3b763dba045f1189))
78
+ * **write:** clarify omitted line summary ([702af59](https://github.com/zlliang/pi-spark/commit/702af59c5877147a05c4279b80e7258807a837c6))
79
+
80
+
81
+ ### Performance Improvements
82
+
83
+ * **fullscreen:** render transcript only once ([7eb60b9](https://github.com/zlliang/pi-spark/commit/7eb60b960d7acc544a8f5aa18b935f12c4bd9457))
84
+
85
+ ## [0.20.0](https://github.com/zlliang/pi-spark/compare/v0.19.3...v0.20.0) (2026-07-19)
86
+
87
+
88
+ ### Features
89
+
90
+ * **credits:** add per-provider enable/disable config ([ec800ef](https://github.com/zlliang/pi-spark/commit/ec800ef0cc44abcfebb0f54577c648c2ab8f3a86))
91
+ * **credits:** improve Kimi usage reporting ([675d5f0](https://github.com/zlliang/pi-spark/commit/675d5f077e9cb16e491d25e243f5641fc11861b1))
92
+ * **footer:** add statusPosition config for inline/below extension statuses ([4bfec49](https://github.com/zlliang/pi-spark/commit/4bfec49c7bbe656404dd75f2e335939f3ec88d8c))
93
+
94
+
95
+ ### Bug Fixes
96
+
97
+ * **credits:** clarify Kimi extra usage balance ([685052c](https://github.com/zlliang/pi-spark/commit/685052c1e04e1ffbfdecd876305eb00f08dacb97))
98
+ * **fullscreen:** suppress idle status gap ([02cc41d](https://github.com/zlliang/pi-spark/commit/02cc41d522512b3c5fdf2111032f1d1d8b07ce56))
99
+
100
+ ## [0.19.3](https://github.com/zlliang/pi-spark/compare/v0.19.2...v0.19.3) (2026-07-17)
101
+
102
+
103
+ ### Bug Fixes
104
+
105
+ * **credits:** handle missing used value in Kimi Code lane ([5eb86f8](https://github.com/zlliang/pi-spark/commit/5eb86f8f0606ef88e0adda73b65e2ec29b9ae57d))
106
+ * **footer:** hide cost below half a cent ([04c0f17](https://github.com/zlliang/pi-spark/commit/04c0f17f07ba6400b7a37d797b1d02dd7faf7c06))
107
+
108
+ ## [0.19.2](https://github.com/zlliang/pi-spark/compare/v0.19.1...v0.19.2) (2026-07-17)
109
+
110
+
111
+ ### Bug Fixes
112
+
113
+ * support Pi 0.80.8 ([3f786a1](https://github.com/zlliang/pi-spark/commit/3f786a1db1c60ceb9a4563cab34e35ca55dba232))
114
+
115
+ ## [0.19.1](https://github.com/zlliang/pi-spark/compare/v0.19.0...v0.19.1) (2026-07-13)
116
+
117
+
118
+ ### Bug Fixes
119
+
120
+ * **credits:** derive Codex window labels from limits ([65e7900](https://github.com/zlliang/pi-spark/commit/65e7900a22803c9e0db18837100bb11c6557a617))
121
+
122
+ ## [0.19.0](https://github.com/zlliang/pi-spark/compare/v0.18.1...v0.19.0) (2026-07-11)
123
+
124
+
125
+ ### Features
126
+
127
+ * **credits:** add Codex reset redemption ([1c56eea](https://github.com/zlliang/pi-spark/commit/1c56eeae9fb3ed46f859fbe8c88e8ce78a388950))
128
+
129
+
130
+ ### Bug Fixes
131
+
132
+ * **lifecycle:** wait for agent settlement ([7653b51](https://github.com/zlliang/pi-spark/commit/7653b513f8b6a91354fa1ba70f688639b142a206))
133
+ * **package:** include docs in published files ([14d96f9](https://github.com/zlliang/pi-spark/commit/14d96f9b5cb1aefda7d5d06ee9fb0cd3ccdf8d62))
134
+ * **title:** use sentence case for English titles ([747e41d](https://github.com/zlliang/pi-spark/commit/747e41db088ba80d617363e8f237f2ad505292a5))
135
+
136
+ ## [0.18.1](https://github.com/zlliang/pi-spark/compare/v0.18.0...v0.18.1) (2026-07-10)
137
+
138
+
139
+ ### Bug Fixes
140
+
141
+ * **lockfile:** normalize pi-ai binary path ([9d9a641](https://github.com/zlliang/pi-spark/commit/9d9a64155105b114f1d6f3576622144ab18505fc))
142
+ * **model:** isolate background Codex sessions ([b240e53](https://github.com/zlliang/pi-spark/commit/b240e53672ef452ea17b962fb683a552c5ca3904))
143
+
144
+ ## [0.18.0](https://github.com/zlliang/pi-spark/compare/v0.17.1...v0.18.0) (2026-07-10)
145
+
146
+
147
+ ### Features
148
+
149
+ * **config:** support max thinking level ([3d58e28](https://github.com/zlliang/pi-spark/commit/3d58e28a5da00c95a33bc5735b8f8e0bb1b66fad))
150
+
151
+ ## [0.17.1](https://github.com/zlliang/pi-spark/compare/v0.17.0...v0.17.1) (2026-07-10)
152
+
153
+
154
+ ### Bug Fixes
155
+
156
+ * preserve routing for background model calls ([27b2c12](https://github.com/zlliang/pi-spark/commit/27b2c12f3d3c179dca4ce44978a34725d6f4db60))
157
+
158
+ ## [0.17.0](https://github.com/zlliang/pi-spark/compare/v0.16.0...v0.17.0) (2026-07-09)
159
+
160
+
161
+ ### Features
162
+
163
+ * add kimi code provider ([#56](https://github.com/zlliang/pi-spark/issues/56)) ([8950df3](https://github.com/zlliang/pi-spark/commit/8950df3c8ac58158a817d7e4cc8a26d315c5a534))
164
+
165
+
166
+ ### Bug Fixes
167
+
168
+ * **credits:** always show Kimi Code weekly usage lane ([985ba89](https://github.com/zlliang/pi-spark/commit/985ba8974fa07490941c7eb77a076ace20566d1f)), closes [#56](https://github.com/zlliang/pi-spark/issues/56)
169
+ * **credits:** fix Kimi Code weekly lane always rendering ([76e9227](https://github.com/zlliang/pi-spark/commit/76e922751d09e81f3619f4493c895306c255c34d)), closes [#56](https://github.com/zlliang/pi-spark/issues/56)
170
+
171
+ ## [0.16.0](https://github.com/zlliang/pi-spark/compare/v0.15.0...v0.16.0) (2026-07-09)
172
+
173
+
174
+ ### Features
175
+
176
+ * **title:** name sessions automatically after the first exchange ([dcef9f5](https://github.com/zlliang/pi-spark/commit/dcef9f5a9d71d6f793383526dfb3d924d0937d93))
177
+
178
+
179
+ ### Bug Fixes
180
+
181
+ * **presets:** use border color for preset selector border ([500a868](https://github.com/zlliang/pi-spark/commit/500a8681758533433e8d6da6d0eafa5c9774d141))
182
+ * update pi-ai provider imports ([ccf7f6d](https://github.com/zlliang/pi-spark/commit/ccf7f6dc292c0c11f114f19a48f3b72f58016345))
183
+
184
+ ## [0.15.0](https://github.com/zlliang/pi-spark/compare/v0.14.7...v0.15.0) (2026-06-20)
185
+
186
+
187
+ ### Features
188
+
189
+ * **footer:** hide cost display for free models when cost is zero ([78e0a23](https://github.com/zlliang/pi-spark/commit/78e0a230d6a148b927d5cd598c0edee505c260c2))
190
+ * remove pi and web agent tools ([1acb837](https://github.com/zlliang/pi-spark/commit/1acb8377bbad437a31d8feb91112529f332964ab))
191
+
192
+
193
+ ### Bug Fixes
194
+
195
+ * **config:** use shared config directory name ([bc5dae0](https://github.com/zlliang/pi-spark/commit/bc5dae08d8528a41e379c041cf0eaba2b176ae68))
196
+ * **tool:** handle missing composed tool action ([e93bf39](https://github.com/zlliang/pi-spark/commit/e93bf391c26afad3e8bf84123d26f1f2a9626a60))
197
+
198
+ ## [0.14.7](https://github.com/zlliang/pi-spark/compare/v0.14.6...v0.14.7) (2026-06-17)
199
+
200
+
201
+ ### Bug Fixes
202
+
203
+ * **config:** isolate invalid feature config instead of disabling all ([c57addb](https://github.com/zlliang/pi-spark/commit/c57addb510ce88031c7def2ddde97a594ac1f39f))
204
+ * **credits:** cancel inflight request on session shutdown ([2695273](https://github.com/zlliang/pi-spark/commit/2695273e155da4aa1edd87098c3574363833aad0))
205
+
206
+ ## [0.14.6](https://github.com/zlliang/pi-spark/compare/v0.14.5...v0.14.6) (2026-06-15)
207
+
208
+
209
+ ### Bug Fixes
210
+
211
+ * **pi:** trim name parameters and align state output ([2dd5efc](https://github.com/zlliang/pi-spark/commit/2dd5efc9888763399a8af4e6d96d5d7744959daa))
212
+ * **presets:** apply preset flag only on startup ([b0a2585](https://github.com/zlliang/pi-spark/commit/b0a2585a6fa63b3a72b14daf2802ad90eace1a3c))
213
+ * **presets:** improve selector layout and key hints ([9bf0c9f](https://github.com/zlliang/pi-spark/commit/9bf0c9f7b5f00dda84464fe8327054af00a9d26b))
214
+ * **utils:** prevent duplicate elapsed timing line in composed tools ([0a00fd5](https://github.com/zlliang/pi-spark/commit/0a00fd554c7eeadeb099f185507e63a850dd89c6))
215
+
216
+ ## [0.14.5](https://github.com/zlliang/pi-spark/compare/v0.14.4...v0.14.5) (2026-06-14)
217
+
218
+
219
+ ### Bug Fixes
220
+
221
+ * **startup:** defer heavy gRPC and MCP imports to first use ([2080906](https://github.com/zlliang/pi-spark/commit/2080906515452d3ad4464b6cf2892dc765025458))
222
+
223
+ ## [0.14.4](https://github.com/zlliang/pi-spark/compare/v0.14.3...v0.14.4) (2026-06-14)
224
+
225
+
226
+ ### Features
227
+
228
+ * **tools:** add per-action execution timing to composed tools ([b857f1d](https://github.com/zlliang/pi-spark/commit/b857f1d8ccd1b5b5713c3486291889b7d037e298))
229
+
230
+ ## [0.14.3](https://github.com/zlliang/pi-spark/compare/v0.14.2...v0.14.3) (2026-06-14)
231
+
232
+
233
+ ### Code Refactoring
234
+
235
+ * **tools:** share composed-tool architecture across pi and web ([12e563b](https://github.com/zlliang/pi-spark/commit/12e563b357f9e5cf83fef04943c66feffd16c26f))
236
+
237
+ ## [0.14.2](https://github.com/zlliang/pi-spark/compare/v0.14.1...v0.14.2) (2026-06-14)
238
+
239
+
240
+ ### Bug Fixes
241
+
242
+ * **pi:** simplify missing model display ([ab6389e](https://github.com/zlliang/pi-spark/commit/ab6389ea0e8e13360c221d6b9fc450e80514416d))
243
+
244
+ ## [0.14.1](https://github.com/zlliang/pi-spark/compare/v0.14.0...v0.14.1) (2026-06-14)
245
+
246
+
247
+ ### Miscellaneous Chores
248
+
249
+ * trigger release 0.14.1 ([b966f0e](https://github.com/zlliang/pi-spark/commit/b966f0ecb4739cbf250051de652fc0f51fa9d5f9))
250
+
251
+ ## [0.14.0](https://github.com/zlliang/pi-spark/compare/v0.13.1...v0.14.0) (2026-06-14)
252
+
253
+
254
+ ### Features
255
+
256
+ * add unified pi and web agent tools ([1229d93](https://github.com/zlliang/pi-spark/commit/1229d93e1beffd444767781c3c1332e26706dc8a))
257
+
258
+ ## [0.13.1](https://github.com/zlliang/pi-spark/compare/v0.13.0...v0.13.1) (2026-06-14)
259
+
260
+
261
+ ### Documentation
262
+
263
+ * refresh README screenshots ([85948f7](https://github.com/zlliang/pi-spark/commit/85948f763b7fdcdf6020f17ebc425398a3133bc9))
264
+
265
+ ## [0.13.0](https://github.com/zlliang/pi-spark/compare/v0.12.0...v0.13.0) (2026-06-14)
266
+
267
+
268
+ ### Code Refactoring
269
+
270
+ * consolidate features into a single extension ([5fd0f5c](https://github.com/zlliang/pi-spark/commit/5fd0f5c5a577fe0808a91b4d313a1cd569e9288f))
271
+
272
+ ## [0.12.0](https://github.com/zlliang/pi-spark/compare/v0.11.2...v0.12.0) (2026-06-13)
273
+
274
+
275
+ ### ⚠ BREAKING CHANGES
276
+
277
+ * fold pi-credits into pi-spark and revert to single package
278
+
279
+ ### Miscellaneous Chores
280
+
281
+ * release 0.12.0 ([e80081a](https://github.com/zlliang/pi-spark/commit/e80081a2d352da108feea446d44fb63f9ecc68ff))
282
+
283
+
284
+ ### Code Refactoring
285
+
286
+ * fold pi-credits into pi-spark and revert to single package ([3a1cbe3](https://github.com/zlliang/pi-spark/commit/3a1cbe360feeab02a1d8b8313eaf6b06b4f8d400))
287
+
288
+ ## [0.11.2](https://github.com/zlliang/pi-spark/compare/v0.11.1...v0.11.2) (2026-06-13)
289
+
290
+
291
+ ### Documentation
292
+
293
+ * **pi-spark:** simplify description ([ede2299](https://github.com/zlliang/pi-spark/commit/ede2299cee377401c066bd76d8e795c9f96d4f1d))
294
+
295
+ ## [0.11.1](https://github.com/zlliang/pi-spark/compare/v0.11.0...v0.11.1) (2026-06-13)
296
+
297
+
298
+ ### Documentation
299
+
300
+ * **pi-spark:** polish README and metadata ([382de1a](https://github.com/zlliang/pi-spark/commit/382de1aeaa48de7d874d088cba60b6ff1e60d42d))
301
+
302
+ ## [0.11.0](https://github.com/zlliang/pi-spark/compare/v0.10.5...v0.11.0) (2026-06-13)
303
+
304
+
305
+ ### Miscellaneous Chores
306
+
307
+ * **pi-spark:** release 0.11.0 ([c5124d4](https://github.com/zlliang/pi-spark/commit/c5124d4070c7caa96e0e1fc37e0a23b45232e38c))
308
+
309
+ ## [0.10.5](https://github.com/zlliang/pi-spark/compare/v0.10.4...v0.10.5) (2026-06-13)
310
+
311
+
312
+ ### Bug Fixes
313
+
314
+ * **pi-spark:** reassert clearOnShrink each render in fullscreen ([55604b5](https://github.com/zlliang/pi-spark/commit/55604b59742b5300ef80102f9fbc3fc1c7a798cd))
315
+
316
+ ## [0.10.4](https://github.com/zlliang/pi-spark/compare/v0.10.3...v0.10.4) (2026-06-13)
317
+
318
+
319
+ ### Bug Fixes
320
+
321
+ * add npm package homepage metadata ([9ff2e39](https://github.com/zlliang/pi-spark/commit/9ff2e39d609c3c0a39c45a6c6b456bde964fbaa8))
322
+
323
+ ## [0.10.3](https://github.com/zlliang/pi-spark/compare/v0.10.2...v0.10.3) (2026-06-13)
324
+
325
+
326
+ ### Miscellaneous Chores
327
+
328
+ * **pi-spark:** release 0.10.3 ([c253364](https://github.com/zlliang/pi-spark/commit/c2533641a4c9fe3b91ad6d0a52077d65036a95f0))
329
+
330
+ ## [0.10.2](https://github.com/zlliang/pi-spark/compare/v0.10.1...v0.10.2) (2026-06-13)
331
+
332
+
333
+ ### Bug Fixes
334
+
335
+ * simplify cost display ([4990a57](https://github.com/zlliang/pi-spark/commit/4990a57fd9688929e115d64121a009630988d93e))
336
+
337
+ ## [0.10.1](https://github.com/zlliang/pi-spark/compare/v0.10.0...v0.10.1) (2026-06-13)
338
+
339
+
340
+ ### Miscellaneous Chores
341
+
342
+ * bump to 0.10.1 ([14a7b3d](https://github.com/zlliang/pi-spark/commit/14a7b3dc56d329095e478d0e0e1979a177bb1cfe))
343
+
344
+ ## [0.10.0](https://github.com/zlliang/pi-spark/compare/v0.9.5...v0.10.0) (2026-06-13)
345
+
346
+
347
+ ### ⚠ BREAKING CHANGES
348
+
349
+ * **models:** the model tool's "current" action is renamed to "active", and the scope/provider/model params are replaced by "query".
350
+
351
+ ### Features
352
+
353
+ * **models:** filter model lists with Liqe queries ([877479b](https://github.com/zlliang/pi-spark/commit/877479b51a715b7a5a05381b207ecd03837e433d))
354
+
355
+
356
+ ### Styles
357
+
358
+ * **name:** use complete sentences in tool prose ([3430899](https://github.com/zlliang/pi-spark/commit/343089946f685b70c1bcda0291cf511f536a0711))
359
+
360
+ ## [0.9.5](https://github.com/zlliang/pi-spark/compare/v0.9.4...v0.9.5) (2026-06-12)
361
+
362
+
363
+ ### Bug Fixes
364
+
365
+ * **models:** keep unavailable model labels visible ([feab815](https://github.com/zlliang/pi-spark/commit/feab8159210033ad362a4951e835247e28325063))
366
+
367
+ ## [0.9.4](https://github.com/zlliang/pi-spark/compare/v0.9.3...v0.9.4) (2026-06-12)
368
+
369
+
370
+ ### Bug Fixes
371
+
372
+ * **models:** expose unavailable models by scope ([8df5053](https://github.com/zlliang/pi-spark/commit/8df5053b1b92aaa78a366088b9414e7f197c4072))
373
+
374
+ ## [0.9.3](https://github.com/zlliang/pi-spark/compare/v0.9.2...v0.9.3) (2026-06-12)
375
+
376
+
377
+ ### Bug Fixes
378
+
379
+ * **models:** align action description order ([3de17c1](https://github.com/zlliang/pi-spark/commit/3de17c1d5f341eeb8673c70060a8dcb8febe8f5c))
380
+ * update model and name extension exports ([68b5191](https://github.com/zlliang/pi-spark/commit/68b51916c89717ac26ccafb863d420d16dd0423c))
381
+
382
+ ## [0.9.2](https://github.com/zlliang/pi-spark/compare/v0.9.1...v0.9.2) (2026-06-12)
383
+
384
+
385
+ ### Bug Fixes
386
+
387
+ * **models:** show expand hint in model tool call ([a7ac029](https://github.com/zlliang/pi-spark/commit/a7ac02943e0621e04093c5cc0a81a192fe440801))
388
+
389
+ ## [0.9.1](https://github.com/zlliang/pi-spark/compare/v0.9.0...v0.9.1) (2026-06-12)
390
+
391
+
392
+ ### Bug Fixes
393
+
394
+ * **models:** polish model tool prompt guideline ([eb01e6b](https://github.com/zlliang/pi-spark/commit/eb01e6b5a3b5dd180d32b9a9c8e06e4b90f42036))
395
+
396
+ ## [0.9.0](https://github.com/zlliang/pi-spark/compare/v0.8.0...v0.9.0) (2026-06-12)
397
+
398
+
399
+ ### Features
400
+
401
+ * **models:** add model inspection tool ([4d2625e](https://github.com/zlliang/pi-spark/commit/4d2625e2e2bd2e5637c50e563efff7b271a5a21d))
402
+
403
+ ## [0.8.0](https://github.com/zlliang/pi-spark/compare/v0.7.0...v0.8.0) (2026-06-10)
404
+
405
+
406
+ ### Features
407
+
408
+ * **credits:** generalize codex-usage into multi-provider credits ([84ffc8e](https://github.com/zlliang/pi-spark/commit/84ffc8e25256619a6b82759095be554433de15d0))
409
+ * move credits extension to the dedicated pi-credits package ([2d1e868](https://github.com/zlliang/pi-spark/commit/2d1e868f2c541c3707c9564ccdedaf80f40a7be5))
410
+
411
+ ## [0.7.0](https://github.com/zlliang/pi-spark/compare/v0.6.3...v0.7.0) (2026-06-10)
412
+
413
+
414
+ ### Code Refactoring
415
+
416
+ * remove trust-all extension ([2a783f9](https://github.com/zlliang/pi-spark/commit/2a783f9d3aa3031f0c204b047546c8f5d1aa6728))
417
+
418
+ ## [0.6.3](https://github.com/zlliang/pi-spark/compare/v0.6.2...v0.6.3) (2026-06-09)
419
+
420
+
421
+ ### Code Refactoring
422
+
423
+ * rename session naming tool ([49f8502](https://github.com/zlliang/pi-spark/commit/49f850226a175e280b0337f96fbaa649276d1153))
424
+
425
+ ## [0.6.2](https://github.com/zlliang/pi-spark/compare/v0.6.1...v0.6.2) (2026-06-09)
426
+
427
+
428
+ ### Code Refactoring
429
+
430
+ * adjust set-session-name reason handling ([6d1b0b8](https://github.com/zlliang/pi-spark/commit/6d1b0b8ba3859b0a8b991758f01582caf65d6c8c))
431
+
432
+ ## [0.6.1](https://github.com/zlliang/pi-spark/compare/v0.6.0...v0.6.1) (2026-06-09)
433
+
434
+
435
+ ### Code Refactoring
436
+
437
+ * **editor:** show preset name in bold without prefix ([79a5fe2](https://github.com/zlliang/pi-spark/commit/79a5fe2910647094503bca9ccbc69326c6ade12a))
438
+
439
+ ## [0.6.0](https://github.com/zlliang/pi-spark/compare/v0.5.2...v0.6.0) (2026-06-09)
440
+
441
+
442
+ ### Features
443
+
444
+ * add trust-all extension ([2784e47](https://github.com/zlliang/pi-spark/commit/2784e4719689280029864c45455bdac1d515669a))
445
+ * **codex-usage:** refresh after usage events ([bc53364](https://github.com/zlliang/pi-spark/commit/bc533646d085966376278ec6672b7d51d11c3e2d))
446
+ * **editor:** add pulse spinner preset ([9033de9](https://github.com/zlliang/pi-spark/commit/9033de9552630b16481dcb37aa9556bd37ab1bc7))
447
+
448
+ ## [0.5.2](https://github.com/zlliang/pi-spark/compare/v0.5.1...v0.5.2) (2026-06-08)
449
+
450
+
451
+ ### Bug Fixes
452
+
453
+ * **codex-usage:** round status percentages ([a641329](https://github.com/zlliang/pi-spark/commit/a64132919bd99d67d2dd039206ab80a41cfab720))
454
+
455
+ ## [0.5.1](https://github.com/zlliang/pi-spark/compare/v0.5.0...v0.5.1) (2026-06-08)
456
+
457
+
458
+ ### Bug Fixes
459
+
460
+ * **codex-usage:** polish usage error message ([99edccf](https://github.com/zlliang/pi-spark/commit/99edccf6d19b3fdb334414b46f59a25d542bc03d))
461
+
462
+ ## [0.5.0](https://github.com/zlliang/pi-spark/compare/v0.4.0...v0.5.0) (2026-06-08)
463
+
464
+
465
+ ### Features
466
+
467
+ * **codex-usage:** add Codex rate-limit usage status ([6778581](https://github.com/zlliang/pi-spark/commit/67785818e3cb2304b16b1feaf1dfb58520cb1542))
468
+
469
+ ## [0.4.0](https://github.com/zlliang/pi-spark/compare/v0.3.1...v0.4.0) (2026-06-08)
470
+
471
+
472
+ ### Features
473
+
474
+ * **set-session-name:** add session naming tool ([bc60e90](https://github.com/zlliang/pi-spark/commit/bc60e9049e17e1b82fc0d936731235f2bd4e94dd))
475
+
476
+ ## [0.3.1](https://github.com/zlliang/pi-spark/compare/v0.3.0...v0.3.1) (2026-06-08)
477
+
478
+
479
+ ### Bug Fixes
480
+
481
+ * **fullscreen:** keep clearOnShrink enabled across reload ([e1adde0](https://github.com/zlliang/pi-spark/commit/e1adde0eca052319dd9231f6e22456ae33109289))
482
+
483
+ ## [0.3.0](https://github.com/zlliang/pi-spark/compare/v0.2.1...v0.3.0) (2026-06-06)
484
+
485
+
486
+ ### Features
487
+
488
+ * **footer:** shorten cwd display ([8ae3525](https://github.com/zlliang/pi-spark/commit/8ae3525af6311de94c4b85f07e3f9c6df5369011))
489
+ * **fullscreen:** clear session on exit ([b48b997](https://github.com/zlliang/pi-spark/commit/b48b9972365da5c24d2f90f36d1ba0e945d87e94))
490
+
491
+
492
+ ### Bug Fixes
493
+
494
+ * **fullscreen:** style exit session label ([b7fcd80](https://github.com/zlliang/pi-spark/commit/b7fcd80113e52efd6c46bcd617c3c0520fcb2fb0))
495
+
496
+ ## [0.2.1](https://github.com/zlliang/pi-spark/compare/v0.2.0...v0.2.1) (2026-06-04)
497
+
498
+
499
+ ### Miscellaneous Chores
500
+
501
+ * release 0.2.1 ([d296d5d](https://github.com/zlliang/pi-spark/commit/d296d5de0eb890b1f9c10dd5edd7343224a58819))
502
+
503
+ ## [0.2.0](https://github.com/zlliang/pi-spark/compare/v0.1.2...v0.2.0) (2026-06-04)
504
+
505
+
506
+ ### Features
507
+
508
+ * **editor:** show working status in border ([b72b127](https://github.com/zlliang/pi-spark/commit/b72b12738b209bb3016009c88e2efd1544d770f6))
509
+ * **footer:** include statuses in main line ([93a18de](https://github.com/zlliang/pi-spark/commit/93a18de6fbd53e7751dbecb01f9efa7b954d8bdf))
510
+ * **presets:** support preset CLI flag ([435c164](https://github.com/zlliang/pi-spark/commit/435c164b61ce231074fdbdee636d45af1845bebf))
511
+
512
+
513
+ ### Bug Fixes
514
+
515
+ * **editor:** clear tool status after executions ([780395f](https://github.com/zlliang/pi-spark/commit/780395f79e078dd7c4af596f5dd66b344ce3ef34))
516
+ * **editor:** track running tools by call id ([72fbe37](https://github.com/zlliang/pi-spark/commit/72fbe370995296988bcd3100d2c25721b08af62b))
517
+
518
+ ## [0.1.2](https://github.com/zlliang/pi-spark/compare/v0.1.1...v0.1.2) (2026-06-03)
519
+
520
+
521
+ ### Bug Fixes
522
+
523
+ * **editor:** add tildes spinner preset ([523f6fc](https://github.com/zlliang/pi-spark/commit/523f6fcd982455fbfe202982c5dfb7a62590b6dd))
524
+
525
+ ## [0.1.1](https://github.com/zlliang/pi-spark/compare/v0.1.0...v0.1.1) (2026-06-02)
526
+
527
+
528
+ ### Miscellaneous Chores
529
+
530
+ * release 0.1.1 ([09bfd96](https://github.com/zlliang/pi-spark/commit/09bfd96eec21dc9467bbbf10946bc5bb9cf858f7))
531
+
532
+ ## [0.1.0](https://github.com/zlliang/pi-spark/releases/tag/v0.1.0) (2026-06-02)
533
+
534
+ Initial release of pi-spark, including the Editor, Footer, Fullscreen, Presets, and Recap extensions, plus shared TUI components and spark configuration support.
package/README.md CHANGED
@@ -56,6 +56,12 @@ Banked rate-limit resets are saved benefits that can reset eligible Codex usage
56
56
 
57
57
  ![Codex resets](./assets/screenshot-codex-resets.png)
58
58
 
59
+ #### Peak/off-peak pricing
60
+
61
+ Pi does not currently support time-based pricing, so its cost estimates can be inaccurate for providers with peak/off-peak rates, such as [DeepSeek](https://api-docs.deepseek.com/quick_start/pricing). pi-spark updates model cost metadata so subsequent usage calculations reflect the current rate.
62
+
63
+ ![Peak/off-peak pricing](./assets/screenshot-peak-off-peak-pricing.png)
64
+
59
65
  ### Presets
60
66
 
61
67
  pi-spark lets you define named model presets in `spark.json` (see [Configuration](#configuration)), so you can switch between models and thinking levels without retyping provider details. The active preset is shown on the editor's top border.
@@ -145,7 +151,7 @@ All fields are optional. Each top-level feature runs with the defaults below unl
145
151
 
146
152
  #### `CreditsConfig`
147
153
 
148
- All supported providers are enabled by default. Set a provider to `false` to disable its credits status, or to `true` to override a global `false` in project config.
154
+ All supported providers are enabled by default. Set a provider to `false` to disable its credits status and pricing updates, or to `true` to override a global `false` in project config.
149
155
 
150
156
  ```json
151
157
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-spark",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
4
4
  "description": "Pi package that polishes your daily experience",
5
5
  "keywords": [
6
6
  "pi-coding-agent",
@@ -13,13 +13,14 @@
13
13
  "license": "MIT",
14
14
  "type": "module",
15
15
  "files": [
16
- "index.ts",
17
- "src",
18
- "themes",
19
16
  "assets",
20
17
  "docs",
21
- "README.md",
22
- "LICENSE"
18
+ "src",
19
+ "themes",
20
+ "CHANGELOG.md",
21
+ "index.ts",
22
+ "LICENSE.md",
23
+ "README.md"
23
24
  ],
24
25
  "pi": {
25
26
  "extensions": [
@@ -19,8 +19,8 @@ const DEFAULT_INLINE_TEXT_OPTIONS: Required<InlineTextOptions> = {
19
19
  /**
20
20
  * Text component that renders inline and truncates it to fit the given width.
21
21
  *
22
- * The given width is a budget, not a guaranteed rendered width. After rendering, callers should
23
- * check the result with visibleWidth(), because the rendered width may be smaller than the budget.
22
+ * The given width is a budget, not a guaranteed rendered width. After rendering, check the result
23
+ * with visibleWidth(), because the rendered width may be smaller than the budget.
24
24
  */
25
25
  export class InlineText implements Component {
26
26
  private text: string;
@@ -12,7 +12,7 @@ const CONFIG_FILE = "spark.json";
12
12
 
13
13
  const cache = new Map<string, SparkConfig>();
14
14
 
15
- /** Load and validate spark.json once per session lifecycle; later calls return the cached result. */
15
+ /** Loads and validates spark.json once per session lifecycle; later calls return the cached result. */
16
16
  export function loadConfig(ctx: ExtensionContext): SparkConfig {
17
17
  const cached = cache.get(ctx.cwd);
18
18
  if (cached) return cached;
@@ -1,24 +1,15 @@
1
1
  import { CreditsManager } from "./manager";
2
2
  import { getEnabledProviders, registerProviderExtensions } from "./providers";
3
3
  import { loadConfig } from "../../config";
4
- import { isUsage } from "../../utils/usage";
5
4
 
6
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
7
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
8
6
 
9
- function hasCost(message: AgentMessage): boolean {
10
- const usage = (message as { usage?: unknown }).usage;
11
- if (!isUsage(usage)) return false;
12
-
13
- return usage.cost.total > 0 || usage.input > 0 || usage.output > 0;
14
- }
15
-
16
7
  export function registerCredits(pi: ExtensionAPI): void {
17
8
  let creditsManager: CreditsManager | undefined = undefined;
18
9
 
19
10
  pi.on("session_start", (_event, ctx) => {
20
11
  const config = loadConfig(ctx).credits;
21
- if (!["tui", "rpc"].includes(ctx.mode) || !config) return;
12
+ if (!config) return;
22
13
 
23
14
  const providers = getEnabledProviders(config);
24
15
  if (providers.length === 0) return;
@@ -33,9 +24,19 @@ export function registerCredits(pi: ExtensionAPI): void {
33
24
  creditsManager?.refresh(ctx);
34
25
  });
35
26
 
36
- pi.on("turn_end", (event, ctx) => {
37
- if (!hasCost(event.message)) return;
27
+ pi.on("context", (_event, ctx) => {
28
+ creditsManager?.refresh(ctx);
29
+ });
30
+
31
+ pi.on("turn_end", (_event, ctx) => {
32
+ creditsManager?.refresh(ctx);
33
+ });
38
34
 
35
+ pi.on("agent_settled", (_event, ctx) => {
36
+ creditsManager?.refresh(ctx);
37
+ });
38
+
39
+ pi.on("session_before_compact", (_event, ctx) => {
39
40
  creditsManager?.refresh(ctx);
40
41
  });
41
42
 
@@ -43,6 +44,12 @@ export function registerCredits(pi: ExtensionAPI): void {
43
44
  creditsManager?.refresh(ctx);
44
45
  });
45
46
 
47
+ pi.on("session_before_tree", (event, ctx) => {
48
+ if (!event.preparation.userWantsSummary) return;
49
+
50
+ creditsManager?.refresh(ctx);
51
+ });
52
+
46
53
  pi.on("session_tree", (event, ctx) => {
47
54
  if (!event.summaryEntry) return;
48
55
 
@@ -1,5 +1,6 @@
1
1
  import { getAuthToken } from "../../utils/auth";
2
2
  import { findProvider } from "./providers";
3
+ import { applyPricingRules } from "./pricing";
3
4
  import { renderCredits, renderError } from "./status";
4
5
 
5
6
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -17,6 +18,7 @@ export class CreditsManager {
17
18
  }
18
19
 
19
20
  async refresh(ctx: ExtensionContext): Promise<void> {
21
+ this.providers.forEach((provider) => applyPricingRules(ctx, provider, Date.now()));
20
22
  this.inflight?.abort();
21
23
 
22
24
  const provider = findProvider(this.providers, ctx.model?.provider);
@@ -55,12 +57,10 @@ export class CreditsManager {
55
57
  return;
56
58
  }
57
59
 
58
- const credits = await provider.fetch(token, signal);
60
+ const credits = await provider.fetch(token, signal, ctx.model?.cost);
61
+ if (signal.aborted || ctx.model?.provider !== provider.id) return;
59
62
 
60
- // The active model may have changed while the request was in flight.
61
- if (ctx.model?.provider !== provider.id) return;
62
-
63
- ctx.ui.setStatus(STATUS_KEY, renderCredits(ctx.ui.theme, provider.label, credits));
63
+ ctx.ui.setStatus(STATUS_KEY, renderCredits(ctx.ui.theme, provider.label, credits, provider.link));
64
64
  } catch (error) {
65
65
  if (signal.aborted) return;
66
66
  if (ctx.model?.provider !== provider.id) return;
@@ -0,0 +1,57 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import type { CreditsProvider } from "./types";
4
+
5
+ export type Cost = Model<Api>["cost"] & { label?: string };
6
+
7
+ /**
8
+ * A time-of-day price window. `start`/`end` are `HH:MM` in UTC. Bounds are [start, end); equal
9
+ * bounds mean all day. When `start > end` the window wraps past midnight (e.g., 16:30 → 00:30).
10
+ */
11
+ interface PricingWindow {
12
+ start: string;
13
+ end: string;
14
+ days?: number[];
15
+ cost: Cost;
16
+ }
17
+
18
+ export interface PricingRule {
19
+ model: string;
20
+ defaultCost: Cost;
21
+ windows: PricingWindow[];
22
+ }
23
+
24
+ /** Updates registry and active model prices without changing the provider's rule data. */
25
+ export function applyPricingRules(ctx: ExtensionContext, provider: CreditsProvider, timestamp: number): void {
26
+ (provider.pricingRules ?? []).forEach((rule) => {
27
+ const cost = resolveCost(rule, timestamp);
28
+ const model = ctx.modelRegistry.find(provider.id, rule.model);
29
+
30
+ if (model) model.cost = { ...cost };
31
+ if (ctx.model?.provider === provider.id && ctx.model.id === rule.model) ctx.model.cost = { ...cost };
32
+ });
33
+ }
34
+
35
+ function resolveCost(rule: PricingRule, timestamp: number): Cost {
36
+ const date = new Date(timestamp);
37
+ const minutes = date.getUTCHours() * 60 + date.getUTCMinutes();
38
+
39
+ const cost = rule.windows.reduce((result, window) => {
40
+ if (window.days && !window.days.includes(date.getUTCDay())) return result;
41
+ if (inDailyRange(minutes, parseHHMM(window.start), parseHHMM(window.end))) return window.cost;
42
+ return result;
43
+ }, rule.defaultCost);
44
+
45
+ return cost;
46
+ }
47
+
48
+ function parseHHMM(hhmm: string): number {
49
+ const [hours, minutes] = hhmm.split(":");
50
+ return Number(hours) * 60 + Number(minutes);
51
+ }
52
+
53
+ function inDailyRange(minutes: number, start: number, end: number): boolean {
54
+ if (start === end) return true;
55
+ if (start < end) return minutes >= start && minutes < end;
56
+ return minutes >= start || minutes < end;
57
+ }
@@ -1,6 +1,7 @@
1
1
  import { convertToUSD, toNumber } from "../../../utils/format";
2
2
  import { http, withAuth } from "../../../utils/http";
3
3
 
4
+ import type { Cost } from "../pricing";
4
5
  import type { Credits, CreditsProvider } from "../types";
5
6
 
6
7
  const PROVIDER = "deepseek";
@@ -18,12 +19,50 @@ interface DeepSeekBalanceInfo {
18
19
  export const deepseekProvider: CreditsProvider = {
19
20
  id: PROVIDER,
20
21
  label: "DeepSeek",
22
+ link: "https://platform.deepseek.com/usage",
21
23
 
22
- async fetch(apiKey, signal): Promise<Credits> {
24
+ // See https://api-docs.deepseek.com/quick_start/pricing/.
25
+ pricingRules: [
26
+ {
27
+ model: "deepseek-flash",
28
+ defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "off-peak" },
29
+ windows: [
30
+ { start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
31
+ { start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
32
+ ],
33
+ },
34
+ {
35
+ model: "deepseek-v4-flash",
36
+ defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "off-peak" },
37
+ windows: [
38
+ { start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
39
+ { start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
40
+ ],
41
+ },
42
+ {
43
+ model: "deepseek-v4-flash-vision-exp",
44
+ defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "off-peak" },
45
+ windows: [
46
+ { start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
47
+ { start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "peak" } },
48
+ ],
49
+ },
50
+ {
51
+ // Update to Flash rates when V4 Pro retires on September 14, 2026 at 04:00 UTC.
52
+ model: "deepseek-v4-pro",
53
+ defaultCost: { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0, label: "off-peak" },
54
+ windows: [
55
+ { start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "peak" } },
56
+ { start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "peak" } },
57
+ ],
58
+ },
59
+ ],
60
+
61
+ async fetch(apiKey, signal, cost?: Cost): Promise<Credits> {
23
62
  const payload = await withAuth(http, apiKey).get(URL, { signal }).json<DeepSeekBalanceResponse>();
24
63
  const balance = payload.balance_infos?.find((entry) => entry.currency === "USD") ?? payload.balance_infos?.[0];
25
64
  const remaining = await convertToUSD(toNumber(balance?.total_balance), balance?.currency, signal);
26
65
 
27
- return { type: "balance", remaining };
66
+ return { type: "balance", remaining, suffix: cost?.label ? `(${cost.label})` : undefined };
28
67
  },
29
68
  };
@@ -107,6 +107,7 @@ function moneyToNumber(money: Money | null | undefined): number | undefined {
107
107
  export const fireworksProvider: CreditsProvider = {
108
108
  id: PROVIDER,
109
109
  label: "Fireworks",
110
+ link: "https://app.fireworks.ai/account/billing",
110
111
 
111
112
  async fetch(apiKey, signal): Promise<Credits> {
112
113
  const name = await resolveAccount(apiKey, signal);
@@ -31,5 +31,5 @@ export function findProvider(providers: CreditsProvider[], provider?: string): C
31
31
  }
32
32
 
33
33
  export function registerProviderExtensions(pi: ExtensionAPI, ctx: ExtensionContext, providers: CreditsProvider[], refresh: RefreshCredits): void {
34
- for (const provider of providers) provider.register?.(pi, ctx, refresh);
34
+ providers.forEach((provider) => provider.register?.(pi, ctx, refresh));
35
35
  }
@@ -1,4 +1,4 @@
1
- import { toNumber } from "../../../utils/format";
1
+ import { convertToUSD, toNumber } from "../../../utils/format";
2
2
  import { http, withAuth } from "../../../utils/http";
3
3
 
4
4
  import type { Credits, CreditsLane, CreditsProvider } from "../types";
@@ -11,6 +11,7 @@ interface KimiCodeUsage {
11
11
  limit?: string | number;
12
12
  used?: string | number;
13
13
  remaining?: string | number;
14
+ resetTime?: string | null;
14
15
  }
15
16
 
16
17
  interface KimiCodeLimit {
@@ -24,6 +25,8 @@ interface KimiCodeBoosterWallet {
24
25
  amount?: string | number;
25
26
  amountLeft?: string | number;
26
27
  } | null;
28
+ monthlyChargeLimit?: { currency?: string } | null;
29
+ monthlyUsed?: { currency?: string } | null;
27
30
  }
28
31
 
29
32
  interface KimiCodeUsageResponse {
@@ -50,24 +53,24 @@ function formatWindowLabel(window?: { duration?: number; timeUnit?: string } | n
50
53
  if (!window?.duration) return "Limit";
51
54
 
52
55
  const duration = window.duration;
53
- const unit = window.timeUnit?.toUpperCase() ?? "";
56
+ const unit = window.timeUnit;
54
57
 
55
- if (unit === "TIME_UNIT_MINUTE") {
56
- if (duration >= 60 && duration % 60 === 0) return `${duration / 60}h`;
57
- return `${duration}m`;
58
- }
58
+ if (unit === "TIME_UNIT_MINUTE") return (duration >= 60 && duration % 60 === 0) ? `${duration / 60}h` : `${duration}m`;
59
59
  if (unit === "TIME_UNIT_HOUR") return `${duration}h`;
60
60
  if (unit === "TIME_UNIT_DAY") return `${duration}d`;
61
- if (unit === "TIME_UNIT_MONTH") return `${duration}mo`;
62
- return `${duration}${unit.replace("TIME_UNIT_", "").toLowerCase()}`;
61
+ if (unit === "TIME_UNIT_WEEK") return `${duration * 7}d`;
62
+ return "Limit";
63
63
  }
64
64
 
65
65
  function buildLane(label: string, detail: KimiCodeUsage): CreditsLane | undefined {
66
66
  const percent = toPercent(detail);
67
- return percent === undefined ? undefined : { label, percent };
67
+ if (percent === undefined) return undefined;
68
+
69
+ const resetAt = Date.parse(detail.resetTime ?? "");
70
+ return { label, percent, resetAt: Number.isFinite(resetAt) ? resetAt : undefined };
68
71
  }
69
72
 
70
- function formatSuffix(wallet?: KimiCodeBoosterWallet | null): string | undefined {
73
+ async function formatSuffix(wallet: KimiCodeBoosterWallet | null | undefined, signal: AbortSignal): Promise<string | undefined> {
71
74
  if (wallet?.balance?.type !== "BOOSTER") return undefined;
72
75
 
73
76
  const amount = toNumber(wallet.balance.amount);
@@ -75,7 +78,9 @@ function formatSuffix(wallet?: KimiCodeBoosterWallet | null): string | undefined
75
78
 
76
79
  const amountLeft = Math.max(0, toNumber(wallet.balance.amountLeft) ?? 0);
77
80
  const remaining = Math.round(amountLeft / BOOSTER_FIXED_POINT_CENTS) / 100;
78
- return `(Extra Usage $${remaining.toFixed(2)})`;
81
+ const currency = wallet.monthlyChargeLimit?.currency || wallet.monthlyUsed?.currency || "USD";
82
+ const remainingUSD = await convertToUSD(remaining, currency, signal);
83
+ return !!remainingUSD ? `(Extra Usage $${remainingUSD.toFixed(2)})` : undefined;
79
84
  }
80
85
 
81
86
  export const kimiCodeProvider: CreditsProvider = {
@@ -100,6 +105,6 @@ export const kimiCodeProvider: CreditsProvider = {
100
105
 
101
106
  if (lanes.length === 0) throw new Error("no usage data");
102
107
 
103
- return { type: "windows", lanes, suffix: formatSuffix(payload.boosterWallet) };
108
+ return { type: "windows", lanes, suffix: await formatSuffix(payload.boosterWallet, signal) };
104
109
  },
105
110
  };
@@ -30,6 +30,7 @@ interface CodexUsageResponse {
30
30
  interface CodexRateWindow {
31
31
  limit_window_seconds: number;
32
32
  used_percent?: number | string;
33
+ reset_at?: number;
33
34
  }
34
35
 
35
36
  export interface BankedRateLimitReset {
@@ -120,6 +121,7 @@ function toLane(window: CodexRateWindow): CreditsLane {
120
121
  return {
121
122
  label: prettyMilliseconds(window.limit_window_seconds * 1_000, { compact: true }),
122
123
  percent: parseUsedPercent(window),
124
+ resetAt: !!window.reset_at ? window.reset_at * 1_000 : undefined,
123
125
  };
124
126
  }
125
127
 
@@ -131,6 +133,7 @@ function parseUsedPercent(window?: CodexRateWindow | null): number | undefined {
131
133
  export const openaiCodexProvider: CreditsProvider = {
132
134
  id: PROVIDER,
133
135
  label: "Codex",
136
+ link: "https://chatgpt.com/#settings/Usage",
134
137
 
135
138
  async fetch(apiKey, signal): Promise<Credits> {
136
139
  const client = createClient(apiKey);
@@ -16,6 +16,7 @@ interface OpenRouterCreditsResponse {
16
16
  export const openrouterProvider: CreditsProvider = {
17
17
  id: PROVIDER,
18
18
  label: "OpenRouter",
19
+ link: "https://openrouter.ai/settings/credits",
19
20
 
20
21
  async fetch(apiKey, signal): Promise<Credits> {
21
22
  const payload = await withAuth(http, apiKey).get(URL, { signal }).json<OpenRouterCreditsResponse>();
@@ -1,3 +1,6 @@
1
+ import { hyperlink } from "@earendil-works/pi-tui";
2
+ import prettyMilliseconds from "pretty-ms";
3
+
1
4
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
5
  import type { Credits, CreditsLane } from "./types";
3
6
 
@@ -6,12 +9,13 @@ const WINDOWS_ERROR = 90;
6
9
  const BALANCE_WARNING = 10;
7
10
  const BALANCE_ERROR = 5;
8
11
 
9
- export function renderCredits(theme: Theme, label: string, credits: Credits): string {
12
+ export function renderCredits(theme: Theme, label: string, credits: Credits, link?: string): string {
10
13
  const styledLabel = theme.fg("dim", label ? `${label} ` : "");
11
14
  const value = credits.type === "windows" ? renderWindows(theme, credits) : renderBalance(theme, credits);
12
15
  const suffix = credits.suffix ? ` ${theme.fg("dim", credits.suffix)}` : "";
13
16
 
14
- return `${styledLabel}${value}${suffix}`;
17
+ const text = `${styledLabel}${value}${suffix}`;
18
+ return link ? hyperlink(text, link) : text;
15
19
  }
16
20
 
17
21
  export function renderError(theme: Theme, label: string, message: string): string {
@@ -26,11 +30,11 @@ function renderWindows(theme: Theme, credits: Extract<Credits, { type: "windows"
26
30
  }
27
31
 
28
32
  function renderLane(theme: Theme, lane: CreditsLane): string {
29
- const text = `${lane.label} ${lane.percent === undefined ? "?" : lane.percent.toFixed(0)}%`;
33
+ const percent = `${lane.label} ${lane.percent === undefined ? "?" : lane.percent.toFixed(0)}%`;
34
+ const color = lane.percent && lane.percent > WINDOWS_ERROR ? "error" : lane.percent && lane.percent > WINDOWS_WARNING ? "warning" : "success";
35
+ const resetAt = !!lane.resetAt ? ` ${theme.fg("dim", `[⟳ ${prettyMilliseconds(Math.max(0, lane.resetAt - Date.now()), { compact: true })}]`)}` : "";
30
36
 
31
- if (lane.percent && lane.percent > WINDOWS_ERROR) return theme.fg("error", text);
32
- if (lane.percent && lane.percent > WINDOWS_WARNING) return theme.fg("warning", text);
33
- return theme.fg("success", text);
37
+ return `${theme.fg(color, percent)}${resetAt}`;
34
38
  }
35
39
 
36
40
  function renderBalance(theme: Theme, credits: Extract<Credits, { type: "balance" }>): string {
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { CreditsProviderId } from "./config";
3
+ import type { Cost, PricingRule } from "./pricing";
3
4
 
4
5
  /**
5
6
  * Normalized credits/usage for a provider.
@@ -15,6 +16,8 @@ export type Credits = (
15
16
  export interface CreditsLane {
16
17
  label: string;
17
18
  percent: number | undefined;
19
+ /** Timestamp in milliseconds. */
20
+ resetAt?: number;
18
21
  }
19
22
 
20
23
  export type RefreshCredits = (ctx: ExtensionContext) => Promise<void>;
@@ -23,6 +26,8 @@ export type RefreshCredits = (ctx: ExtensionContext) => Promise<void>;
23
26
  export interface CreditsProvider {
24
27
  readonly id: CreditsProviderId;
25
28
  readonly label: string;
26
- fetch(apiKey: string, signal: AbortSignal): Promise<Credits>;
29
+ readonly link?: string;
30
+ readonly pricingRules?: PricingRule[];
31
+ fetch(apiKey: string, signal: AbortSignal, cost?: Cost): Promise<Credits>;
27
32
  register?(pi: ExtensionAPI, ctx: ExtensionContext, refresh: RefreshCredits): void;
28
33
  }
@@ -53,12 +53,12 @@ class Editor extends CustomEditor {
53
53
  const lines = super.render(width);
54
54
  if (lines.length === 0) return lines;
55
55
 
56
- lines[0] = this.renderTopBorder(width);
56
+ lines[0] = this.getTopBorder(width);
57
57
 
58
58
  return lines;
59
59
  }
60
60
 
61
- private renderTopBorder(width: number): string {
61
+ private getTopBorder(width: number): string {
62
62
  const theme = this.ctx.ui.theme;
63
63
 
64
64
  const left = this.getLeft();
@@ -63,7 +63,7 @@ class FooterComponent implements Component {
63
63
  return [statusesText, styledCostText, styledContextUsageText].filter(Boolean).join(this.theme.fg("dim", " · "));
64
64
  }
65
65
 
66
- /** Get extension statuses, sorted by key alphabetically. */
66
+ /** Gets extension statuses, sorted alphabetically by key. */
67
67
  private getStatusesText(): string {
68
68
  const extensionStatuses = this.footerData.getExtensionStatuses();
69
69
  if (extensionStatuses.size === 0) return "";
@@ -3,7 +3,7 @@ import * as z from "zod";
3
3
 
4
4
  const MIN_IDLE_MS = 5_000;
5
5
 
6
- /** Accept a millisecond number or a human-readable duration (e.g., "3m"), normalized to milliseconds. */
6
+ /** Accepts a millisecond number or a human-readable duration (e.g., "3m"), normalized to milliseconds. */
7
7
  export const idleTimeoutSchema = z
8
8
  .union([z.number(), z.string()])
9
9
  .transform((value, ctx) => {
@@ -76,7 +76,7 @@ export class IdleListener<T> {
76
76
  this.enterCallbacks.forEach((callback) => callback(ctx));
77
77
  }
78
78
 
79
- /** Emit on every wake signal, even when the listener is already active. */
79
+ /** Emits on every wake signal, even when the listener is already active. */
80
80
  wake(ctx: T): void {
81
81
  this.stop();
82
82
  this.state = "active";
@@ -30,7 +30,7 @@ export class TitleManager {
30
30
  this.config = config;
31
31
  }
32
32
 
33
- /** Generate and set the session title once, silently, from the current context. */
33
+ /** Generates and sets the session title once, silently, from the current context. */
34
34
  async run(ctx: ExtensionContext): Promise<void> {
35
35
  if (this.pi.getSessionName() || this.inflight) return;
36
36
 
@@ -36,7 +36,7 @@ export function formatCwd(cwd: string, home: string): string {
36
36
  return shortenPath(displayCwd);
37
37
  }
38
38
 
39
- /** Shorten every path component except the first and last to one character, like fish `prompt_pwd`. */
39
+ /** Shortens every path component except the first and last to one character, like fish `prompt_pwd`. */
40
40
  function shortenPath(path: string): string {
41
41
  const parts = path.split(sep);
42
42
  const lastIndex = parts.length - 1;
@@ -47,7 +47,7 @@ function shortenPath(path: string): string {
47
47
  }).join(sep);
48
48
  }
49
49
 
50
- /** Replace newlines, tabs, carriage returns with space, then collapse multiple spaces */
50
+ /** Replaces newlines, tabs, and carriage returns with spaces, then collapses multiple spaces. */
51
51
  export function sanitizeText(text: string): string {
52
52
  return text
53
53
  .replace(/[\r\n\t]/g, " ")
@@ -55,7 +55,7 @@ export function sanitizeText(text: string): string {
55
55
  .trim();
56
56
  }
57
57
 
58
- /** Coerce a possibly-stringified numeric value to a finite number, or `undefined`. */
58
+ /** Coerces a possibly-stringified numeric value to a finite number, or `undefined`. */
59
59
  export function toNumber(value?: string | number | null): number | undefined {
60
60
  if (value === undefined || value === null) return undefined;
61
61
  const parsed = typeof value === "number" ? value : Number(value);
@@ -68,7 +68,7 @@ interface FrankfurterRateResponse {
68
68
  rate?: string | number;
69
69
  }
70
70
 
71
- /** Convert an amount in the given currency to USD via the Frankfurter API. */
71
+ /** Converts an amount in the given currency to USD via the Frankfurter API. */
72
72
  export async function convertToUSD(amount: number | undefined, currency: string | undefined, signal: AbortSignal): Promise<number | undefined> {
73
73
  if (amount === undefined) return undefined;
74
74
  if (!currency || currency === "USD") return amount;
@@ -27,7 +27,7 @@ type ThinkingLevelSelection = {
27
27
  };
28
28
 
29
29
  /**
30
- * Complete a one-shot background request, using an isolated session for OpenAI Codex models.
30
+ * Completes a one-shot background request, using an isolated session for OpenAI Codex models.
31
31
  * See [the investigation](../../docs/background-model-calls-and-openai-codex-sessions.md).
32
32
  */
33
33
  export const completeBackground: typeof completeSimple = async (model, context, options) => {
@@ -43,7 +43,7 @@ export const completeBackground: typeof completeSimple = async (model, context,
43
43
  };
44
44
 
45
45
  /**
46
- * Resolve the model and thinking level for a background feature (recap, title, ...).
46
+ * Resolves the model and thinking level for a background feature (recap, title, ...).
47
47
  *
48
48
  * `feature` names the config section so warnings can point at the offending fields. When the
49
49
  * feature's model config is incomplete or unavailable, this falls back to the session's main
@@ -26,9 +26,9 @@ export function isUsage(value: unknown): value is Usage {
26
26
  }
27
27
 
28
28
  /**
29
- * Extract usage from a session entry.
29
+ * Extracts usage from a session entry.
30
30
  *
31
- * `usage` live in different fields depending on the entry type:
31
+ * `usage` lives in different fields depending on the entry type:
32
32
  *
33
33
  * - `message` carries them on `message`.
34
34
  * - `custom` on `data`.