oc-deepseek-pricing 0.1.0 → 0.1.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
@@ -2,13 +2,14 @@
2
2
 
3
3
  An OpenCode v2 TUI plugin that displays the current DeepSeek API pricing window in the prompt footer and session sidebar.
4
4
 
5
- The indicator is intended for the direct DeepSeek provider. DeepSeek off-peak rates are half of peak rates.
5
+ The indicator is shown when the selected model ID contains `deepseek`. DeepSeek off-peak rates are half of peak rates.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - Displays the current pricing window in the prompt footer.
10
- - Displays pricing details in the session sidebar.
11
- - Distinguishes peak and off-peak periods.
10
+ - Displays a compact pricing status in the session sidebar.
11
+ - Exposes complete pricing details through `/deepseek-pricing`.
12
+ - Distinguishes peak and off-peak periods with whale emoji indicators.
12
13
  - Shows the next pricing transition and a countdown.
13
14
  - Treats weekends as fully off-peak.
14
15
  - Supports Chinese public-holiday overrides.
@@ -19,23 +20,44 @@ The indicator is intended for the direct DeepSeek provider. DeepSeek off-peak ra
19
20
 
20
21
  Peak windows are evaluated in UTC:
21
22
 
22
- - `01:00-04:00 UTC`, Monday through Friday.
23
- - `06:00-10:00 UTC`, Monday through Friday.
23
+ - 01:00-04:00 UTC, Monday through Friday;
24
+ - 06:00-10:00 UTC, Monday through Friday.
24
25
 
25
26
  All other times are off-peak, including:
26
27
 
27
- - `04:00-06:00 UTC` on weekdays;
28
- - all hours after `10:00 UTC` and before `01:00 UTC`;
28
+ - 04:00-06:00 UTC on weekdays;
29
+ - all hours after 10:00 UTC and before 01:00 UTC;
29
30
  - Saturdays and Sundays in full;
30
31
  - Chinese public holidays in full.
31
32
 
32
- The off-peak price multiplier is `0.5`.
33
+ The off-peak price multiplier is 0.5.
34
+
35
+ ## Installation
36
+
37
+ Install the published plugin directly with the OpenCode CLI:
38
+
39
+ ```sh
40
+ opencode plugin add oc-deepseek-pricing
41
+ ```
42
+
43
+ To install this exact release:
44
+
45
+ ```sh
46
+ opencode plugin add oc-deepseek-pricing@0.1.0
47
+ ```
48
+
49
+ OpenCode registers the plugin automatically. No manual change to `cli.json`, `opencode.json`, or `tui.jsonc` is required.
50
+
51
+ Verify the installation with:
52
+
53
+ ```sh
54
+ opencode plugin list
55
+ ```
33
56
 
34
57
  ## Requirements
35
58
 
36
- - OpenCode v2 with TUI plugin support.
37
- - Bun.
38
- - A terminal supported by OpenCode.
59
+ - OpenCode v2 with TUI plugin support;
60
+ - a terminal supported by OpenCode.
39
61
 
40
62
  Development dependencies include:
41
63
 
@@ -50,6 +72,7 @@ Development dependencies include:
50
72
  ```text
51
73
  oc-deepseek-pricing/
52
74
  ├── CALENDAR_UPDATE.md
75
+ ├── LICENSE
53
76
  ├── README.md
54
77
  ├── bun.lock
55
78
  ├── package.json
@@ -58,7 +81,8 @@ oc-deepseek-pricing/
58
81
  │ ├── pricing.test.ts
59
82
  │ ├── pricing.ts
60
83
  │ └── tui.tsx
61
- └── tsconfig.json
84
+ ├── tsconfig.json
85
+ └── tui.ts
62
86
  ```
63
87
 
64
88
  ## Development setup
@@ -69,10 +93,10 @@ Install dependencies:
69
93
  bun install
70
94
  ```
71
95
 
72
- Run the TypeScript check:
96
+ Run all validation checks:
73
97
 
74
98
  ```sh
