@icaruk/zai-peak-hours 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 CHANGED
@@ -1,6 +1,13 @@
1
1
  # @icaruk/zai-peak-hours
2
2
 
3
- OpenCode plugin that displays z.ai peak hours information with automatic timezone detection via toast notifications.
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
+ - 💬 Zero-token slash commands (`/peak_hours`, `/peak_hours_status`) - no LLM invocation
4
11
 
5
12
  ## Installation
6
13
 
@@ -12,32 +19,25 @@ Add to your `~/.config/opencode/opencode.json`:
12
19
  }
13
20
  ```
14
21
 
15
- ## Features
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
22
+ Restart OpenCode to load the plugin.
23
23
 
24
24
  ## Commands
25
25
 
26
+ Commands are automatically available after plugin installation:
27
+
26
28
  - `/peak_hours` - Display current peak hours status
27
- - `/peak_hours_status` - Display plugin diagnostics and configuration
29
+ - `/peak_hours_status` - Display plugin diagnostics and configuration status
28
30
 
29
- ## Messages
31
+ These commands execute locally without invoking LLM (zero token usage) via `command.execute.before` hook.
30
32
 
31
- The plugin displays toast notifications:
33
+ ## Configuration
32
34
 
33
- **Peak Hours:**
34
- ```
35
- Currently in peak hours. X hours Y minutes remaining
36
- ```
35
+ Optional configuration file at `~/.config/opencode/peak-hours.json`:
37
36
 
38
- **Off-Peak Hours:**
39
- ```
40
- Currently in off-peak hours. X hours Y minutes remaining
37
+ ```json
38
+ {
39
+ "enabled": true
40
+ }
41
41
  ```
42
42
 
43
43
  ## Development
@@ -57,7 +57,7 @@ npm run build
57
57
  npm run dev
58
58
  ```
59
59
 
60
- Add local config in `opencode.json`
60
+ Add local config in `opencode.json`:
61
61
 
62
62
  ```json
63
63
  {
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
- import { getPeakHoursStatus, formatPeakHoursMessage } from './peak-hours';
2
- import { DEFAULT_CONFIG } from './config';
1
+ import { getPeakHoursStatus, formatPeakHoursMessage } from './utils.js';
2
+ import { DEFAULT_CONFIG } from './config.js';
3
3
  import * as fs from 'node:fs';
4
4
  import * as path from 'node:path';
5
+ // Command-handled sentinel
6
+ const COMMAND_HANDLED_SENTINEL = '__PEAK_HOURS_COMMAND_HANDLED__';
7
+ function handled() {
8
+ throw new Error(COMMAND_HANDLED_SENTINEL);
9
+ }
5
10
  function getConfigPath() {
6
11
  const configDir = process.env.XDG_CONFIG_HOME
7
12
  ? path.join(process.env.XDG_CONFIG_HOME, 'opencode')
@@ -15,7 +20,7 @@ function loadConfig() {
15
20
  const configContent = fs.readFileSync(configPath, 'utf-8');
16
21
  const userConfig = JSON.parse(configContent);
17
22
  return {
18
- enabled: userConfig.enabled !== undefined ? userConfig.enabled : DEFAULT_CONFIG.enabled
23
+ enabled: userConfig.enabled !== undefined ? userConfig.enabled : DEFAULT_CONFIG.enabled,
19
24
  };
20
25
  }
21
26
  catch (error) {
@@ -43,7 +48,7 @@ function renderStatusReport(status, config) {
43
48
  '',
44
49
  'Peak Hours (UTC+8):',
45
50
  `- start: 14:00`,
46
- `- end: 18:00`
51
+ `- end: 18:00`,
47
52
  ];
48
53
  return lines.join('\n');
49
54
  }
@@ -56,70 +61,66 @@ export const PeakHours = async ({ client }) => {
56
61
  path: { id: sessionID },
57
62
  body: {
58
63
  noReply: true,
59
- parts: [{ type: 'text', text: output, ignored: true }]
60
- }
64
+ parts: [{ type: 'text', text: output, ignored: true }],
65
+ },
61
66
  });
62
67
  }
