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 +21 -0
- package/README.md +101 -0
- package/assets/.gitkeep +0 -0
- package/assets/preview.png +0 -0
- package/extensions/toast-meter/README.md +24 -0
- package/extensions/toast-meter/config.default.json +12 -0
- package/extensions/toast-meter/index.ts +126 -0
- package/package.json +30 -0
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.
|
package/assets/.gitkeep
ADDED
|
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,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
|
+
}
|