75
- bun check
99
+ bun run check
76
100
  ```
77
101
 
78
102
  Run the test suite:
@@ -81,86 +105,60 @@ Run the test suite:
81
105
  bun test
82
106
  ```
83
107
 
84
- A successful validation should complete without TypeScript errors and with all pricing tests passing.
108
+ A successful validation completes without formatting, lint, TypeScript, or test errors.
85
109
 
86
- ## OpenCode CLI configuration
110
+ ## Restarting OpenCode
87
111
 
88
- Register the local TUI package in:
112
+ After changing the plugin source or configuration, close the OpenCode TUI completely and start it again.
89
113
 
90
- ```text
91
- ~/.config/opencode/cli.json
92
- ```
114
+ Opening a new conversation is not necessarily sufficient because plugin entrypoints are loaded during TUI startup.
93
115
 
94
- Keep existing plugins and add `./plugins/oc-deepseek-pricing` to the `plugins` array.
116
+ ## Expected interface
95
117
 
96
- Example:
118
+ When a DeepSeek model is selected during an off-peak window, the prompt and home footers display:
97
119
 
98
- ```json
99
- {
100
- "diffs": {
101
- "wrap": "word",
102
- "fullscreen": true
103
- },
104
- "session": {
105
- "sidebar": "auto",
106
- "scrollbar": false,
107
- "thinking": "hide"
108
- },
109
- "animations": true,
110
- "plugins": ["./herdr-opencode", "./plugins/oc-deepseek-pricing"]
111
- }
120
+ ```text
121
+ 🐋💤 off-peak · 9h 13m left
112
122
  ```
113
123
 
114
- Relative paths are resolved from the OpenCode configuration directory.
124
+ During a peak window, they display:
115
125
 
116
- The package exposes its TUI entrypoint through `package.json`:
117
-
118
- ```json
119
- {
120
- "exports": {
121
- "./tui": "./src/tui.tsx"
122
- }
123
- }
126
+ ```text
127
+ 🐳 peak · 1h 15m left
124
128
  ```
125
129
 
126
- The legacy or integration-specific `tui.jsonc` file does not need to contain this plugin when the package is loaded through `cli.json`.
127
-
128
- ## Restarting OpenCode
129
-
130
- After changing the plugin source or configuration, close the OpenCode TUI completely and start it again.
131
-
132
- Opening a new conversation is not necessarily sufficient because plugin entrypoints are loaded during TUI startup.
133
-
134
- ## Expected interface
135
-
136
- When loaded, the prompt footer displays a compact indicator similar to:
130
+ The session sidebar displays only the current status and remaining time:
137
131
 
138
132
  ```text
139
- DeepSeek: OFF-PEAK 50% · PEAK in 11h 36m
133
+ Status 🐋💤 off-peak
134
+ 9h 13m left
140
135
  ```
141
136
 
142
- During a peak window it displays a value similar to:
137
+ During a peak window, the sidebar displays:
143
138
 
144
139
  ```text
145
- DeepSeek: PEAK · OFF-PEAK 50% in 1h 15m
140
+ Status 🐳 peak
141
+ 1h 15m left
146
142
  ```
147
143
 
148
- The session sidebar displays details similar to:
144
+ Run `/deepseek-pricing` to display the complete pricing details:
149
145
 
150
146
  ```text
151
147
  DeepSeek pricing
152
- Status OFF-PEAK 50%
148
+ Status 🐋💤 off-peak
153
149
  Reason outside peak windows
154
- Next PEAK in 11h 36m
150
+ Next peak in 9h 13m
155
151
  At 10/28/2026, 2:00:00 AM
156
152
  China 2026-10-27
157
153
  ```
158
154
 
159
- The exact local transition time depends on the operating-system timezone and daylight-saving rules. Pricing classification itself is based on UTC, while holiday lookup uses the `Asia/Shanghai` calendar date.
155
+ The `Next` line intentionally uses the state name without an emoji. If the Chinese public-holiday calendar does not cover the current year, the command also displays a warning.
156
+
157
+ The exact local transition time depends on the operating-system timezone and daylight-saving rules. Pricing classification is based on UTC, while holiday lookup uses the Asia/Shanghai calendar date.
160
158
 
