pi-deepseek-tariff 0.1.0 → 0.3.1
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/README.md +54 -13
- package/extensions/deepseek-peak.ts +52 -32
- package/package.json +3 -2
- package/src/config.ts +3 -0
- package/src/i18n.ts +12 -0
- package/src/view.ts +11 -7
package/README.md
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-deepseek-tariff)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://pi.dev/packages)
|
|
6
|
+
[](CHANGELOG.md)
|
|
6
7
|
|
|
7
8
|
A [pi](https://pi.dev) extension that shows the current **DeepSeek API tariff window** — peak or off-peak — with a countdown, and a panel with live prices parsed from the official DeepSeek pricing page.
|
|
8
9
|
|
|
9
10
|
DeepSeek charges double during peak hours, so knowing the current window is worth real money.
|
|
10
11
|
|
|
11
12
|
```
|
|
13
|
+
● DeepSeek Off-peak for 11h 59m ← footer line, always visible
|
|
14
|
+
|
|
12
15
|
Now OFF-PEAK
|
|
13
16
|
Computer: 14:43:52 26-10-2026(MSK) | UTC: 2026-10-26 11:43
|
|
14
17
|
Next window: 04:00 - 07:00 27-10-2026(MSK) in 13h 16m
|
|
@@ -25,18 +28,18 @@ Peak windows (MSK): 04:00-07:00 and 09:00-13:00, Mon-Fri
|
|
|
25
28
|
Off-peak = night, weekends and Chinese holidays
|
|
26
29
|
Price source: api-docs.deepseek.com, updated 11:20 26-10 (next in 5h 59m)
|
|
27
30
|
────────────────────────────────────────────────────────────
|
|
28
|
-
Commands: /
|
|
31
|
+
Commands: /dsp on|off|refresh|en|ru · /dsp footer on|off
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
## Install
|
|
32
35
|
|
|
33
36
|
```bash
|
|
34
|
-
# from
|
|
35
|
-
pi install git:github.com/valderan/pi-deepseek-tariff@v0.1.0
|
|
36
|
-
|
|
37
|
-
# from npm
|
|
37
|
+
# from npm (always the `latest` dist-tag)
|
|
38
38
|
pi install npm:pi-deepseek-tariff
|
|
39
39
|
|
|
40
|
+
# from git: tags mirror the npm versions, so pin whichever you want
|
|
41
|
+
pi install git:github.com/valderan/pi-deepseek-tariff@v0.3.0
|
|
42
|
+
|
|
40
43
|
# local checkout, for development
|
|
41
44
|
git clone git@github.com:valderan/pi-deepseek-tariff.git
|
|
42
45
|
pi install ./pi-deepseek-tariff
|
|
@@ -45,20 +48,27 @@ pi install ./pi-deepseek-tariff
|
|
|
45
48
|
pi -e ./pi-deepseek-tariff
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
Then run `/
|
|
51
|
+
Then run `/dsp`. Afterwards `pi list`, `pi update --extensions`, `pi remove …` manage it like any other package.
|
|
49
52
|
|
|
50
53
|
## Commands
|
|
51
54
|
|
|
55
|
+
`/dsp` is the short form of the command. The full name `/deepseek-peak` works too, and `/deepseek-peek` is accepted for muscle memory.
|
|
56
|
+
|
|
52
57
|
| Command | Effect |
|
|
53
58
|
|---|---|
|
|
54
|
-
| `/
|
|
55
|
-
| `/
|
|
56
|
-
| `/
|
|
57
|
-
| `/
|
|
59
|
+
| `/dsp` | toggle the panel |
|
|
60
|
+
| `/dsp on` / `off` | show or hide the panel (the footer line stays while a DeepSeek model is active) |
|
|
61
|
+
| `/dsp refresh` | re-parse prices from the docs page now |
|
|
62
|
+
| `/dsp en` / `ru` | switch the panel language (persisted) |
|
|
63
|
+
| `/dsp footer on` / `off` (or `/dsp footeron` / `footeroff`) | show or hide the footer line (persisted) |
|
|
58
64
|
|
|
59
65
|
## What it does
|
|
60
66
|
|
|
61
|
-
- **Footer line** —
|
|
67
|
+
- **Footer line** — one compact status line: `● DeepSeek Off-peak for 11h 59m`, or `● DeepSeek Peak for 1h 40m` during peak. The text is always yellow and only the dot changes colour — green for off-peak, red for peak. Shown only while a DeepSeek model is active; `/dsp footer off` hides it for good.
|
|
68
|
+
|
|
69
|
+
Russian looks like `● DeepSeek Обычный ещё 11ч 59м` / `● DeepSeek Пиковый ещё 1ч 40м`.
|
|
70
|
+
|
|
71
|
+
Pi renders every extension status on a single footer line, joined alphabetically, so the segment is kept short.
|
|
62
72
|
- **Peak rules** — DeepSeek defines peak as `01:00-04:00` and `06:00-10:00` **UTC, Monday to Friday**; weekends and Chinese public holidays are off-peak all day.
|
|
63
73
|
- **Local time everywhere** — the UTC clock comes from the machine clock, so peak windows, weekday labels and countdowns are rendered in *your* zone (`(MSK)`, `(UTC-04:00)`, …). Weekday labels shift with the zone: at UTC−4 the windows land on Sun–Thu.
|
|
64
74
|
- **Prices** — parsed from `api-docs.deepseek.com/quick_start/pricing/`: cache hit, cache miss and output per model, plus the Search & Fetch request rates. The tariff in effect is highlighted.
|
|
@@ -74,6 +84,7 @@ Then run `/deepseek-peak`. Afterwards `pi list`, `pi update --extensions`, `pi r
|
|
|
74
84
|
"intervalMs": 30000,
|
|
75
85
|
"pricingTtlHours": 6,
|
|
76
86
|
"alwaysShow": false,
|
|
87
|
+
"footer": true,
|
|
77
88
|
"models": ["deepseek"],
|
|
78
89
|
"providerPrefix": "deepseek:",
|
|
79
90
|
"holidays": ["2026-10-01", "2026-10-02"]
|
|
@@ -86,6 +97,7 @@ Then run `/deepseek-peak`. Afterwards `pi list`, `pi update --extensions`, `pi r
|
|
|
86
97
|
| `intervalMs` | `30000` | minimum refresh cadence (clamped to ≥ 5000) |
|
|
87
98
|
| `pricingTtlHours` | `6` | how long parsed prices stay valid |
|
|
88
99
|
| `alwaysShow` | `false` | show the footer line for non-DeepSeek models too |
|
|
100
|
+
| `footer` | `true` | show the footer status line at all; `/dsp footer off` writes this |
|
|
89
101
|
| `models` | `["deepseek"]` | id substrings that count as DeepSeek |
|
|
90
102
|
| `providerPrefix` | `"deepseek:"` | prefix used when printing model ids |
|
|
91
103
|
| `holidays` | `[]` | Chinese public holidays as Beijing dates (`YYYY-MM-DD`), off-peak all day |
|
|
@@ -117,12 +129,41 @@ The rules and parsing modules are pure and take injectable inputs, so tests neve
|
|
|
117
129
|
|
|
118
130
|
## Release
|
|
119
131
|
|
|
132
|
+
Git tags and npm versions are kept in lockstep: the tag `vX.Y.Z` always points at
|
|
133
|
+
the commit whose `package.json` says `X.Y.Z`, and npm publishes that same version.
|
|
134
|
+
|
|
120
135
|
```bash
|
|
121
|
-
|
|
136
|
+
# 1. bump package.json, add a CHANGELOG entry, commit
|
|
137
|
+
npm version 0.3.2 --no-git-tag-version # or edit by hand
|
|
138
|
+
git commit -am "chore: release 0.3.2"
|
|
139
|
+
|
|
140
|
+
# 2. tag the release commit and push
|
|
141
|
+
git tag -a v0.3.2 -m v0.3.2
|
|
122
142
|
git push origin main --tags
|
|
143
|
+
|
|
144
|
+
# 3. publish the tag to npm (2FA prompt, or --otp=123456)
|
|
145
|
+
npm publish --access public
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`npm publish` sets the `latest` dist-tag automatically for a plain semver version.
|
|
149
|
+
Check the result:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm view pi-deepseek-tariff dist-tags # { latest: '0.3.2' }
|
|
153
|
+
npm view pi-deepseek-tariff versions
|
|
123
154
|
```
|
|
124
155
|
|
|
125
|
-
|
|
156
|
+
`.github/workflows/ci.yml` can publish on a `v*` tag instead of step 3 when the
|
|
157
|
+
`NPM_TOKEN` secret is configured; with trusted publishing enabled on npmjs.com the
|
|
158
|
+
token is not needed at all and the build is signed with provenance.
|
|
159
|
+
|
|
160
|
+
Tags are pinnable with `pi install git:…@v0.3.2`. Published versions show up in the
|
|
161
|
+
[pi package gallery](https://pi.dev/packages) through the `pi-package` keyword.
|
|
162
|
+
|
|
163
|
+
## Changelog
|
|
164
|
+
|
|
165
|
+
See [CHANGELOG.md](CHANGELOG.md). Git tags and npm versions are kept in lockstep, so
|
|
166
|
+
`vX.Y.Z` always matches the published `X.Y.Z`.
|
|
126
167
|
|
|
127
168
|
## License
|
|
128
169
|
|
|
@@ -40,7 +40,7 @@ export default function deepseekPeak(pi: ExtensionAPI) {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
const renderFooter = (ctx: ExtensionContext | ExtensionCommandContext, current: Pricing = pricing.get()) => {
|
|
43
|
-
if (!config.alwaysShow && !isDeepSeekModel(ctx)) {
|
|
43
|
+
if (!config.footer || (!config.alwaysShow && !isDeepSeekModel(ctx))) {
|
|
44
44
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -117,41 +117,61 @@ export default function deepseekPeak(pi: ExtensionAPI) {
|
|
|
117
117
|
pi.on("agent_start", async (_event, ctx) => render(ctx));
|
|
118
118
|
pi.on("turn_start", async (_event, ctx) => render(ctx));
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
const commandHandler = async (args: string, ctx: ExtensionCommandContext) => {
|
|
121
|
+
config = loadConfig();
|
|
122
|
+
lang = config.language ?? "en";
|
|
123
|
+
pricing.setTtl(ttlMs());
|
|
124
|
+
const parts = args.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
125
|
+
const arg = parts[0] ?? "";
|
|
126
|
+
const option = parts[1] ?? "";
|
|
127
|
+
|
|
128
|
+
// Footer line: /dsp footer on|off, or the one-word /dsp footeron|footeroff.
|
|
129
|
+
if (arg === "footer" || arg === "footeron" || arg === "footeroff") {
|
|
130
|
+
const next =
|
|
131
|
+
arg === "footeron" ? true : arg === "footeroff" ? false : option === "on" ? true : option === "off" ? false : !config.footer;
|
|
132
|
+
config.footer = next;
|
|
133
|
+
saveConfig({ footer: next });
|
|
134
|
+
render(ctx);
|
|
135
|
+
ctx.ui.notify(
|
|
136
|
+
next ? "DeepSeek footer line: on · Строка футера: вкл" : "DeepSeek footer line: off · Строка футера: выкл",
|
|
137
|
+
"info",
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
if (arg === "ru" || arg === "en") {
|
|
143
|
+
lang = arg;
|
|
144
|
+
config.language = arg;
|
|
145
|
+
saveConfig({ language: arg });
|
|
146
|
+
panelVisible = true;
|
|
147
|
+
render(ctx);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
143
150
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
if (arg === "off" || (arg === "" && panelVisible)) {
|
|
152
|
+
panelVisible = false;
|
|
153
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
154
|
+
renderFooter(ctx);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
150
157
|
|
|
158
|
+
if (arg === "refresh") {
|
|
159
|
+
const value = await pricing.refresh(true);
|
|
151
160
|
panelVisible = true;
|
|
152
|
-
render(ctx);
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
render(ctx, value);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
panelVisible = true;
|
|
166
|
+
render(ctx);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const description = "DeepSeek peak/off-peak panel · Панель DeepSeek peak/off-peak";
|
|
170
|
+
// `/dsp` is the short form; `deepseek-peek` accepts the spelling people reach
|
|
171
|
+
// for when they type "peak" wrong, so the command still resolves.
|
|
172
|
+
for (const name of ["deepseek-peak", "dsp", "deepseek-peek"]) {
|
|
173
|
+
pi.registerCommand(name, { description, handler: commandHandler });
|
|
174
|
+
}
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
export { STRINGS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-deepseek-tariff",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "DeepSeek peak/off-peak tariff panel for pi: local clock, next window, live per-model prices and Search & Fetch rates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"pi": {
|
|
31
31
|
"extensions": [
|
|
32
32
|
"./extensions/deepseek-peak.ts"
|
|
33
|
-
]
|
|
33
|
+
],
|
|
34
|
+
"image": "https://raw.githubusercontent.com/valderan/pi-deepseek-tariff/main/assets/banner.png"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/config.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type Config = {
|
|
|
21
21
|
intervalMs: number;
|
|
22
22
|
pricingTtlHours: number;
|
|
23
23
|
alwaysShow: boolean;
|
|
24
|
+
footer: boolean;
|
|
24
25
|
models: string[];
|
|
25
26
|
providerPrefix: string;
|
|
26
27
|
holidays: Set<string>;
|
|
@@ -41,6 +42,7 @@ export function loadConfig(): Config {
|
|
|
41
42
|
intervalMs: 30_000,
|
|
42
43
|
pricingTtlHours: DEFAULT_TTL_HOURS,
|
|
43
44
|
alwaysShow: false,
|
|
45
|
+
footer: true,
|
|
44
46
|
models: ["deepseek"],
|
|
45
47
|
providerPrefix: "deepseek:",
|
|
46
48
|
holidays: new Set<string>(),
|
|
@@ -54,6 +56,7 @@ export function loadConfig(): Config {
|
|
|
54
56
|
config.pricingTtlHours = raw.pricingTtlHours;
|
|
55
57
|
}
|
|
56
58
|
if (typeof raw.alwaysShow === "boolean") config.alwaysShow = raw.alwaysShow;
|
|
59
|
+
if (typeof raw.footer === "boolean") config.footer = raw.footer;
|
|
57
60
|
if (typeof raw.providerPrefix === "string" && raw.providerPrefix.length > 0) {
|
|
58
61
|
config.providerPrefix = raw.providerPrefix;
|
|
59
62
|
}
|
package/src/i18n.ts
CHANGED
|
@@ -34,6 +34,10 @@ type Strings = {
|
|
|
34
34
|
sourceBuiltin: string;
|
|
35
35
|
requestPricing: string;
|
|
36
36
|
commands: string;
|
|
37
|
+
statusBrand: string;
|
|
38
|
+
statusPeak: string;
|
|
39
|
+
statusOffPeak: string;
|
|
40
|
+
statusFor: (duration: string) => string;
|
|
37
41
|
locale: string;
|
|
38
42
|
hourUnit: string;
|
|
39
43
|
minuteUnit: string;
|
|
@@ -60,6 +64,10 @@ export const STRINGS: Record<Lang, Strings> = {
|
|
|
60
64
|
sourceBuiltin: "built-in values (page unavailable)",
|
|
61
65
|
requestPricing: "Search & Fetch:",
|
|
62
66
|
commands: "Commands:",
|
|
67
|
+
statusBrand: "DeepSeek",
|
|
68
|
+
statusPeak: "Peak",
|
|
69
|
+
statusOffPeak: "Off-peak",
|
|
70
|
+
statusFor: (duration) => `for ${duration}`,
|
|
63
71
|
locale: "en-GB",
|
|
64
72
|
hourUnit: "h",
|
|
65
73
|
minuteUnit: "m",
|
|
@@ -84,6 +92,10 @@ export const STRINGS: Record<Lang, Strings> = {
|
|
|
84
92
|
sourceBuiltin: "встроенные значения (страница недоступна)",
|
|
85
93
|
requestPricing: "Search & Fetch:",
|
|
86
94
|
commands: "Команды:",
|
|
95
|
+
statusBrand: "DeepSeek",
|
|
96
|
+
statusPeak: "Пиковый",
|
|
97
|
+
statusOffPeak: "Обычный",
|
|
98
|
+
statusFor: (duration) => `ещё ${duration}`,
|
|
87
99
|
locale: "ru-RU",
|
|
88
100
|
hourUnit: "ч",
|
|
89
101
|
minuteUnit: "м",
|
package/src/view.ts
CHANGED
|
@@ -10,7 +10,6 @@ import type { Config } from "./config.ts";
|
|
|
10
10
|
import { localizeUnit, STRINGS, type Lang } from "./i18n.ts";
|
|
11
11
|
import type { Pricing, RequestPricing } from "./pricing.ts";
|
|
12
12
|
import {
|
|
13
|
-
clockTime,
|
|
14
13
|
dayRangeLabel,
|
|
15
14
|
humanize,
|
|
16
15
|
isHoliday,
|
|
@@ -54,16 +53,21 @@ export function requestPricingText(entries: RequestPricing[], lang: Lang): strin
|
|
|
54
53
|
return `${parts.join(" · ")}${sharedUnit ? ` ${localized(sharedUnit)}` : ""}`.trim();
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Compact footer line: a coloured dot plus always-yellow text, for example
|
|
58
|
+
* "● DeepSeek Off-peak for 11h 59m" or "● DeepSeek Peak for 1h 40m".
|
|
59
|
+
* Only the dot changes colour, so the line reads calmly in both states.
|
|
60
|
+
*/
|
|
57
61
|
export function statusText(theme: Theme, config: Config, lang: Lang, pricing: Pricing): string {
|
|
58
62
|
void pricing;
|
|
63
|
+
const text = STRINGS[lang];
|
|
59
64
|
const now = new Date();
|
|
60
65
|
const peak = isPeak(now, config.holidays);
|
|
61
66
|
const next = nextTransition(now, config.holidays);
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
return `${theme.fg("dim", "deepseek:")} ${color} ${theme.fg("dim", `${hint} ${when}`)}`;
|
|
67
|
+
const dot = theme.fg(peak ? "error" : "success", "●");
|
|
68
|
+
const label = peak ? text.statusPeak : text.statusOffPeak;
|
|
69
|
+
const duration = humanize(next.getTime() - now.getTime(), lang);
|
|
70
|
+
return `${dot} ${theme.fg("warning", `${text.statusBrand} ${label} ${text.statusFor(duration)}`)}`;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export function panelLines(
|
|
@@ -153,7 +157,7 @@ export function panelLines(
|
|
|
153
157
|
lines.push(SEPARATOR);
|
|
154
158
|
|
|
155
159
|
// 8. Command hints.
|
|
156
|
-
lines.push(`${theme.fg("dim", text.commands)} ${white("/
|
|
160
|
+
lines.push(`${theme.fg("dim", text.commands)} ${white("/dsp on|off|refresh|en|ru · /dsp footer on|off")}`);
|
|
157
161
|
|
|
158
162
|
return lines;
|
|
159
163
|
}
|