toast-meter 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sylvia Schmitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # toast-meter
2
+
3
+ Playful Pi package for a footer context-usage indicator.
4
+
5
+ License: MIT
6
+ Author: Sylvia Schmitt
7
+
8
+ ## What it does
9
+
10
+ - Shows current context usage in Pi's footer
11
+ - Uses brains → bricks / bread as the context fills up
12
+ - Switches the final stage label to `House is on fire`
13
+ - Supports English and German presets
14
+ - Lets projects override thresholds only
15
+
16
+ ## Install with Pi
17
+
18
+ Local path:
19
+
20
+ ```json
21
+ {
22
+ "packages": ["./path/to/toast-meter"]
23
+ }
24
+ ```
25
+
26
+ Git:
27
+
28
+ ```json
29
+ {
30
+ "packages": ["git:github.com/ai2ys/toast-meter@v0.1.0"]
31
+ }
32
+ ```
33
+
34
+ npm:
35
+
36
+ ```json
37
+ {
38
+ "packages": ["npm:toast-meter@0.1.0"]
39
+ }
40
+ ```
41
+
42
+ Or via commands:
43
+
44
+ ```bash
45
+ pi install /absolute/path/to/toast-meter
46
+ pi install git:github.com/ai2ys/toast-meter@v0.1.0
47
+ pi install npm:toast-meter@0.1.0
48
+ ```
49
+
50
+ ## Example output
51
+
52
+ ### English
53
+
54
+ ```text
55
+ 0k 🧠🧠🧠🧠 • Dumb as a Brick
56
+ 120k 🧠🧱🧱🧱 • Dumb as a Brick
57
+ 151k 🏠🔥 • House is on fire
58
+ ```
59
+
60
+ ### Deutsch
61
+
62
+ ```text
63
+ 0k 🧠🧠🧠🧠 • Dumm wie Brot
64
+ 120k 🧠🍞🍞🍞 • Dumm wie Brot
65
+ 151k 🏠🔥 • House is on fire
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ Package defaults live here:
71
+
72
+ - `extensions/toast-meter/config.default.json`
73
+
74
+ Project override (optional):
75
+
76
+ - `.pi/toast-meter.json`
77
+
78
+ Example override:
79
+
80
+ ```json
81
+ {
82
+ "mode": "en",
83
+ "showText": true,
84
+ "refreshIntervalMs": 2000,
85
+ "levels": {
86
+ "1": 80000,
87
+ "2": 100000,
88
+ "3": 120000,
89
+ "4": 140000,
90
+ "5": 150000
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Notes
96
+
97
+ - Level 0 is fixed in code.
98
+ - Icons are fixed in code.
99
+ - Thresholds 1–5 are configurable.
100
+ - Default mode is `en`.
101
+ - The package is discoverable on Pi package listings via the `pi-package` keyword.
File without changes
Binary file
@@ -0,0 +1,24 @@
1
+ # toast-meter extension
2
+
3
+ This extension is part of the `toast-meter` Pi package.
4
+
5
+ ## Configuration
6
+
7
+ Project overrides go in:
8
+
9
+ - `.pi/toast-meter.json`
10
+
11
+ Package defaults live in:
12
+
13
+ - `config.default.json`
14
+
15
+ Supported options include:
16
+
17
+ - `mode`: `"en"` or `"de"`
18
+ - `showText`: `true` or `false`
19
+ - `refreshIntervalMs`: number
20
+ - `levels`: thresholds for levels `1` to `5`
21
+
22
+ See the main package README for installation, examples, and full configuration details:
23
+
24
+ - [`../../README.md`](../../README.md)
@@ -0,0 +1,12 @@
1
+ {
2
+ "mode": "en",
3
+ "showText": true,
4
+ "refreshIntervalMs": 2000,
5
+ "levels": {
6
+ "1": 80000,
7
+ "2": 100000,
8
+ "3": 120000,
9
+ "4": 140000,
10
+ "5": 150000
11
+ }
12
+ }
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+
5
+ const WIDGET_KEY = "smart-dumb-zone-indicator";
6
+ const DEFAULT_CONFIG_PATH = path.join(__dirname, "config.default.json");
7
+ const PROJECT_CONFIG_PATH = path.join(process.cwd(), ".pi", "toast-meter.json");
8
+ const DEFAULT_REFRESH_MS = 2000;
9
+
10
+ type IndicatorConfig = {
11
+ mode?: "de" | "en";
12
+ refreshIntervalMs?: number;
13
+ showText?: boolean;
14
+ label?: string;
15
+ levels?: Partial<Record<1 | 2 | 3 | 4 | 5, number>>;
16
+ };
17
+
18
+ type Preset = {
19
+ label: string;
20
+ icons: [string, string, string, string, string, string];
21
+ thresholds: [number, number, number, number, number];
22
+ };
23
+
24
+ const PRESETS: Record<NonNullable<IndicatorConfig["mode"]>, Preset> = {
25
+ de: {
26
+ label: "Dumm wie Brot",
27
+ icons: ["🧠🧠🧠🧠", "🧠🧠🧠🍞", "🧠🧠🍞🍞", "🧠🍞🍞🍞", "🍞🍞🍞🍞", "🏠🔥"],
28
+ thresholds: [80e3, 100e3, 120e3, 140e3, 150e3],
29
+ },
30
+ en: {
31
+ label: "Dumb as a Brick",
32
+ icons: ["🧠🧠🧠🧠", "🧠🧠🧠🧱", "🧠🧠🧱🧱", "🧠🧱🧱🧱", "🧱🧱🧱🧱", "🏠🔥"],
33
+ thresholds: [80e3, 100e3, 120e3, 140e3, 150e3],
34
+ },
35
+ };
36
+
37
+ const DEFAULT_MODE: NonNullable<IndicatorConfig["mode"]> = "en";
38
+ const FINAL_STAGE_LABEL = "House is on fire";
39
+
40
+ function readJsonConfig(configPath: string): IndicatorConfig {
41
+ try {
42
+ const raw = fs.readFileSync(configPath, "utf8");
43
+ return JSON.parse(raw) as IndicatorConfig;
44
+ } catch {
45
+ return {};
46
+ }
47
+ }
48
+
49
+ function readConfig(): IndicatorConfig {
50
+ const defaults = readJsonConfig(DEFAULT_CONFIG_PATH);
51
+ const project = readJsonConfig(PROJECT_CONFIG_PATH);
52
+ return {
53
+ ...defaults,
54
+ ...project,
55
+ levels: {
56
+ ...(defaults.levels ?? {}),
57
+ ...(project.levels ?? {}),
58
+ },
59
+ };
60
+ }
61
+
62
+ function buildLevels(config: IndicatorConfig): { at: number; icon: string }[] {
63
+ const mode = config.mode ?? DEFAULT_MODE;
64
+ const preset = PRESETS[mode];
65
+ const thresholds = preset.thresholds.map((fallback, index) => config.levels?.[(index + 1) as 1 | 2 | 3 | 4 | 5] ?? fallback);
66
+ return [
67
+ { at: 0, icon: preset.icons[0] },
68
+ ...thresholds.map((at, index) => ({ at, icon: preset.icons[index + 1] })),
69
+ ];
70
+ }
71
+
72
+ function formatStatus(ctx: ExtensionContext, config: IndicatorConfig): string {
73
+ const usage = ctx.getContextUsage();
74
+ const tokens = usage?.tokens ?? null;
75
+ const theme = ctx.ui.theme;
76
+ const mode = config.mode ?? DEFAULT_MODE;
77
+ const preset = PRESETS[mode];
78
+ const label = config.label ?? preset.label;
79
+ const levels = buildLevels(config);
80
+
81
+ if (tokens === null) {
82
+ return theme.fg("muted", `${label}: ?`);
83
+ }
84
+
85
+ const k = Math.round(tokens / 1000);
86
+ const showText = config.showText ?? false;
87
+ const current = levels.reduce((acc, level) => (tokens >= level.at ? level.icon : acc), levels[0]?.icon ?? "🍞");
88
+ const isFinalStage = tokens >= (levels[levels.length - 1]?.at ?? Number.MAX_SAFE_INTEGER);
89
+ const value = theme.fg("muted", `${k}k`);
90
+ const iconPart = theme.fg(isFinalStage ? "error" : tokens >= 100e3 ? "warning" : "success", current);
91
+ if (!showText) return `${value} ${iconPart}`;
92
+ const textLabel = isFinalStage ? FINAL_STAGE_LABEL : label;
93
+ return `${value} ${iconPart} ${theme.fg("dim", "•")} ${theme.fg("muted", textLabel)}`;
94
+ }
95
+
96
+ export default function (pi: ExtensionAPI) {
97
+ let timer: ReturnType<typeof setInterval> | null = null;
98
+
99
+ const stopTimer = () => {
100
+ if (timer) {
101
+ clearInterval(timer);
102
+ timer = null;
103
+ }
104
+ };
105
+
106
+ const refresh = (ctx: ExtensionContext) => {
107
+ if (!ctx.hasUI) return;
108
+ const config = readConfig();
109
+ ctx.ui.setStatus(WIDGET_KEY, formatStatus(ctx, config));
110
+ };
111
+
112
+ pi.on("session_start", (_event, ctx) => {
113
+ refresh(ctx);
114
+ stopTimer();
115
+ const config = readConfig();
116
+ const refreshMs = config.refreshIntervalMs ?? DEFAULT_REFRESH_MS;
117
+ timer = setInterval(() => refresh(ctx), refreshMs);
118
+ });
119
+
120
+ pi.on("turn_end", (_event, ctx) => refresh(ctx));
121
+ pi.on("model_select", (_event, ctx) => refresh(ctx));
122
+ pi.on("session_shutdown", (_event, ctx) => {
123
+ stopTimer();
124
+ if (ctx.hasUI) ctx.ui.setStatus(WIDGET_KEY, undefined);
125
+ });
126
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "toast-meter",
3
+ "version": "0.1.0",
4
+ "description": "Playful Pi footer indicator for context usage",
5
+ "license": "MIT",
6
+ "author": "Sylvia Schmitt",
7
+ "keywords": ["pi-package"],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ai2ys/toast-meter.git"
11
+ },
12
+ "homepage": "https://github.com/ai2ys/toast-meter#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ai2ys/toast-meter/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "files": ["extensions", "assets", "README.md", "package.json"],
23
+ "pi": {
24
+ "extensions": ["./extensions"],
25
+ "image": "https://raw.githubusercontent.com/ai2ys/toast-meter/main/assets/preview.png"
26
+ },
27
+ "peerDependencies": {
28
+ "@earendil-works/pi-coding-agent": "*"
29
+ }
30
+ }