161
159
  ## Chinese public holidays
162
160
 
163
- The holiday data is stored in:
161
+ Holiday data is stored in:
164
162
 
165
163
  ```text
166
164
  src/holidays.ts
@@ -168,7 +166,7 @@ src/holidays.ts
168
166
 
169
167
  Only officially verified calendar years should be added to `COVERED_HOLIDAY_YEARS`.
170
168
 
171
- For the full annual update procedure, source requirements, timezone rules, validation checklist, and required tests, see [CALENDAR_UPDATE.md](./CALENDAR_UPDATE.md).
169
+ For the complete annual update procedure, source requirements, timezone rules, validation checklist, and required tests, see [CALENDAR_UPDATE.md](./CALENDAR_UPDATE.md).
172
170
 
173
171
  If the current Chinese calendar year is not covered, the plugin continues to apply the normal UTC weekday and weekend schedule but displays a coverage warning. Do not suppress the warning by adding a year without adding and reviewing the complete official calendar.
174
172
 
@@ -200,11 +198,15 @@ bun test
200
198
  Run:
201
199
 
202
200
  ```sh
203
- bun check
201
+ bun run typecheck
204
202
  ```
205
203
 
206
- The repository uses `NodeNext` module resolution. Relative TypeScript imports must therefore use the emitted JavaScript extension, for example:
204
+ The repository uses NodeNext module resolution. Relative TypeScript imports must therefore use the emitted JavaScript extension, for example:
207
205
 
208
206
  ```ts
209
207
  import { getPricingSnapshot } from "./pricing.js"
