@javierfrancoleal/deepseek-prices 0.0.0-stage → 1.0.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 CHANGED
@@ -1,3 +1,118 @@
1
- # Temporary Holding Version
1
+ # @javierfrancoleal/deepseek-prices
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Barre de prix **en direct** des jetons DeepSeek pour la GUI Web de DeepSeek Harness.
4
+ Live DeepSeek token **price bar** for the DeepSeek Harness Web GUI.
5
+
6
+ ---
7
+
8
+ ## Ce que fait le plugin / What it does
9
+
10
+ Affiche une barre pleine largeur en bas de la fenêtre (`shell.bottom`) qui montre, en
11
+ temps réel :
12
+
13
+ - **La tarifa actual** — 🟢 VALLE (hors pointe) ou 🔴 PICO (pointe).
14
+ - **Le prix des jetons** (USD / 1M tokens) pour `deepseek-flash` et `deepseek-v4-pro`
15
+ — entrée (cache miss) → sortie. Le détail complet (cache hit + cache miss + sortie,
16
+ pointe et hors pointe) apparaît au survol.
17
+ - **L'heure d'origine (UTC)** et **l'heure locale (Québec, `America/Toronto`)**.
18
+ - **Le compte à rebours** jusqu'au prochain changement de tarif, avec l'heure locale
19
+ du basculement.
20
+ - **La date de vérification** des prix (survol de la barre).
21
+
22
+ ---
23
+
24
+ ## La grille tarifaire réelle de DeepSeek (vérifiée le 2026-11-03)
25
+
26
+ Source officielle : <https://api-docs.deepseek.com/quick_start/pricing>
27
+
28
+ Il y a **deux tarifs** : **pointe (peak)** et **hors pointe (off-peak)**. Hors pointe
29
+ = **50 %** du tarif pointe.
30
+
31
+ | Modèle | Entrée (cache hit) | Entrée (cache miss) | Sortie |
32
+ |---|---|---|---|
33
+ | `deepseek-flash` (V4.1) | pointe $0.006 · HP $0.003 | pointe $0.30 · HP $0.15 | pointe $1.20 · HP $0.60 |
34
+ | `deepseek-v4-pro` | pointe $0.044 · HP $0.022 | pointe $1.32 · HP $0.66 | pointe $3.96 · HP $1.98 |
35
+
36
+ **Heures de pointe** : `01:00–04:00` et `06:00–10:00` **UTC**, du lundi au vendredi,
37
+ **hors jours fériés chinois**. Tout le reste (nuits, weekends, jours fériés chinois)
38
+ est **hors pointe**.
39
+
40
+ > ⚠️ Deux fenêtres de pointe par jour (avec un creux hors pointe de 04:00 à 06:00 UTC).
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ Le plugin est un paquet « double-face » : un half **host** (vérification
47
+ quotidienne) et un half **browser** (la barre).
48
+
49
+ Dans le profil (`~/.dsh/profiles/desktop/`), ajoutez la dépendance et le bundle :
50
+
51
+ ```jsonc
52
+ // package.json
53
+ {
54
+ "dependencies": {
55
+ "@javierfrancoleal/deepseek-prices": "link:/…/plugins/jfranco-deepseek-prices"
56
+ },
57
+ "dsh": {
58
+ "profile": {
59
+ "bundles": [
60
+ "@deepseek-ai/dsh-base",
61
+ "@deepseek-ai/dsh-web-app",
62
+ "@javierfrancoleal/deepseek-prices"
63
+ ]
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ Puis :
70
+
71
+ ```bash
72
+ cd ~/.dsh/profiles/desktop
73
+ pnpm install
74
+ ```
75
+
76
+ Rechargez la page Web. La barre apparaît en bas de la fenêtre. (Le `cordis.patch.yml`
77
+ du plugin insère déjà sa ligne `jfranco-deepseek-prices`.)
78
+
79
+ ---
80
+
81
+ ## Vérification quotidienne
82
+
83
+ Le half host (`lib/index.js`) télécharge **une fois par jour** la page officielle
84
+ des prix, la compare à la grille embarquée, et ajoute un enregistrement JSONL à
85
+ `~/.dsh/deepseek-prices/verify.jsonl`.
86
+
87
+ - `status: "ok"` → aucune dérive.
88
+ - `status: "drift"` → les heures de pointe ou des prix clés ont changé (warning dans
89
+ le journal du host).
90
+ - `status: "error"` → la page n'a pas pu être lue (warning dans le journal).
91
+
92
+ La barre affiche la date de vérification embarquée au survol.
93
+
94
+ ---
95
+
96
+ ## Configuration / limites
97
+
98
+ - **Fuseau local** : `America/Toronto` (Québec). Modifiable dans `lib/client.js`
99
+ (`fmtClock(..., "America/Toronto")`).
100
+ - **Jours fériés chinois** : liste 2026 approximative dans `PRICING.chineseHolidays2026`
101
+ (host et client). À réviser chaque année — le vérificateur quotidien contrôle les
102
+ prix et les heures de pointe, **pas** cette liste.
103
+ - **Prix embarqués** : si DeepSeek change ses prix, le vérificateur quotidien le
104
+ signale ; mettez à jour `PRICING` dans `lib/index.js` **et** `lib/client.js`.
105
+
106
+ ---
107
+
108
+ ## Structure
109
+
110
+ | Fichier | Rôle |
111
+ |---|---|
112
+ | `lib/index.js` | Half host : vérification quotidienne contre la page officielle |
113
+ | `lib/client.js` | Half browser : la barre de prix dans le slot global `shell.bottom` |
114
+ | `cordis.patch.yml` | Insère la ligne `jfranco-deepseek-prices` |
115
+ | `package.json` | Déclare `dsh.bundle.patch` (host) + `dsh.client` (browser) |
116
+ | `locale/*.json` | Métadonnées d'affichage du gestionnaire de plugins |
117
+
118
+ Créé par **J.Franco**.
@@ -0,0 +1,5 @@
1
+ # @javierfrancoleal/deepseek-prices — live DeepSeek token price bar.
2
+ # Host-side (daily verifier) + browser-side (shell.bottom price bar) in one package.
3
+ - insert:
4
+ - id: jfranco-deepseek-prices
5
+ name: '@javierfrancoleal/deepseek-prices'
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none">
2
+ <rect width="128" height="128" rx="24" fill="#4D6BFE"/>
3
+ <path d="M30 84 L48 58 L62 70 L82 40 L98 52" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
4
+ <circle cx="98" cy="52" r="6" fill="#ffffff"/>
5
+ <path d="M30 96 H98" stroke="#ffffff" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
6
+ </svg>
package/lib/client.js ADDED
@@ -0,0 +1,217 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@javierfrancoleal/deepseek-prices",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ const React = require("react");
8
+
9
+ // ── styles ────────────────────────────────────────────────────────────────
10
+ const CSS = [
11
+ ".Jfdp_bar{box-sizing:border-box;width:100%;height:34px;display:flex;align-items:center;gap:14px;padding:0 16px;border-top:1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.2));background:var(--dsw-specific-menu, rgba(0,0,0,.04));color:var(--dsw-alias-label-secondary, #666);font-size:12px;line-height:1;font-family:var(--dsh-font-family-body, inherit);overflow:hidden;white-space:nowrap}",
12
+ ".Jfdp_bar[data-tariff='peak']{background:linear-gradient(90deg, rgba(229,72,77,.10), var(--dsw-specific-menu, rgba(0,0,0,.04))) }",
13
+ ".Jfdp_bar[data-tariff='offpeak']{background:linear-gradient(90deg, rgba(48,164,108,.10), var(--dsw-specific-menu, rgba(0,0,0,.04))) }",
14
+ ".Jfdp_dot{width:9px;height:9px;border-radius:50%;flex:none;background:#e5484d;box-shadow:0 0 0 3px rgba(229,72,77,.15)}",
15
+ ".Jfdp_bar[data-tariff='offpeak'] .Jfdp_dot{background:#30a46c;box-shadow:0 0 0 3px rgba(48,164,108,.15)}",
16
+ ".Jfdp_status{flex:none;font-weight:700;font-size:12px;letter-spacing:.06em;color:var(--dsw-alias-label-primary, #111)}",
17
+ ".Jfdp_bar[data-tariff='peak'] .Jfdp_status{color:#e5484d}",
18
+ ".Jfdp_bar[data-tariff='offpeak'] .Jfdp_status{color:#30a46c}",
19
+ ".Jfdp_model{display:inline-flex;align-items:baseline;gap:6px;flex:none}",
20
+ ".Jfdp_modelName{font-weight:600;color:var(--dsw-alias-label-primary, #111)}",
21
+ ".Jfdp_price{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary, #555)}",
22
+ ".Jfdp_arrow{opacity:.55}",
23
+ ".Jfdp_spacer{flex:1}",
24
+ ".Jfdp_clock{flex:none;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary, #888)}",
25
+ ".Jfdp_countdown{flex:none;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary, #111)}",
26
+ ".Jfdp_countdown b{color:var(--dsw-alias-label-secondary, #555);font-weight:600}",
27
+ ".Jfdp_hideSmall{flex:none}",
28
+ "@media (max-width:900px){.Jfdp_bar{gap:8px;padding:0 10px}.Jfdp_hideSmall{display:none}}"
29
+ ].join("\n");
30
+ const cssTagId = "@javierfrancoleal/deepseek-prices/PriceBar.css";
31
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(cssTagId) + "]") === null) {
32
+ const tag = document.createElement("style");
33
+ tag.dataset.plugin = "@javierfrancoleal/deepseek-prices";
34
+ tag.dataset.pluginCss = cssTagId;
35
+ tag.textContent = CSS;
36
+ document.head.appendChild(tag);
37
+ }
38
+
39
+ // ── canonical pricing snapshot (verified 2026-11-03) ─────────────────────
40
+ const PRICING = {
41
+ verifiedAt: "2026-11-03",
42
+ source: "https://api-docs.deepseek.com/quick_start/pricing",
43
+ peakWindowsUtc: [
44
+ { start: "01:00", end: "04:00" },
45
+ { start: "06:00", end: "10:00" }
46
+ ],
47
+ chineseHolidays2026: [
48
+ "2026-01-01",
49
+ "2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20", "2026-02-21", "2026-02-22",
50
+ "2026-04-04", "2026-04-05", "2026-04-06",
51
+ "2026-05-01", "2026-05-02", "2026-05-03", "2026-05-04", "2026-05-05",
52
+ "2026-06-19", "2026-06-20", "2026-06-21",
53
+ "2026-09-25", "2026-09-26", "2026-09-27",
54
+ "2026-10-01", "2026-10-02", "2026-10-03", "2026-10-04", "2026-10-05", "2026-10-06", "2026-10-07"
55
+ ],
56
+ models: {
57
+ "deepseek-flash": {
58
+ label: "Flash",
59
+ inputCacheHit: { peak: 0.006, offPeak: 0.003 },
60
+ inputCacheMiss: { peak: 0.3, offPeak: 0.15 },
61
+ output: { peak: 1.2, offPeak: 0.6 }
62
+ },
63
+ "deepseek-v4-pro": {
64
+ label: "Pro",
65
+ inputCacheHit: { peak: 0.044, offPeak: 0.022 },
66
+ inputCacheMiss: { peak: 1.32, offPeak: 0.66 },
67
+ output: { peak: 3.96, offPeak: 1.98 }
68
+ }
69
+ }
70
+ };
71
+
72
+ const WINDOWS = PRICING.peakWindowsUtc.map(function (w) {
73
+ const a = w.start.split(":").map(Number);
74
+ const b = w.end.split(":").map(Number);
75
+ return [a[0] * 60 + a[1], b[0] * 60 + b[1]];
76
+ });
77
+ const HOLIDAYS = new Set(PRICING.chineseHolidays2026 || []);
78
+
79
+ function isoDate(date) {
80
+ const y = date.getUTCFullYear();
81
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
82
+ const d = String(date.getUTCDate()).padStart(2, "0");
83
+ return y + "-" + m + "-" + d;
84
+ }
85
+ function isPeak(date) {
86
+ const day = date.getUTCDay();
87
+ if (day === 0 || day === 6) return false;
88
+ if (HOLIDAYS.has(isoDate(date))) return false;
89
+ const mins = date.getUTCHours() * 60 + date.getUTCMinutes();
90
+ for (let i = 0; i < WINDOWS.length; i++) {
91
+ if (mins >= WINDOWS[i][0] && mins < WINDOWS[i][1]) return true;
92
+ }
93
+ return false;
94
+ }
95
+ function nextTransition(date) {
96
+ const cur = isPeak(date);
97
+ let t = new Date(date.getTime());
98
+ for (let i = 0; i < 60 * 24 * 10; i++) {
99
+ t = new Date(t.getTime() + 60000);
100
+ if (isPeak(t) !== cur) return t;
101
+ }
102
+ return null;
103
+ }
104
+ function fmtClock(date, tz) {
105
+ try {
106
+ return new Intl.DateTimeFormat("en-CA", { timeZone: tz, hour: "2-digit", minute: "2-digit", hour12: false }).format(date);
107
+ } catch (e) {
108
+ return "--:--";
109
+ }
110
+ }
111
+ function fmtDuration(ms) {
112
+ let s = Math.max(0, Math.floor(ms / 1000));
113
+ const h = Math.floor(s / 3600);
114
+ const m = Math.floor((s % 3600) / 60);
115
+ const sec = s % 60;
116
+ if (h > 0) return h + "h " + m + "m " + sec + "s";
117
+ if (m > 0) return m + "m " + sec + "s";
118
+ return sec + "s";
119
+ }
120
+ function fmtPrice(n) {
121
+ if (n < 0.01) return "$" + n.toFixed(3);
122
+ return "$" + n.toFixed(2);
123
+ }
124
+
125
+ const LANGS = {
126
+ en: { peak: "PEAK", offPeak: "OFF-PEAK", to: "to", utc: "UTC", quebec: "Quebec", changes: "changes to", in: "in", next: "next" },
127
+ fr: { peak: "POINTE", offPeak: "HORS POINTE", to: "à", utc: "UTC", quebec: "Québec", changes: "passe à", in: "dans", next: "prochaine" },
128
+ es: { peak: "PICO", offPeak: "VALLE", to: "a", utc: "UTC", quebec: "Québec", changes: "cambia a", in: "en", next: "próximo" }
129
+ };
130
+ function pickLang() {
131
+ try {
132
+ const l = (navigator.language || "").toLowerCase();
133
+ if (l.indexOf("fr") === 0) return "fr";
134
+ if (l.indexOf("en") === 0) return "en";
135
+ } catch (e) { /* ignore */ }
136
+ return "es";
137
+ }
138
+
139
+ function PriceBar() {
140
+ const [now, setNow] = React.useState(function () { return new Date(); });
141
+ React.useEffect(function () {
142
+ const id = setInterval(function () { setNow(new Date()); }, 1000);
143
+ return function () { clearInterval(id); };
144
+ }, []);
145
+
146
+ const peak = isPeak(now);
147
+ const next = nextTransition(now);
148
+ const lang = pickLang();
149
+ const t = LANGS[lang] || LANGS.es;
150
+ const tariff = peak ? "peak" : "offpeak";
151
+ const statusLabel = peak ? t.peak : t.offPeak;
152
+ const nextLabel = next ? (isPeak(next) ? t.peak : t.offPeak) : "";
153
+ const remaining = next ? fmtDuration(next.getTime() - now.getTime()) : "—";
154
+
155
+ const modelRows = Object.keys(PRICING.models).map(function (key) {
156
+ const m = PRICING.models[key];
157
+ const inp = m.inputCacheMiss[peak ? "peak" : "offPeak"];
158
+ const out = m.output[peak ? "peak" : "offPeak"];
159
+ return React.createElement("span", { className: "Jfdp_model", key: key },
160
+ React.createElement("span", { className: "Jfdp_modelName" }, m.label),
161
+ React.createElement("span", { className: "Jfdp_price" },
162
+ fmtPrice(inp),
163
+ React.createElement("span", { className: "Jfdp_arrow" }, " → "),
164
+ fmtPrice(out)
165
+ )
166
+ );
167
+ });
168
+
169
+ // Full-detail tooltip (cache hit + cache miss + output, peak & off-peak).
170
+ const detail = Object.keys(PRICING.models).map(function (key) {
171
+ const m = PRICING.models[key];
172
+ return m.label + " — " + t.peak + ": in " + fmtPrice(m.inputCacheMiss.peak)
173
+ + " (hit " + fmtPrice(m.inputCacheHit.peak) + ") / out " + fmtPrice(m.output.peak)
174
+ + " · " + t.offPeak + ": in " + fmtPrice(m.inputCacheMiss.offPeak)
175
+ + " (hit " + fmtPrice(m.inputCacheHit.offPeak) + ") / out " + fmtPrice(m.output.offPeak);
176
+ }).join("\n");
177
+ const title = "DeepSeek token prices (USD / 1M)\n" + detail
178
+ + "\n\n" + t.peak + ": 01:00–04:00 & 06:00–10:00 UTC (Mon–Fri, excl. CN holidays)"
179
+ + "\n" + t.offPeak + ": all other hours (50% off)"
180
+ + "\nSource: " + PRICING.source + "\nVerified: " + PRICING.verifiedAt;
181
+
182
+ return React.createElement("div", {
183
+ className: "Jfdp_bar",
184
+ "data-tariff": tariff,
185
+ "data-deepseek-prices": "true",
186
+ title: title
187
+ },
188
+ React.createElement("span", { className: "Jfdp_dot" }),
189
+ React.createElement("span", { className: "Jfdp_status" }, statusLabel),
190
+ React.createElement("span", { className: "Jfdp_hideSmall" }, "·"),
191
+ modelRows,
192
+ React.createElement("span", { className: "Jfdp_spacer" }),
193
+ React.createElement("span", { className: "Jfdp_clock" }, t.utc + " " + fmtClock(now, "UTC")),
194
+ React.createElement("span", { className: "Jfdp_clock Jfdp_hideSmall" }, t.quebec + " " + fmtClock(now, "America/Toronto")),
195
+ React.createElement("span", { className: "Jfdp_countdown" },
196
+ next ? (t.changes + " " + nextLabel + " " + t.in + " " + remaining + " · " + fmtClock(next, "America/Toronto") + " " + t.quebec) : ""
197
+ )
198
+ );
199
+ }
200
+
201
+ const inject = ["slots"];
202
+ function apply(ctx) {
203
+ ctx.slots.inject("shell.bottom", function () {
204
+ return ctx.slots.register({
205
+ name: "shell.bottom",
206
+ id: "jfranco-deepseek-prices",
207
+ order: 0
208
+ }, PriceBar);
209
+ });
210
+ }
211
+
212
+ exports.PriceBar = PriceBar;
213
+ exports.apply = apply;
214
+ exports.inject = inject;
215
+ return module.exports;
216
+ }
217
+ });
package/lib/index.js ADDED
@@ -0,0 +1,181 @@
1
+ // @javierfrancoleal/deepseek-prices — host half.
2
+ //
3
+ // Schedules a daily self-verification against the official DeepSeek pricing
4
+ // page and appends one JSONL record per run to
5
+ // `~/.dsh/deepseek-prices/verify.jsonl`. A drift (peak-hour windows or key
6
+ // token prices differing from the canonical snapshot below) is logged as a
7
+ // warning through the host logger.
8
+ //
9
+ // The browser half (lib/client.js) renders the live price bar; it ships its
10
+ // own copy of PRICING so the two halves stay independently loadable.
11
+ import { appendFile, mkdir } from "node:fs/promises";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ const name = "jfranco-deepseek-prices";
16
+
17
+ // Canonical pricing snapshot.
18
+ // Verified 2026-11-03 against https://api-docs.deepseek.com/quick_start/pricing
19
+ // (prices are USD per 1M tokens; off-peak is half of peak).
20
+ export const PRICING = {
21
+ verifiedAt: "2026-11-03",
22
+ source: "https://api-docs.deepseek.com/quick_start/pricing",
23
+ // Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday-Friday, excluding
24
+ // Chinese public holidays. Everything else is off-peak (50% of peak).
25
+ peakWindowsUtc: [
26
+ { start: "01:00", end: "04:00" },
27
+ { start: "06:00", end: "10:00" },
28
+ ],
29
+ peakDays: "Mon-Fri",
30
+ peakNote: "excluding Chinese public holidays",
31
+ // 2026 Chinese statutory holidays (approximate — review annually; the daily
32
+ // verifier checks prices and peak windows, not this list).
33
+ chineseHolidays2026: [
34
+ "2026-01-01",
35
+ "2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20", "2026-02-21", "2026-02-22",
36
+ "2026-04-04", "2026-04-05", "2026-04-06",
37
+ "2026-05-01", "2026-05-02", "2026-05-03", "2026-05-04", "2026-05-05",
38
+ "2026-06-19", "2026-06-20", "2026-06-21",
39
+ "2026-09-25", "2026-09-26", "2026-09-27",
40
+ "2026-10-01", "2026-10-02", "2026-10-03", "2026-10-04", "2026-10-05", "2026-10-06", "2026-10-07",
41
+ ],
42
+ models: {
43
+ "deepseek-flash": {
44
+ inputCacheHit: { peak: 0.006, offPeak: 0.003 },
45
+ inputCacheMiss: { peak: 0.3, offPeak: 0.15 },
46
+ output: { peak: 1.2, offPeak: 0.6 },
47
+ },
48
+ "deepseek-v4-pro": {
49
+ inputCacheHit: { peak: 0.044, offPeak: 0.022 },
50
+ inputCacheMiss: { peak: 1.32, offPeak: 0.66 },
51
+ output: { peak: 3.96, offPeak: 1.98 },
52
+ },
53
+ },
54
+ };
55
+
56
+ const VERIFY_LOG_DIR = join(homedir(), ".dsh", "deepseek-prices");
57
+
58
+ /** Strip HTML tags so a Docusaurus page can be scanned as plain text. */
59
+ function stripHtml(html) {
60
+ return html
61
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
62
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
63
+ .replace(/<[^>]+>/g, " ")
64
+ .replace(/&nbsp;/gi, " ")
65
+ .replace(/&amp;/gi, "&")
66
+ .replace(/\s+/g, " ")
67
+ .trim();
68
+ }
69
+
70
+ /** Extract the expected peak/off-peak signals from the fetched pricing text. */
71
+ function scanPage(text) {
72
+ const findings = {};
73
+ // Peak-hours sentence, e.g. "Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, ..."
74
+ const peakMatch = /Peak hours are\s+([^.]+)/i.exec(text);
75
+ findings.peakHours = peakMatch ? peakMatch[1].trim() : null;
76
+ findings.halfPrice = /Off-peak rates are half of the peak rates/i.test(text);
77
+
78
+ // Key prices we expect to see anywhere on the page (USD per 1M tokens).
79
+ const expectedPrices = [
80
+ "0.003", "0.006", "0.15", "0.3", "0.6", "1.2",
81
+ "0.022", "0.044", "0.66", "1.32", "1.98", "3.96",
82
+ ];
83
+ findings.missingPrices = expectedPrices.filter((p) => !text.includes(p));
84
+
85
+ // Model names present.
86
+ findings.models = {
87
+ flash: text.includes("deepseek-flash"),
88
+ pro: text.includes("deepseek-v4-pro"),
89
+ };
90
+ return findings;
91
+ }
92
+
93
+ /**
94
+ * Fetch the official pricing page and compare it against PRICING.
95
+ * Returns a plain record; never throws.
96
+ */
97
+ export async function verify(_ctx) {
98
+ const result = {
99
+ checkedAt: new Date().toISOString(),
100
+ source: PRICING.source,
101
+ status: "ok",
102
+ drift: null,
103
+ findings: null,
104
+ error: null,
105
+ };
106
+ try {
107
+ const controller = new AbortController();
108
+ const timeout = setTimeout(() => controller.abort(), 20000);
109
+ let res;
110
+ try {
111
+ res = await fetch(PRICING.source, {
112
+ signal: controller.signal,
113
+ headers: { "user-agent": "deepseek-prices-verifier/1.0" },
114
+ });
115
+ } finally {
116
+ clearTimeout(timeout);
117
+ }
118
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
119
+ const text = await res.text();
120
+ const findings = scanPage(stripHtml(text));
121
+ result.findings = findings;
122
+
123
+ const drift = [];
124
+ const expectedHours = "01:00 - 04:00 and 06:00 - 10:00 UTC";
125
+ if (findings.peakHours && !findings.peakHours.toLowerCase().includes("01:00")
126
+ && !findings.peakHours.includes("06:00")) {
127
+ drift.push(`peak-hours changed: ${findings.peakHours} (expected ${expectedHours})`);
128
+ }
129
+ if (findings.missingPrices.length > 0) {
130
+ drift.push(`prices not found on page: ${findings.missingPrices.join(", ")}`);
131
+ }
132
+ if (!findings.halfPrice) {
133
+ drift.push("off-peak-is-half rule not found on page");
134
+ }
135
+ if (drift.length > 0) {
136
+ result.status = "drift";
137
+ result.drift = drift;
138
+ }
139
+ } catch (error) {
140
+ result.status = "error";
141
+ result.error = error instanceof Error ? error.message : String(error);
142
+ }
143
+
144
+ // Append one JSONL record (best effort; a full disk must not crash the host).
145
+ try {
146
+ await mkdir(VERIFY_LOG_DIR, { recursive: true });
147
+ await appendFile(join(VERIFY_LOG_DIR, "verify.jsonl"), JSON.stringify(result) + "\n", "utf8");
148
+ } catch (_error) {
149
+ /* ignore */
150
+ }
151
+
152
+ if (_ctx?.logger) {
153
+ if (result.status === "error") {
154
+ _ctx.logger.warn(`[deepseek-prices] verification failed: ${result.error}`);
155
+ } else if (result.status === "drift") {
156
+ _ctx.logger.warn(`[deepseek-prices] PRICE DRIFT: ${result.drift.join(" | ")}`);
157
+ } else {
158
+ _ctx.logger.info(`[deepseek-prices] verified against official page, no drift (${result.checkedAt})`);
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+
164
+ function apply(ctx) {
165
+ ctx.effect(() => {
166
+ let cancelled = false;
167
+ let timer = null;
168
+ const run = async () => {
169
+ await verify(ctx);
170
+ if (!cancelled) timer = setTimeout(run, 24 * 60 * 60 * 1000);
171
+ };
172
+ // First check shortly after the host settles, then once a day.
173
+ timer = setTimeout(run, 30 * 1000);
174
+ return () => {
175
+ cancelled = true;
176
+ if (timer) clearTimeout(timer);
177
+ };
178
+ }, "deepseek-prices: daily verification");
179
+ }
180
+
181
+ export { apply, name };
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek Token Prices",
4
+ "description": "Live DeepSeek API token price bar: peak/off-peak tariff, UTC + Quebec clocks, and countdown to the next tariff change."
5
+ }
6
+ }
package/locale/es.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Precios de tokens de DeepSeek",
4
+ "description": "Barra de precios en vivo de los tokens de DeepSeek: tarifa pico/valle, relojes UTC y Québec, y cuenta regresiva hasta el próximo cambio."
5
+ }
6
+ }
package/locale/fr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Prix des jetons DeepSeek",
4
+ "description": "Barre de prix en direct des jetons DeepSeek : tarif pointe/hors pointe, horloges UTC et Québec, et compte à rebours jusqu'au prochain changement."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek 令牌价格",
4
+ "description": "DeepSeek API 令牌实时价格栏:高峰/低谷费率、UTC 与魁北克时钟,以及下次费率切换的倒计时。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@javierfrancoleal/deepseek-prices",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "Live DeepSeek API token price bar for the web GUI: peak/off-peak tariff, UTC + local (Quebec) clocks, countdown to the next tariff change, and a daily self-verification against the official DeepSeek pricing page.",
5
+ "author": "J.Franco",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "lib/index.js",
9
+ "exports": {
10
+ ".": "./lib/index.js",
11
+ "./client": "./lib/client.js",
12
+ "./cordis.patch.yml": "./cordis.patch.yml",
13
+ "./package.json": "./package.json",
14
+ "./locale/*.json": "./locale/*.json"
15
+ },
16
+ "icon": "./icon.svg",
17
+ "files": [
18
+ "icon.svg",
19
+ "locale/*.json",
20
+ "lib/index.js",
21
+ "lib/client.js",
22
+ "cordis.patch.yml",
23
+ "README.md"
24
+ ],
25
+ "engines": {
26
+ "node": ">=24",
27
+ "dsh": ">=0.2.1-rc.0"
28
+ },
29
+ "keywords": [
30
+ "deepseek-harness",
31
+ "pricing",
32
+ "tokens",
33
+ "peak",
34
+ "off-peak",
35
+ "tariff"
36
+ ],
37
+ "dsh": {
38
+ "manifestVersion": 1,
39
+ "bundle": {
40
+ "patch": "./cordis.patch.yml"
41
+ },
42
+ "client": {
43
+ "platform": "web",
44
+ "inject": [
45
+ "@deepseek-ai/dsh-client-ui-layout"
46
+ ]
47
+ }
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/cordis": "~4.0.5"
54
+ }
55
+ }