63
68
  catch (err) {
64
69
  console.error('Failed to inject output:', err);
65
70
  }
66
71
  }
67
- async function handlePeakHoursCommand(sessionID) {
72
+ async function injectCommandOutputAndHandle(sessionID, output) {
73
+ if (output !== undefined && output !== null) {
74
+ await injectRawOutput(sessionID, output);
75
+ }
76
+ handled();
77
+ }
78
+ async function handlePeakHoursSlashCommand(input) {
79
+ if (!config.enabled) {
80
+ return await injectCommandOutputAndHandle(input.sessionID, 'Peak Hours plugin is disabled');
81
+ }
68
82
  const status = getPeakHoursStatus();
69
83
  const output = formatPeakHoursMessage(status);
70
- await injectRawOutput(sessionID, output);
71
- handled();
84
+ return await injectCommandOutputAndHandle(input.sessionID, output);
72
85
  }
73
- async function handlePeakHoursStatusCommand(sessionID) {
86
+ async function handlePeakHoursStatusSlashCommand(input) {
87
+ if (!config.enabled) {
88
+ return await injectCommandOutputAndHandle(input.sessionID, 'Peak Hours plugin is disabled');
89
+ }
74
90
  const status = getPeakHoursStatus();
75
91
  const output = renderStatusReport(status, config);
76
- await injectRawOutput(sessionID, output);
77
- handled();
92
+ return await injectCommandOutputAndHandle(input.sessionID, output);
78
93
  }
79
94
  return {
95
+ // Register built-in slash commands
80
96
  config: async (input) => {
81
- input.output.command = {
82
- peak_hours: {
83
- template: '',
84
- description: 'Display current z.ai peak hours status and time until next transition'
85
- },
86
- peak_hours_status: {
87
- template: '',
88
- description: 'Display peak hours plugin diagnostics and configuration status'
89
- }
97
+ const cfg = input;
98
+ cfg.command ??= {};
99
+ cfg.command['peak_hours'] = {
100
+ template: '/peak_hours',
101
+ description: 'Display current z.ai peak hours status and time until next transition',
102
+ };
103
+ cfg.command['peak_hours_status'] = {
104
+ template: '/peak_hours_status',
105
+ description: 'Display peak hours plugin diagnostics and configuration status',
90
106
  };
91
107
  },
92
- 'tui.command.execute': async (input) => {
93
- const command = input.command;
94
- const sessionID = input.sessionID;
95
- if (!config.enabled) {
96
- await injectRawOutput(sessionID, 'Peak Hours plugin is disabled');
97
- return;
98
- }
99
- if (command === 'peak_hours') {
100
- await handlePeakHoursCommand(sessionID);
101
- }
102
- else if (command === 'peak_hours_status') {
103
- await handlePeakHoursStatusCommand(sessionID);
104
- }
105
- },
106
- 'session.created': async (input) => {
107
- if (!config.enabled) {
108
- return;
109
- }
110
- const status = getPeakHoursStatus();
111
- const message = formatPeakHoursMessage(status);
108
+ // Intercept slash commands and handle them without LLM invocation
109
+ 'command.execute.before': async (input) => {
112
110
  try {
113
- await typedClient.tui.showToast({
114
- body: {
115
- message,
116
- variant: status.inPeakHours ? 'warning' : 'info'
117
- }
118
- });
111
+ const cmd = input.command;
112
+ if (cmd === 'peak_hours') {
113
+ return await handlePeakHoursSlashCommand(input);
114
+ }
115
+ if (cmd === 'peak_hours_status') {
116
+ return await handlePeakHoursStatusSlashCommand(input);
117
+ }
119
118
  }
120
119
  catch (err) {
121
- console.error('Failed to show toast:', err);
120
+ // IMPORTANT: do not swallow command-handled sentinel errors.
121
+ // If this hook resolves, the command proceeds to the LLM.
122
+ throw err;
122
123
  }
123
- }
124
+ },
124
125
  };
125
126
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icaruk/zai-peak-hours",
3
- "version": "0.1.0",
3
+ "version": "0.3.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",
File without changes
File without changes