210
208
  ```
209
+
210
+ ## License
211
+
212
+ This project is licensed under the MIT License. See [LICENSE](./LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-deepseek-pricing",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenCode TUI plugin showing DeepSeek peak and off-peak pricing windows",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,7 +12,9 @@
12
12
  "tui"
13
13
  ],
14
14
  "files": [
15
- "src",
15
+ "src/holidays.ts",
16
+ "src/pricing.ts",
17
+ "src/tui.tsx",
16
18
  "README.md",
17
19
  "LICENSE"
18
20
  ],
@@ -35,12 +37,6 @@
35
37
  "pack:check": "npm pack --dry-run",
36
38
  "prepublishOnly": "bun run check"
37
39
  },
38
- "peerDependencies": {
39
- "@opencode/plugin": ">=2.2.0 <2.3.0",
40
- "@opentui/core": "0.7.x",
41
- "@opentui/solid": "0.7.x",
42
- "solid-js": "1.9.17"
43
- },
44
40
  "devDependencies": {
45
41
  "@biomejs/biome": "2.6.3",
46
42
  "@opencode/plugin": "2.2.0",
@@ -1,121 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
-
3
- import { formatDuration, getPricingSnapshot } from "./pricing.js"
4
-
5
- const COVERED_YEARS = new Set([2026])
6
-
7
- function snapshotAt(iso: string, holidays: ReadonlySet<string> = new Set()) {
8
- return getPricingSnapshot(new Date(iso), {
9
- chinesePublicHolidays: holidays,
10
- coveredHolidayYears: COVERED_YEARS,
11
- })
12
- }
13
-
14
- function stateAt(iso: string) {
15
- return snapshotAt(iso).state
16
- }
17
-
18
- describe("pricing windows", () => {
19
- test("handles both weekday peak windows", () => {
20
- expect(stateAt("2026-10-26T00:59:59Z")).toBe("off-peak")
21
- expect(stateAt("2026-10-26T01:00:00Z")).toBe("peak")
22
- expect(stateAt("2026-10-26T04:00:00Z")).toBe("off-peak")
23
- expect(stateAt("2026-10-26T06:00:00Z")).toBe("peak")
24
- expect(stateAt("2026-10-26T10:00:00Z")).toBe("off-peak")
25
- })
26
-
27
- test("keeps weekends off-peak", () => {
28
- const saturday = snapshotAt("2026-10-24T02:00:00Z")
29
- const sunday = snapshotAt("2026-10-25T07:00:00Z")
30
-
31
- expect(saturday.state).toBe("off-peak")
32
- expect(saturday.reason).toBe("weekend")
33
-
34
- expect(sunday.state).toBe("off-peak")
35
- expect(sunday.reason).toBe("weekend")
36
- })
37
-
38
- test("marks ordinary weekday hours outside the windows as off-peak", () => {
39
- const snapshot = snapshotAt("2026-10-26T05:00:00Z")
40
-
41
- expect(snapshot.state).toBe("off-peak")
42
- expect(snapshot.reason).toBe("outside-peak-window")
43
- expect(snapshot.rateMultiplier).toBe(0.5)
44
- })
45
-
46
- test("uses the full multiplier during peak hours", () => {
47
- const snapshot = snapshotAt("2026-10-26T08:00:00Z")
48
-
49
- expect(snapshot.state).toBe("peak")
50
- expect(snapshot.reason).toBe("peak-window")
51
- expect(snapshot.rateMultiplier).toBe(1)
52
- })
53
- })
54
-
55
- describe("Chinese public holidays", () => {
56
- test("overrides a weekday peak window", () => {
57
- const snapshot = snapshotAt("2026-10-01T02:00:00Z", new Set(["2026-10-01"]))
58
-
59
- expect(snapshot.state).toBe("off-peak")
60
- expect(snapshot.reason).toBe("chinese-public-holiday")
61
- expect(snapshot.rateMultiplier).toBe(0.5)
62
- })
63
-
64
- test("reports whether the Chinese calendar year is covered", () => {
65
- expect(snapshotAt("2026-10-27T12:00:00Z").holidayCalendarCovered).toBe(true)
66
-
67
- const uncovered = getPricingSnapshot(new Date("2027-01-05T12:00:00Z"), {
68
- chinesePublicHolidays: new Set(),
69
- coveredHolidayYears: COVERED_YEARS,
70
- })
71
-
72
- expect(uncovered.holidayCalendarCovered).toBe(false)
73
- })
74
- })
75
-
76
- describe("next transition", () => {
77
- test("finds the end of the first peak window", () => {
78
- const snapshot = snapshotAt("2026-10-27T02:15:00Z")
79
-
80
- expect(snapshot.nextState).toBe("off-peak")
81
- expect(snapshot.nextTransition).toBe("2026-10-27T04:00:00.000Z")
82
- expect(snapshot.secondsUntilTransition).toBe(6_300)
83
- })
84
-
85
- test("finds the beginning of the second peak window", () => {
86
- const snapshot = snapshotAt("2026-10-27T05:30:00Z")
87
-
88
- expect(snapshot.nextState).toBe("peak")
89
- expect(snapshot.nextTransition).toBe("2026-10-27T06:00:00.000Z")
90
- expect(snapshot.secondsUntilTransition).toBe(1_800)
91
- })
92
-
93
- test("skips the weekend when finding the next peak window", () => {
94
- const snapshot = snapshotAt("2026-10-30T10:00:00Z")
95
-
96
- expect(snapshot.state).toBe("off-peak")
97
- expect(snapshot.nextState).toBe("peak")
98
- expect(snapshot.nextTransition).toBe("2026-11-02T01:00:00.000Z")
99
- })
100
- })
101
-
102
- describe("duration formatting", () => {
103
- test("formats minutes and hours", () => {
104
- expect(formatDuration(60)).toBe("1m")
105
- expect(formatDuration(3_600)).toBe("1h")
106
- expect(formatDuration(5_400)).toBe("1h 30m")
107
- })
108
-
109
- test("rounds partial minutes up", () => {
110
- expect(formatDuration(1)).toBe("1m")
111
- expect(formatDuration(61)).toBe("2m")
112
- })
113
- })
114
-
115
- describe("input validation", () => {
116
- test("rejects invalid dates", () => {
117
- expect(() => {
118
- getPricingSnapshot(new Date("invalid"))
119
- }).toThrow("Invalid date")
120
- })
121
- })