@ohgodtamit/pi-usage 0.1.0-alpha.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 +8 -0
- package/LICENSE +22 -0
- package/README.md +403 -0
- package/THIRD_PARTY_NOTICES.md +54 -0
- package/dist/aggregate.d.ts +392 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/format.d.ts +46 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/freshness.d.ts +42 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/mascot.d.ts +35 -0
- package/dist/mascot.d.ts.map +1 -0
- package/dist/prices.d.ts +24 -0
- package/dist/prices.d.ts.map +1 -0
- package/dist/provider.d.ts +147 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/view.d.ts +207 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/zai.d.ts +57 -0
- package/dist/zai.d.ts.map +1 -0
- package/package.json +60 -0
- package/src/aggregate.ts +1838 -0
- package/src/cache.ts +100 -0
- package/src/config.ts +93 -0
- package/src/format.ts +166 -0
- package/src/freshness.ts +55 -0
- package/src/index.ts +642 -0
- package/src/mascot.ts +227 -0
- package/src/prices.ts +63 -0
- package/src/provider.ts +704 -0
- package/src/view.ts +2585 -0
- package/src/zai.ts +101 -0
package/src/mascot.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi-chan — lightweight anime mascot for the usage panel TUI.
|
|
3
|
+
* ASCII poses + per-view icons keep the menu and Wrapped view playful
|
|
4
|
+
* without breaking narrow terminals.
|
|
5
|
+
*/
|
|
6
|
+
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { WrappedStats } from "./aggregate.ts";
|
|
8
|
+
import { formatHour } from "./format.ts";
|
|
9
|
+
|
|
10
|
+
/** Selectable top-level views (Tokscale-style menu + Wrapped AI). */
|
|
11
|
+
export type ViewKey =
|
|
12
|
+
| "overview"
|
|
13
|
+
| "models"
|
|
14
|
+
| "delegation"
|
|
15
|
+
| "daily"
|
|
16
|
+
| "stats"
|
|
17
|
+
| "hourly"
|
|
18
|
+
| "providers"
|
|
19
|
+
| "wrapped";
|
|
20
|
+
|
|
21
|
+
/** Ordered list of views for tab/arrow navigation. */
|
|
22
|
+
export const VIEW_ORDER: ViewKey[] = [
|
|
23
|
+
"overview",
|
|
24
|
+
"models",
|
|
25
|
+
"delegation",
|
|
26
|
+
"daily",
|
|
27
|
+
"stats",
|
|
28
|
+
"hourly",
|
|
29
|
+
"providers",
|
|
30
|
+
"wrapped",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export interface ViewTabMeta {
|
|
34
|
+
icon: string;
|
|
35
|
+
short: string;
|
|
36
|
+
label: string;
|
|
37
|
+
hint: string;
|
|
38
|
+
/** Accent when this tab is selected. */
|
|
39
|
+
color: ThemeColor;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const VIEW_TABS: Record<ViewKey, ViewTabMeta> = {
|
|
43
|
+
overview: {
|
|
44
|
+
icon: "◈",
|
|
45
|
+
short: "Over",
|
|
46
|
+
label: "Overview",
|
|
47
|
+
hint: "Quota bars & headline stats at a glance",
|
|
48
|
+
color: "accent",
|
|
49
|
+
},
|
|
50
|
+
models: {
|
|
51
|
+
icon: "◎",
|
|
52
|
+
short: "Mod",
|
|
53
|
+
label: "Models",
|
|
54
|
+
hint: "Models, skills, plugins, tools & projects",
|
|
55
|
+
color: "success",
|
|
56
|
+
},
|
|
57
|
+
delegation: {
|
|
58
|
+
icon: "⑂",
|
|
59
|
+
short: "Del",
|
|
60
|
+
label: "Delegation",
|
|
61
|
+
hint: "Direct and delegated usage & concurrency",
|
|
62
|
+
color: "accent",
|
|
63
|
+
},
|
|
64
|
+
daily: {
|
|
65
|
+
icon: "☀",
|
|
66
|
+
short: "Day",
|
|
67
|
+
label: "Daily",
|
|
68
|
+
hint: "Per-day activity, uptime & top model",
|
|
69
|
+
color: "warning",
|
|
70
|
+
},
|
|
71
|
+
stats: {
|
|
72
|
+
icon: "▦",
|
|
73
|
+
short: "Stat",
|
|
74
|
+
label: "Stats",
|
|
75
|
+
hint: "Contribution graph & streaks",
|
|
76
|
+
color: "accent",
|
|
77
|
+
},
|
|
78
|
+
hourly: {
|
|
79
|
+
icon: "⏱",
|
|
80
|
+
short: "Hr",
|
|
81
|
+
label: "Hourly",
|
|
82
|
+
hint: "Peak coding hours through the day",
|
|
83
|
+
color: "success",
|
|
84
|
+
},
|
|
85
|
+
providers: {
|
|
86
|
+
icon: "⚡",
|
|
87
|
+
short: "Prov",
|
|
88
|
+
label: "Providers",
|
|
89
|
+
hint: "Usage split by provider / backend",
|
|
90
|
+
color: "warning",
|
|
91
|
+
},
|
|
92
|
+
wrapped: {
|
|
93
|
+
icon: "✦",
|
|
94
|
+
short: "Wrap",
|
|
95
|
+
label: "Wrapped",
|
|
96
|
+
hint: "Year in review — [ ] or y to change year",
|
|
97
|
+
color: "accent",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Cycle palette for rainbow bars (monthly chart, tool rows, etc.). */
|
|
102
|
+
export const RAINBOW: ThemeColor[] = [
|
|
103
|
+
"accent",
|
|
104
|
+
"success",
|
|
105
|
+
"warning",
|
|
106
|
+
"error",
|
|
107
|
+
"accent",
|
|
108
|
+
"success",
|
|
109
|
+
"warning",
|
|
110
|
+
"accent",
|
|
111
|
+
"success",
|
|
112
|
+
"warning",
|
|
113
|
+
"error",
|
|
114
|
+
"accent",
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
export type MascotPose = "wave" | "celebrate" | "night" | "curious" | "sleepy" | "tools";
|
|
118
|
+
|
|
119
|
+
/** Raw ASCII lines per pose (no theme — color applied at render). */
|
|
120
|
+
const POSES: Record<MascotPose, string[]> = {
|
|
121
|
+
wave: [" ∧_∧", " (◕ ω ◕)", " / つ Year", " ( ~ )", " ~ ~ "],
|
|
122
|
+
celebrate: [" \\(^o^)/", " ∧_∧ ", " (≧▽≦)", " / つ !", " ★ ★ ★"],
|
|
123
|
+
night: [" ∧_∧ ", " (◕ ‿ ◕)", " / zzz", " ( つ )", " ~ ☾ "],
|
|
124
|
+
curious: [" ∧_∧", " (◕ ◡ ◕)?", " / つ hm", " ( ~ )", " ? ? "],
|
|
125
|
+
sleepy: [" ∧_∧", " ( - ω -)", " / つ zZ", " ( ~ )", " ~ ~ "],
|
|
126
|
+
tools: [" ∧_∧", " (◕ ▽ ◕)", " / つ ⚙", " ( ~ )", " ♪ ♪ ♪"],
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const VIEW_POSE: Partial<Record<ViewKey, MascotPose>> = {
|
|
130
|
+
overview: "wave",
|
|
131
|
+
models: "tools",
|
|
132
|
+
delegation: "curious",
|
|
133
|
+
daily: "curious",
|
|
134
|
+
stats: "celebrate",
|
|
135
|
+
hourly: "night",
|
|
136
|
+
providers: "wave",
|
|
137
|
+
wrapped: "celebrate",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** Pick a mascot pose for the active view or Wrapped stats mood. */
|
|
141
|
+
export function mascotPose(view: ViewKey, stats?: WrappedStats | null): MascotPose {
|
|
142
|
+
if (view === "wrapped") {
|
|
143
|
+
if (!stats) return "sleepy";
|
|
144
|
+
if (stats.longestStreak >= 7) return "celebrate";
|
|
145
|
+
if (stats.peakHour != null && stats.peakHour >= 22) return "night";
|
|
146
|
+
if (stats.modelCount >= 4) return "curious";
|
|
147
|
+
return "celebrate";
|
|
148
|
+
}
|
|
149
|
+
return VIEW_POSE[view] ?? "wave";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Compact mascot for the Wrapped report sidebar (4 lines, aligned). */
|
|
153
|
+
const WRAPPED_POSES: Record<MascotPose, string[]> = {
|
|
154
|
+
wave: [" ∧_∧", " (◕‿◕)", " /つ wrap", " ~~"],
|
|
155
|
+
celebrate: [" ∧_∧", " (≧◡≦)", " /つ ✦", " ★ ★"],
|
|
156
|
+
night: [" ∧_∧", " (◕‿◕)", " /つ ☾", " zzz"],
|
|
157
|
+
curious: [" ∧_∧", " (◕◡◕)", " /つ ?", " ~~"],
|
|
158
|
+
sleepy: [" ∧_∧", " (-ω-)", " /つ …", " ~~"],
|
|
159
|
+
tools: [" ∧_∧", " (◕‿◕)", " /つ ⚙", " ~~"],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Theme-colored mascot lines. */
|
|
163
|
+
export function renderMascot(pose: MascotPose, theme: Theme): string[] {
|
|
164
|
+
const lines = POSES[pose];
|
|
165
|
+
const faceColor: ThemeColor =
|
|
166
|
+
pose === "celebrate" ? "warning" : pose === "night" ? "accent" : "success";
|
|
167
|
+
return lines.map((line, i) => {
|
|
168
|
+
if (i === 1) return theme.fg(faceColor, line);
|
|
169
|
+
if (i === 4 && (pose === "celebrate" || pose === "tools")) return theme.fg("warning", line);
|
|
170
|
+
return theme.fg("muted", line);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Wrapped sidebar mascot — compact, report-card styling. */
|
|
175
|
+
export function renderWrappedMascot(pose: MascotPose, theme: Theme): string[] {
|
|
176
|
+
const lines = WRAPPED_POSES[pose];
|
|
177
|
+
return lines.map((line, i) => {
|
|
178
|
+
if (i === 1) return theme.fg("accent", line);
|
|
179
|
+
if (i === 3 && pose === "celebrate") return theme.fg("warning", line);
|
|
180
|
+
return theme.fg("borderMuted", line);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** One-line insight caption for the Wrapped footer. */
|
|
185
|
+
export function wrappedMascotCaption(stats: WrappedStats | null, year: number): string {
|
|
186
|
+
if (!stats) return `No sessions recorded in ${year}. Pick another year with [ ].`;
|
|
187
|
+
if (stats.longestStreak >= 14) return `${stats.longestStreak}-day streak — standout consistency.`;
|
|
188
|
+
if (stats.peakHour != null && stats.peakHour >= 22)
|
|
189
|
+
return `Peak hour ${formatHour(stats.peakHour)} — late-night sessions dominated.`;
|
|
190
|
+
if (stats.peakHour != null && stats.peakHour < 7)
|
|
191
|
+
return `Peak hour ${formatHour(stats.peakHour)} — early starts shaped the year.`;
|
|
192
|
+
if (stats.topModels.length >= 1 && stats.topModels[0].pct >= 60)
|
|
193
|
+
return `${Math.round(stats.topModels[0].pct)}% of usage on ${stats.topModels[0].name}.`;
|
|
194
|
+
return `${stats.activeDays} active days across ${stats.modelCount} models.`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Short speech line for menu hint row. */
|
|
198
|
+
export function mascotQuip(view: ViewKey, stats?: WrappedStats | null): string {
|
|
199
|
+
if (view === "wrapped") {
|
|
200
|
+
if (!stats) return "Year in review — [ ] or y to change year";
|
|
201
|
+
if (stats.longestStreak >= 14) return `${stats.longestStreak}-day streak — your highlight stat`;
|
|
202
|
+
if (stats.peakHour != null && stats.peakHour >= 22)
|
|
203
|
+
return "Night sessions defined your peak hours";
|
|
204
|
+
return `${stats.year} report ready — scroll for breakdowns`;
|
|
205
|
+
}
|
|
206
|
+
const tab = VIEW_TABS[view];
|
|
207
|
+
return tab.hint;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Glyphs for common pi tool names (Models → Tools section). */
|
|
211
|
+
const TOOL_GLYPHS: Record<string, string> = {
|
|
212
|
+
read: "↳",
|
|
213
|
+
write: "✎",
|
|
214
|
+
edit: "✎",
|
|
215
|
+
bash: "$",
|
|
216
|
+
grep: "⌕",
|
|
217
|
+
glob: "✶",
|
|
218
|
+
web_fetch: "@",
|
|
219
|
+
web_search: "◎",
|
|
220
|
+
task: "▶",
|
|
221
|
+
ask: "?",
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export function toolGlyph(name: string): string {
|
|
225
|
+
const base = name.split(":").pop()?.split("/").pop() ?? name;
|
|
226
|
+
return TOOL_GLYPHS[base] ?? TOOL_GLYPHS[base.toLowerCase()] ?? "•";
|
|
227
|
+
}
|
package/src/prices.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled default model prices ($/million tokens).
|
|
3
|
+
*
|
|
4
|
+
* pi records `cost = 0` for token-priced / proxied providers it has no pricing
|
|
5
|
+
* for (zai/GLM, 9Router `kr/…` `cx/…`, MiniMax, etc.). These defaults let the
|
|
6
|
+
* panel show an approximate cost out of the box. They are merged under the
|
|
7
|
+
* user's `~/.pi/agent/usage.json` `modelPrices`, so any user entry overrides
|
|
8
|
+
* the matching default. Set your own with `/usage-pricing`.
|
|
9
|
+
*
|
|
10
|
+
* Figures are taken from each provider's official pricing page (June 2026):
|
|
11
|
+
* - Anthropic platform.claude.com/docs/en/about-claude/pricing
|
|
12
|
+
* - OpenAI developers.openai.com/api/docs/pricing
|
|
13
|
+
* - Google ai.google.dev/gemini-api/docs/pricing
|
|
14
|
+
* - Z.ai (GLM) docs.z.ai/guides/overview/pricing
|
|
15
|
+
* - MiniMax platform.minimax.io/docs/guides/pricing-paygo
|
|
16
|
+
*
|
|
17
|
+
* Keys match a model ID exactly, or by base name (after the last `/`) so an
|
|
18
|
+
* entry like `claude-opus-4.7` also covers `kr/claude-opus-4.7`. Prices change
|
|
19
|
+
* often and subscription/proxy costs differ from list rates — treat these as
|
|
20
|
+
* estimates and override as needed.
|
|
21
|
+
*/
|
|
22
|
+
import type { ModelPrice } from "./config.ts";
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_MODEL_PRICES: Record<string, ModelPrice> = {
|
|
25
|
+
// Anthropic Claude — Opus tier ($5 / $25, cache hit $0.50, 5m write $6.25).
|
|
26
|
+
"claude-opus-4.8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
27
|
+
"claude-opus-4.7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
28
|
+
"claude-opus-4.6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
29
|
+
"claude-opus-4-8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
30
|
+
"claude-opus-4-8-high": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
31
|
+
"claude-opus-4-8-max": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
32
|
+
"claude-opus-4-8-thinking-max": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
33
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
34
|
+
"claude-opus-4-5-thinking": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
35
|
+
// Anthropic Claude — Sonnet tier ($3 / $15, cache hit $0.30, 5m write $3.75).
|
|
36
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
37
|
+
"claude-4.5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
38
|
+
// OpenAI GPT-5 family (cached input only; no cache-write charge).
|
|
39
|
+
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
40
|
+
"gpt-5.5-review": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
41
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
42
|
+
"gpt-5.3-codex": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
|
|
43
|
+
"gpt-5.1": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
|
|
44
|
+
// Google Gemini 3.
|
|
45
|
+
"gemini-3.1-pro-preview": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
|
46
|
+
"gemini-3.1-pro-high": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
|
47
|
+
"gemini-3.1-pro-low": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
|
48
|
+
"gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },
|
|
49
|
+
// Z.ai GLM (per-token API list rates; subscription cost differs).
|
|
50
|
+
"glm-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
51
|
+
"glm-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
52
|
+
"glm-5": { input: 1.0, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
53
|
+
"glm-5-turbo": { input: 1.2, output: 4.0, cacheRead: 0.24, cacheWrite: 0 },
|
|
54
|
+
"glm-5v-turbo": { input: 1.2, output: 4.0, cacheRead: 0.24, cacheWrite: 0 },
|
|
55
|
+
"glm-4.7": { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
|
56
|
+
"glm-4.6": { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
|
57
|
+
"glm-4.6v": { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
|
58
|
+
"glm-4.5v": { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
|
59
|
+
// MiniMax M-series.
|
|
60
|
+
"MiniMax-M3": { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0.375 },
|
|
61
|
+
"MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
|
|
62
|
+
"MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0.375 },
|
|
63
|
+
};
|