dsh-deepseek-peak-clock 0.0.0-stage → 1.0.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 esketids
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 CHANGED
@@ -1,3 +1,79 @@
1
- # Temporary Holding Version
1
+ # dsh-deepseek-peak-clock
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
+ A [DeepSeek Harness](https://github.com/deepseek-ai) plugin that shows whether the DeepSeek API is
4
+ in its **peak** or **off-peak** billing window — right in the composer HUD row, next to turns,
5
+ tok/s and cache hit rate.
6
+
7
+ ```
8
+ ● Off-peak · ends in 1h 22m green dot, half price
9
+ ● Peak · ends in 2h 04m amber dot, full price
10
+ ```
11
+
12
+ The countdown ticks every second. Hovering the cell shows the window end in your local time and the
13
+ current per-1M-token rates:
14
+
15
+ ```
16
+ Peak · input / cached input / output · until 12:00
17
+ Flash $0.30 / $0.006 / $1.20 · Pro $1.32 / $0.044 / $3.96 · per 1M tokens
18
+ ```
19
+
20
+ ## Schedule
21
+
22
+ Peak windows are **01:00–04:00** and **06:00–10:00 UTC**, everything else is off-peak at half price.
23
+ Since 2026-08-22 16:00 UTC the split only applies Monday–Friday Beijing time; Saturdays, Sundays and
24
+ Chinese public holidays are off-peak all day.
25
+
26
+ The rules are a port of the logic behind [deepakness.com/deepseek](https://deepakness.com/deepseek/)
27
+ and its [price clock extension](https://github.com/deepakness/deepseek-price-clock), which in turn
28
+ follow the [official DeepSeek pricing page](https://api-docs.deepseek.com/quick_start/pricing).
29
+
30
+ ## Install
31
+
32
+ This is a Harness bundle: a package whose `cordis.patch.yml` inserts one Loader row.
33
+
34
+ 1. Clone the repo somewhere stable (the profile links to the directory, so do not delete it).
35
+ 2. Install it into your profile with the plugin manager, pointing at the checkout directory:
36
+
37
+ ```
38
+ plugin_manager install_bundle /absolute/path/to/dsh-deepseek-peak-clock
39
+ ```
40
+
41
+ Or add it by hand to your profile's Cordis composition:
42
+
43
+ ```yaml
44
+ - insert:
45
+ - id: deepseek-peak-clock
46
+ name: dsh-deepseek-peak-clock
47
+ ```
48
+
49
+ 3. Refresh the Web GUI. The cell appears under the composer; it is session-scoped, so open a
50
+ session to see it.
51
+
52
+ The Host half is a no-op: the schedule is pure UTC arithmetic, so the clock renders entirely in the
53
+ browser and works offline.
54
+
55
+ ## Configuration
56
+
57
+ None. The plugin adds one locale namespace, one HUD cell and nothing else.
58
+
59
+ ## Maintenance
60
+
61
+ `CN_HOLIDAYS` in `client.js` lists Chinese public holidays that DeepSeek bills off-peak all day.
62
+ The State Council publishes the next year's arrangement around November, so add those dates when
63
+ they are announced. Everything else (windows, weekend rule) only changes when DeepSeek changes its
64
+ pricing, which this plugin does not detect automatically — compare with the pricing page
65
+ occasionally.
66
+
67
+ ## Layout
68
+
69
+ | File | Purpose |
70
+ | --- | --- |
71
+ | `index.js` | Host half (empty `apply`; no services, no network) |
72
+ | `client.js` | Schedule port + the HUD cell component |
73
+ | `cordis.patch.yml` | Loader row that activates the bundle |
74
+ | `locale/en.json`, `locale/zh.json` | Plugin title and description for the Plugins page |
75
+ | `icon.svg` | Half-green/half-red clock icon |
76
+
77
+ ## License
78
+
79
+ MIT. The schedule logic derives from DeepakNess's price clock; see the license file.
package/client.js ADDED
@@ -0,0 +1,278 @@
1
+ /**
2
+ * DeepSeek peak/off-peak clock, rendered in the HUD row under the composer.
3
+ *
4
+ * Schedule logic is a port of deepakness.com/deepseek and
5
+ * github.com/deepakness/deepseek-price-clock `price.js` — keep SWITCH_MINUTES,
6
+ * WEEKEND_RULE_START and CN_HOLIDAYS in sync with the site.
7
+ */
8
+ window.__ModuleLoader__.load({
9
+ id: 'dsh-deepseek-peak-clock',
10
+ factory(require) {
11
+ const React = require('react');
12
+ const h = React.createElement;
13
+
14
+ const NS = 'deepseek-peak-clock';
15
+ const SLOT = 'conversation.composer.dock';
16
+ const CELL_ID = 'deepseek-peak-clock';
17
+
18
+ // --- schedule ---------------------------------------------------------
19
+
20
+ // Peak window switch instants in UTC minutes-of-day: 01:00 and 06:00 open
21
+ // a peak window, 04:00 and 10:00 close one. Everything else is off-peak.
22
+ const SWITCH_MINUTES = [60, 240, 360, 600];
23
+
24
+ // Since 00:00 Beijing time on Sunday August 23, 2026 (16:00 UTC Saturday
25
+ // August 22) the split only applies Monday to Friday Beijing time.
26
+ const WEEKEND_RULE_START = Date.UTC(2026, 7, 22, 16);
27
+
28
+ // Chinese public holidays as Beijing dates; DeepSeek bills them off-peak
29
+ // all day. Add the next year's dates once the State Council announces them.
30
+ const CN_HOLIDAYS = [
31
+ ['2026-09-25', '2026-09-27'], // Mid-Autumn
32
+ ['2026-10-01', '2026-10-07'], // National Day
33
+ ];
34
+
35
+ function isBeijingWeekend(date) {
36
+ const day = new Date(date.getTime() + 8 * 3600000).getUTCDay();
37
+ return day === 0 || day === 6;
38
+ }
39
+
40
+ function beijingDateKey(date) {
41
+ return new Date(date.getTime() + 8 * 3600000).toISOString().slice(0, 10);
42
+ }
43
+
44
+ function isBeijingHoliday(date) {
45
+ const key = beijingDateKey(date);
46
+ for (const [from, to] of CN_HOLIDAYS) {
47
+ if (key >= from && key <= to) return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ // A Saturday, Sunday or public holiday under the current rules is off-peak
53
+ // all day.
54
+ function offPeakAllDay(date) {
55
+ return (
56
+ date.getTime() >= WEEKEND_RULE_START &&
57
+ (isBeijingWeekend(date) || isBeijingHoliday(date))
58
+ );
59
+ }
60
+
61
+ function kindAtInstant(date) {
62
+ if (offPeakAllDay(date)) return 'off';
63
+ const minuteOfDay = date.getUTCHours() * 60 + date.getUTCMinutes();
64
+ return (minuteOfDay >= 60 && minuteOfDay < 240) ||
65
+ (minuteOfDay >= 360 && minuteOfDay < 600)
66
+ ? 'peak'
67
+ : 'off';
68
+ }
69
+
70
+ function isSwitchInstant(date) {
71
+ const minuteOfDay = date.getUTCHours() * 60 + date.getUTCMinutes();
72
+ for (const m of SWITCH_MINUTES) {
73
+ if (minuteOfDay === m) return !offPeakAllDay(date);
74
+ }
75
+ return false;
76
+ }
77
+
78
+ function nextSwitch(now) {
79
+ const dayStart = Date.UTC(
80
+ now.getUTCFullYear(),
81
+ now.getUTCMonth(),
82
+ now.getUTCDate(),
83
+ );
84
+ let day = 0;
85
+ let index = 0;
86
+ // Bounded scan: a gated weekend/holiday stretch never exceeds a few days.
87
+ for (let guard = 0; guard < 4000; guard++) {
88
+ const instant =
89
+ dayStart + day * 86400000 + SWITCH_MINUTES[index] * 60000;
90
+ const candidate = new Date(instant);
91
+ if (instant > now.getTime() && isSwitchInstant(candidate)) {
92
+ return { date: candidate, kind: kindAtInstant(candidate) };
93
+ }
94
+ index++;
95
+ if (index >= SWITCH_MINUTES.length) {
96
+ index = 0;
97
+ day++;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ // --- formatting -------------------------------------------------------
104
+
105
+ function formatDuration(ms) {
106
+ const total = Math.max(0, Math.round(ms / 1000));
107
+ const hours = Math.floor(total / 3600);
108
+ const minutes = Math.floor((total % 3600) / 60);
109
+ const seconds = total % 60;
110
+ if (hours > 0) {
111
+ return hours + 'h ' + String(minutes).padStart(2, '0') + 'm';
112
+ }
113
+ if (minutes > 0) {
114
+ return minutes + 'm ' + String(seconds).padStart(2, '0') + 's';
115
+ }
116
+ return seconds + 's';
117
+ }
118
+
119
+ function formatLocalTime(date) {
120
+ try {
121
+ return date.toLocaleTimeString([], {
122
+ hour: '2-digit',
123
+ minute: '2-digit',
124
+ });
125
+ } catch {
126
+ return date.getUTCHours() + ':00';
127
+ }
128
+ }
129
+
130
+ // Rates per 1M tokens, from deepakness.com/deepseek (off-peak is half price).
131
+ const RATES = {
132
+ off: {
133
+ flash: ['$0.15', '$0.003', '$0.60'],
134
+ pro: ['$0.66', '$0.022', '$1.98'],
135
+ },
136
+ peak: {
137
+ flash: ['$0.30', '$0.006', '$1.20'],
138
+ pro: ['$1.32', '$0.044', '$3.96'],
139
+ },
140
+ };
141
+
142
+ function rateLine(kind, t) {
143
+ const table = RATES[kind];
144
+ return (
145
+ 'Flash ' +
146
+ table.flash.join(' / ') +
147
+ ' · Pro ' +
148
+ table.pro.join(' / ') +
149
+ ' · ' +
150
+ t('perMillion')
151
+ );
152
+ }
153
+
154
+ // --- dictionaries -----------------------------------------------------
155
+
156
+ const DICTS = {
157
+ en: {
158
+ offPeak: 'Off-peak',
159
+ peak: 'Peak',
160
+ endsIn: 'ends in',
161
+ endsAt: 'next window',
162
+ until: 'until',
163
+ prices: 'input / cached input / output',
164
+ perMillion: 'per 1M tokens',
165
+ aria: 'DeepSeek API price window',
166
+ },
167
+ zh: {
168
+ offPeak: '低谷',
169
+ peak: '高峰',
170
+ endsIn: '剩余',
171
+ endsAt: '下次切换',
172
+ until: '截至',
173
+ prices: '输入 / 缓存输入 / 输出',
174
+ perMillion: '每百万 tokens',
175
+ aria: 'DeepSeek API 峰谷计费时段',
176
+ },
177
+ };
178
+
179
+ // --- component --------------------------------------------------------
180
+
181
+ function useTicker(periodMs) {
182
+ const [now, setNow] = React.useState(() => new Date());
183
+ React.useEffect(() => {
184
+ const id = setInterval(() => setNow(new Date()), periodMs);
185
+ return () => clearInterval(id);
186
+ }, [periodMs]);
187
+ return now;
188
+ }
189
+
190
+ function Clock(props) {
191
+ const t = props.t;
192
+ const now = useTicker(1000);
193
+ const next = nextSwitch(now);
194
+ const kind = kindAtInstant(now);
195
+ const offPeak = kind === 'off';
196
+
197
+ if (!next) return null;
198
+
199
+ const label = t(offPeak ? 'offPeak' : 'peak');
200
+ const remaining = formatDuration(next.date.getTime() - now.getTime());
201
+ const flip = formatLocalTime(next.date);
202
+ const accent = offPeak
203
+ ? 'var(--dsw-alias-state-success-primary)'
204
+ : 'var(--dsw-alias-state-warn-primary)';
205
+
206
+ const title =
207
+ label +
208
+ ' · ' +
209
+ t('prices') +
210
+ ' · ' +
211
+ t('until') +
212
+ ' ' +
213
+ flip +
214
+ '\n' +
215
+ rateLine(kind, t);
216
+
217
+ return h(
218
+ 'span',
219
+ {
220
+ title,
221
+ 'aria-label': t('aria') + ': ' + label + ' ' + remaining,
222
+ style: {
223
+ display: 'inline-flex',
224
+ alignItems: 'center',
225
+ gap: '6px',
226
+ fontSize: '11px',
227
+ lineHeight: 1.4,
228
+ whiteSpace: 'nowrap',
229
+ userSelect: 'none',
230
+ color: 'var(--dsw-alias-label-secondary)',
231
+ fontVariantNumeric: 'tabular-nums',
232
+ },
233
+ },
234
+ h('span', {
235
+ 'aria-hidden': true,
236
+ style: {
237
+ width: '6px',
238
+ height: '6px',
239
+ borderRadius: '50%',
240
+ flex: '0 0 auto',
241
+ background: accent,
242
+ boxShadow: '0 0 0 3px color-mix(in srgb, ' + accent + ' 18%, transparent)',
243
+ },
244
+ }),
245
+ h(
246
+ 'span',
247
+ { style: { color: 'var(--dsw-alias-label-primary)' } },
248
+ label,
249
+ ),
250
+ h('span', { 'aria-hidden': true, opacity: 0.5 }, '·'),
251
+ h('span', null, t('endsIn') + ' ' + remaining),
252
+ );
253
+ }
254
+
255
+ return {
256
+ inject: ['slots', 'locale'],
257
+ apply(ctx) {
258
+ ctx.effect(() => {
259
+ const disposers = [];
260
+ for (const [locale, dict] of Object.entries(DICTS)) {
261
+ disposers.push(ctx.locale.register(NS, locale, dict));
262
+ }
263
+ return () => {
264
+ for (const dispose of disposers) dispose();
265
+ };
266
+ });
267
+
268
+ const t = ctx.locale.bind(NS);
269
+
270
+ ctx.slots.inject(SLOT, () =>
271
+ ctx.slots.register({ name: SLOT, id: CELL_ID, order: 2 }, () =>
272
+ h(Clock, { t }),
273
+ ),
274
+ );
275
+ },
276
+ };
277
+ },
278
+ });
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: deepseek-peak-clock
3
+ name: 'dsh-deepseek-peak-clock'
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <circle cx="32" cy="32" r="26" fill="#16a34a"/>
3
+ <path d="M32 6 A26 26 0 0 1 32 58 Z" fill="#dc2626"/>
4
+ <circle cx="32" cy="32" r="26" fill="none" stroke="#0f172a" stroke-opacity="0.22" stroke-width="2"/>
5
+ <path d="M32 17 V32 L42 39" fill="none" stroke="#ffffff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
6
+ </svg>
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Host half of the DeepSeek peak/off-peak clock bundle.
3
+ *
4
+ * The schedule is a pure function of UTC time, so nothing runs on the Host:
5
+ * the Client module renders the clock in the composer HUD on its own.
6
+ */
7
+ export function apply() {}
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek Peak Clock",
4
+ "description": "Shows whether DeepSeek API billing is in the peak or off-peak window, with a live countdown to the next price flip, in the row under the composer."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DeepSeek 峰谷电价时钟",
4
+ "description": "在输入框下方的状态行显示 DeepSeek API 当前处于高峰还是低谷计费时段,并实时倒计时到下一次价格切换。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "dsh-deepseek-peak-clock",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "DeepSeek peak/off-peak price clock for the DeepSeek Harness composer HUD.",
6
+ "license": "MIT",
7
+ "author": "esketids",
8
+ "keywords": [
9
+ "deepseek",
10
+ "deepseek-harness",
11
+ "dsh",
12
+ "dsh-plugin",
13
+ "pricing",
14
+ "hud"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/esketids/dsh-deepseek-peak-clock.git"
19
+ },
20
+ "homepage": "https://github.com/esketids/dsh-deepseek-peak-clock#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/esketids/dsh-deepseek-peak-clock/issues"
23
+ },
24
+ "exports": {
25
+ ".": "./index.js",
26
+ "./client": "./client.js",
27
+ "./locale/*.json": "./locale/*.json",
28
+ "./icon": "./icon.svg"
29
+ },
30
+ "files": [
31
+ "index.js",
32
+ "client.js",
33
+ "cordis.patch.yml",
34
+ "locale/*.json",
35
+ "icon.svg",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "dsh": {
40
+ "bundle": { "patch": "./cordis.patch.yml" },
41
+ "client": {
42
+ "platform": "web",
43
+ "immediately": true,
44
+ "inject": ["@deepseek-ai/dsh-client-ui-conversation"]
45
+ }
46
+ }
47
+ }