@icaruk/zai-peak-hours 0.0.9 → 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 +27 -12
- package/dist/command-handled.d.ts +2 -0
- package/dist/command-handled.js +4 -0
- package/dist/config.d.ts +0 -2
- package/dist/config.js +1 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -113
- package/dist/peak-hours.js +16 -15
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +90 -0
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# @icaruk/zai-peak-hours
|
|
2
2
|
|
|
3
|
-
OpenCode plugin that displays z.ai peak hours information with automatic timezone detection
|
|
3
|
+
OpenCode plugin that displays z.ai peak hours information with automatic timezone detection (UTC+8 / Asia/Shanghai).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🌍 Automatic timezone detection (UTC+8 / Asia/Shanghai)
|
|
8
|
+
- ⏰ Real-time peak hours status (14:00-18:00 UTC+8)
|
|
9
|
+
- 📊 Time remaining until next peak/off-peak transition
|
|
10
|
+
- 🔔 Toast notification on session start
|
|
11
|
+
- 💬 Zero-token commands (`/peak_hours`, `/peak_hours_status`) - no LLM invocation
|
|
4
12
|
|
|
5
13
|
## Installation
|
|
6
14
|
|
|
@@ -12,23 +20,20 @@ Add to your `~/.config/opencode/opencode.json`:
|
|
|
12
20
|
}
|
|
13
21
|
```
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- 🌍 Automatic timezone detection (UTC+8 / Asia/Shanghai)
|
|
18
|
-
- ⏰ Real-time peak hours status (14:00-18:00 UTC+8)
|
|
19
|
-
- 📊 Time remaining until next peak/off-peak transition
|
|
20
|
-
- 🔔 Toast notifications on session start and periodic updates
|
|
21
|
-
- ⚙️ Configurable update interval
|
|
22
|
-
- 🛠️ Manual commands for on-demand status checks
|
|
23
|
+
Restart OpenCode to load the plugin.
|
|
23
24
|
|
|
24
25
|
## Commands
|
|
25
26
|
|
|
27
|
+
Commands are automatically available after plugin installation:
|
|
28
|
+
|
|
26
29
|
- `/peak_hours` - Display current peak hours status
|
|
27
|
-
- `/peak_hours_status` - Display plugin diagnostics and configuration
|
|
30
|
+
- `/peak_hours_status` - Display plugin diagnostics and configuration status
|
|
31
|
+
|
|
32
|
+
These commands execute locally without invoking LLM (zero token usage) via `command.execute.before` hook.
|
|
28
33
|
|
|
29
34
|
## Messages
|
|
30
35
|
|
|
31
|
-
The plugin displays toast
|
|
36
|
+
The plugin displays a toast notification when a new session is created:
|
|
32
37
|
|
|
33
38
|
**Peak Hours:**
|
|
34
39
|
```
|
|
@@ -40,6 +45,16 @@ Currently in peak hours. X hours Y minutes remaining
|
|
|
40
45
|
Currently in off-peak hours. X hours Y minutes remaining
|
|
41
46
|
```
|
|
42
47
|
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
Optional configuration file at `~/.config/opencode/peak-hours.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"enabled": true
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
43
58
|
## Development
|
|
44
59
|
|
|
45
60
|
```bash
|
|
@@ -57,7 +72,7 @@ npm run build
|
|
|
57
72
|
npm run dev
|
|
58
73
|
```
|
|
59
74
|
|
|
60
|
-
Add local config in `opencode.json
|
|
75
|
+
Add local config in `opencode.json`:
|
|
61
76
|
|
|
62
77
|
```json
|
|
63
78
|
{
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
1
|
export const DEFAULT_CONFIG = {
|
|
2
|
-
enabled: true
|
|
3
|
-
updateIntervalMinutes: 15 // 15 minutes
|
|
2
|
+
enabled: true
|
|
4
3
|
};
|
|
5
|
-
export function getPluginConfig(config) {
|
|
6
|
-
const userConfig = config?.peakHours || {};
|
|
7
|
-
return {
|
|
8
|
-
enabled: userConfig.enabled !== undefined ? userConfig.enabled : DEFAULT_CONFIG.enabled,
|
|
9
|
-
updateIntervalMinutes: userConfig.updateIntervalMinutes !== undefined ? userConfig.updateIntervalMinutes : DEFAULT_CONFIG.updateIntervalMinutes
|
|
10
|
-
};
|
|
11
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export { PeakHoursPlugin } from "./plugin.js";
|
|
2
|
+
export type { PeakHoursStatus, } from "./peak-hours.js";
|
package/dist/index.js
CHANGED
|
@@ -1,113 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { getPeakHoursStatus, formatPeakHoursMessage } from './peak-hours';
|
|
3
|
-
import { DEFAULT_CONFIG } from './config';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
let timerId = null;
|
|
7
|
-
let configCache = null;
|
|
8
|
-
function getConfigPath() {
|
|
9
|
-
const configDir = process.env.XDG_CONFIG_HOME
|
|
10
|
-
? path.join(process.env.XDG_CONFIG_HOME, 'opencode')
|
|
11
|
-
: path.join(process.env.HOME || process.env.USERPROFILE || '', '.config', 'opencode');
|
|
12
|
-
return path.join(configDir, 'peak-hours.json');
|
|
13
|
-
}
|
|
14
|
-
function loadConfig() {
|
|
15
|
-
if (configCache) {
|
|
16
|
-
return configCache;
|
|
17
|
-
}
|
|
18
|
-
const configPath = getConfigPath();
|
|
19
|
-
if (fs.existsSync(configPath)) {
|
|
20
|
-
try {
|
|
21
|
-
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
22
|
-
const userConfig = JSON.parse(configContent);
|
|
23
|
-
configCache = {
|
|
24
|
-
enabled: userConfig.enabled !== undefined ? userConfig.enabled : DEFAULT_CONFIG.enabled,
|
|
25
|
-
updateIntervalMinutes: userConfig.updateIntervalMinutes !== undefined ? userConfig.updateIntervalMinutes : DEFAULT_CONFIG.updateIntervalMinutes
|
|
26
|
-
};
|
|
27
|
-
return configCache;
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
console.error('Error loading peak-hours config:', error);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
configCache = DEFAULT_CONFIG;
|
|
34
|
-
return configCache;
|
|
35
|
-
}
|
|
36
|
-
export const PeakHours = async ({ client }) => {
|
|
37
|
-
const config = loadConfig();
|
|
38
|
-
return {
|
|
39
|
-
'tool.execute.after': async (input) => {
|
|
40
|
-
if (!config.enabled) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (input.tool === 'peak_hours') {
|
|
44
|
-
const status = getPeakHoursStatus();
|
|
45
|
-
const message = formatPeakHoursMessage(status);
|
|
46
|
-
await client.tui.showToast({
|
|
47
|
-
body: {
|
|
48
|
-
message,
|
|
49
|
-
variant: status.inPeakHours ? 'warning' : 'info'
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
else if (input.tool === 'peak_hours_status') {
|
|
54
|
-
const config = loadConfig();
|
|
55
|
-
const status = getPeakHoursStatus();
|
|
56
|
-
const currentTime = new Date().toISOString();
|
|
57
|
-
const diagnostics = `=== Peak Hours Plugin Diagnostics ===
|
|
58
|
-
Plugin enabled: ${config.enabled}
|
|
59
|
-
Update interval: ${config.updateIntervalMinutes} minutes
|
|
60
|
-
Current time: ${currentTime}
|
|
61
|
-
In peak hours: ${status.inPeakHours}
|
|
62
|
-
Time until ${status.transitionType}: ${status.timeUntilTransition}
|
|
63
|
-
===================================`;
|
|
64
|
-
await client.tui.showToast({
|
|
65
|
-
body: {
|
|
66
|
-
message: diagnostics,
|
|
67
|
-
variant: 'info'
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
'session.created': async () => {
|
|
73
|
-
if (!config.enabled) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const status = getPeakHoursStatus();
|
|
77
|
-
const message = formatPeakHoursMessage(status);
|
|
78
|
-
await client.tui.showToast({
|
|
79
|
-
body: {
|
|
80
|
-
message,
|
|
81
|
-
variant: status.inPeakHours ? 'warning' : 'info'
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
},
|
|
85
|
-
tool: {
|
|
86
|
-
'peak_hours': tool({
|
|
87
|
-
description: 'Display current z.ai peak hours status and time until next transition',
|
|
88
|
-
args: {},
|
|
89
|
-
async execute(_args, context) {
|
|
90
|
-
const status = getPeakHoursStatus();
|
|
91
|
-
const message = formatPeakHoursMessage(status);
|
|
92
|
-
return `Peak Hours Status:\n${message}\n\nIn peak hours: ${status.inPeakHours}\nTime until ${status.transitionType}: ${status.timeUntilTransition}`;
|
|
93
|
-
}
|
|
94
|
-
}),
|
|
95
|
-
'peak_hours_status': tool({
|
|
96
|
-
description: 'Display peak hours plugin diagnostics and configuration status',
|
|
97
|
-
args: {},
|
|
98
|
-
async execute(_args, context) {
|
|
99
|
-
const config = loadConfig();
|
|
100
|
-
const status = getPeakHoursStatus();
|
|
101
|
-
const currentTime = new Date().toISOString();
|
|
102
|
-
return `=== Peak Hours Plugin Diagnostics ===
|
|
103
|
-
Plugin enabled: ${config.enabled}
|
|
104
|
-
Update interval: ${config.updateIntervalMinutes} minutes
|
|
105
|
-
Current time: ${currentTime}
|
|
106
|
-
In peak hours: ${status.inPeakHours}
|
|
107
|
-
Time until ${status.transitionType}: ${status.timeUntilTransition}
|
|
108
|
-
====================================`;
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
};
|
|
1
|
+
export { PeakHoursPlugin } from "./plugin.js";
|
package/dist/peak-hours.js
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
import dayjs from 'dayjs';
|
|
2
|
-
import utc from 'dayjs/plugin/utc.js';
|
|
3
|
-
import timezone from 'dayjs/plugin/timezone.js';
|
|
4
|
-
dayjs.extend(utc);
|
|
5
|
-
dayjs.extend(timezone);
|
|
6
1
|
export function getPeakHoursStatus() {
|
|
7
2
|
const peakHoursStart = 14;
|
|
8
3
|
const peakHoursEnd = 18;
|
|
9
|
-
|
|
10
|
-
const
|
|
4
|
+
// Calcular hora actual en UTC+8 (China timezone)
|
|
5
|
+
const now = new Date();
|
|
6
|
+
const utcMs = now.getTime() + now.getTimezoneOffset() * 60000;
|
|
7
|
+
const chinaTime = new Date(utcMs + 8 * 3600000);
|
|
8
|
+
const currentHour = chinaTime.getHours();
|
|
11
9
|
let inPeakHours;
|
|
12
10
|
let transitionTime;
|
|
13
11
|
let transitionType;
|
|
14
12
|
if (currentHour >= peakHoursStart && currentHour < peakHoursEnd) {
|
|
15
13
|
inPeakHours = true;
|
|
16
14
|
transitionType = 'end';
|
|
17
|
-
transitionTime =
|
|
15
|
+
transitionTime = new Date(chinaTime);
|
|
16
|
+
transitionTime.setHours(peakHoursEnd, 0, 0, 0);
|
|
18
17
|
}
|
|
19
18
|
else {
|
|
20
19
|
inPeakHours = false;
|
|
21
20
|
transitionType = 'start';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
let transitionDay = new Date(chinaTime);
|
|
22
|
+
if (currentHour >= peakHoursEnd) {
|
|
23
|
+
transitionDay.setDate(transitionDay.getDate() + 1);
|
|
24
|
+
}
|
|
25
|
+
transitionDay.setHours(peakHoursStart, 0, 0, 0);
|
|
26
|
+
transitionTime = transitionDay;
|
|
26
27
|
}
|
|
27
|
-
const
|
|
28
|
-
const hours = Math.floor(
|
|
29
|
-
const minutes = Math.floor((
|
|
28
|
+
const diffMs = transitionTime.getTime() - chinaTime.getTime();
|
|
29
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
30
|
+
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
30
31
|
let timeUntilTransition;
|
|
31
32
|
if (hours > 0 && minutes > 0) {
|
|
32
33
|
timeUntilTransition = `${hours} hours ${minutes} minutes`;
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getPeakHoursStatus, formatPeakHoursMessage } from './peak-hours';
|
|
2
|
+
import { DEFAULT_CONFIG } from './config';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
function getConfigPath() {
|
|
6
|
+
const configDir = process.env.XDG_CONFIG_HOME
|
|
7
|
+
? path.join(process.env.XDG_CONFIG_HOME, 'opencode')
|
|
8
|
+
: path.join(process.env.HOME || process.env.USERPROFILE || '', '.config', 'opencode');
|
|
9
|
+
return path.join(configDir, 'peak-hours.json');
|
|
10
|
+
}
|
|
11
|
+
function loadConfig() {
|
|
12
|
+
const configPath = getConfigPath();
|
|
13
|
+
if (fs.existsSync(configPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
16
|
+
const userConfig = JSON.parse(configContent);
|
|
17
|
+
return {
|
|
18
|
+
enabled: userConfig.enabled !== undefined ? userConfig.enabled : DEFAULT_CONFIG.enabled
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error('Error loading peak-hours config:', error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return DEFAULT_CONFIG;
|
|
26
|
+
}
|
|
27
|
+
function renderCommandHeading(title) {
|
|
28
|
+
return `${title}\n${'='.repeat(title.length)}`;
|
|
29
|
+
}
|
|
30
|
+
function renderStatusReport(status, config) {
|
|
31
|
+
const currentTime = new Date().toISOString();
|
|
32
|
+
const lines = [
|
|
33
|
+
renderCommandHeading('Peak Hours Status'),
|
|
34
|
+
'',
|
|
35
|
+
'Configuration:',
|
|
36
|
+
`- enabled: ${config.enabled}`,
|
|
37
|
+
'',
|
|
38
|
+
'Current Status:',
|
|
39
|
+
`- current_time: ${currentTime}`,
|
|
40
|
+
`- in_peak_hours: ${status.inPeakHours}`,
|
|
41
|
+
`- transition: ${status.transitionType}`,
|
|
42
|
+
`- time_until_${status.transitionType}: ${status.timeUntilTransition}`,
|
|
43
|
+
'',
|
|
44
|
+
'Peak Hours (UTC+8):',
|
|
45
|
+
`- start: 14:00`,
|
|
46
|
+
`- end: 18:00`
|
|
47
|
+
];
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
}
|
|
50
|
+
export const PeakHoursPlugin = async ({ client }) => {
|
|
51
|
+
const config = loadConfig();
|
|
52
|
+
const typedClient = client;
|
|
53
|
+
return {
|
|
54
|
+
'command.execute.before': async (input, output) => {
|
|
55
|
+
const command = input.command;
|
|
56
|
+
if (!config.enabled) {
|
|
57
|
+
output.parts = [{ type: 'text', text: 'Peak Hours plugin is disabled', ignored: true }];
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (command === 'peak_hours') {
|
|
61
|
+
const status = getPeakHoursStatus();
|
|
62
|
+
const message = formatPeakHoursMessage(status);
|
|
63
|
+
output.parts = [{ type: 'text', text: message, ignored: true }];
|
|
64
|
+
}
|
|
65
|
+
else if (command === 'peak_hours_status') {
|
|
66
|
+
const status = getPeakHoursStatus();
|
|
67
|
+
const report = renderStatusReport(status, config);
|
|
68
|
+
output.parts = [{ type: 'text', text: report, ignored: true }];
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
'session.created': async (_input) => {
|
|
72
|
+
if (!config.enabled) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const status = getPeakHoursStatus();
|
|
76
|
+
const message = formatPeakHoursMessage(status);
|
|
77
|
+
try {
|
|
78
|
+
await typedClient.tui.showToast({
|
|
79
|
+
body: {
|
|
80
|
+
message,
|
|
81
|
+
variant: status.inPeakHours ? 'warning' : 'info'
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error('Failed to show toast:', err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icaruk/zai-peak-hours",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin to display z.ai peak hours information with automatic timezone detection",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
"author": "Icaruk",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@opencode-ai/plugin": "^1.4.
|
|
30
|
-
"@opencode-ai/sdk": "^1.4.3",
|
|
31
|
-
"dayjs": "^1.11.10"
|
|
29
|
+
"@opencode-ai/plugin": "^1.4.3"
|
|
32
30
|
},
|
|
33
31
|
"devDependencies": {
|
|
34
32
|
"@types/node": "^20.0.0",
|