opencode-session-renamer 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 Joseph Han
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 ADDED
@@ -0,0 +1,171 @@
1
+ # opencode-session-renamer
2
+
3
+ Chinese version: [简体中文](#简体中文)
4
+
5
+ Automatically generates a session title from the user's first message and renames the OpenCode session (with a timestamp suffix).
6
+
7
+ ## Install
8
+
9
+ Add the plugin to your `~/.config/opencode/opencode.jsonc`:
10
+
11
+ ```jsonc
12
+ {
13
+ "plugin": ["opencode-session-renamer"]
14
+ }
15
+ ```
16
+
17
+ Restart OpenCode after installation.
18
+
19
+ ## Development Install
20
+
21
+ For development or contributing, you can install from source:
22
+
23
+ ### Option 1: Symlink to Plugin Directory
24
+
25
+ ```bash
26
+ # Clone the repo
27
+ git clone https://github.com/joseph-bing-han/opencode-session-renamer.git
28
+ cd opencode-session-renamer
29
+
30
+ # Install dependencies and build
31
+ bun install
32
+ bun run build
33
+
34
+ # Create symlink
35
+ ln -sf $(pwd)/src/index.ts ~/.config/opencode/plugin/session-renamer.ts
36
+ ```
37
+
38
+ ### Option 2: Configure Local Path
39
+
40
+ Add the local path to `~/.config/opencode/opencode.jsonc`:
41
+
42
+ ```jsonc
43
+ {
44
+ "plugin": [
45
+ "/path/to/opencode-session-renamer/dist/index.js"
46
+ ]
47
+ }
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ The plugin loads config files in this order (first match wins):
53
+
54
+ 1. `<project>/.opencode/session-renamer.jsonc`
55
+ 2. `<project>/.opencode/session-renamer.json`
56
+ 3. `~/.config/opencode/session-renamer.jsonc`
57
+ 4. `~/.config/opencode/session-renamer.json`
58
+
59
+ Example:
60
+
61
+ ```jsonc
62
+ {
63
+ "model": "opencode/grok-code",
64
+ "titleMaxLength": 20,
65
+ "dateFormat": "YY-MM-DD HH:mm",
66
+ "minMessageLength": 5,
67
+ "debug": false
68
+ }
69
+ ```
70
+
71
+ Fields:
72
+
73
+ - `model`: Model used for title generation in `providerID/modelID` format. Default: `opencode/grok-code`.
74
+ - `titleMaxLength`: Maximum title length (excluding the timestamp suffix).
75
+ - `dateFormat`: Date format (currently supports: `YYYY` / `YY` / `MM` / `DD` / `HH` / `mm`).
76
+ - `minMessageLength`: Minimum message length to trigger rename (to avoid renaming on very short messages).
77
+ - `debug`: Enable debug logs (prefixed with `[session-renamer]`).
78
+
79
+ ## FAQ
80
+
81
+ ### 1) Why do I get `ProviderModelNotFoundError`?
82
+
83
+ This usually means `model` is set to a model ID that doesn't exist in your current OpenCode provider list. Change it to a valid model (e.g. `opencode/grok-code` or `opencode/glm-4.7-free`).
84
+
85
+ The plugin also tries to fetch available models from OpenCode `/config/providers` and fall back automatically, but the most reliable approach is still to set `model` to an ID that is available in your environment.
86
+
87
+ ---
88
+
89
+ ## 简体中文
90
+
91
+ 自动根据用户第一条消息内容,为 OpenCode 的会话生成标题并重命名(附带时间后缀)。
92
+
93
+ ### 安装
94
+
95
+ 在 `~/.config/opencode/opencode.jsonc` 中添加插件:
96
+
97
+ ```jsonc
98
+ {
99
+ "plugin": ["opencode-session-renamer"]
100
+ }
101
+ ```
102
+
103
+ 安装后需要重启 OpenCode。
104
+
105
+ ### 开发安装
106
+
107
+ 如需开发或贡献代码,可以从源码安装:
108
+
109
+ #### 方式一:符号链接到插件目录
110
+
111
+ ```bash
112
+ # 克隆仓库
113
+ git clone https://github.com/joseph-bing-han/opencode-session-renamer.git
114
+ cd opencode-session-renamer
115
+
116
+ # 安装依赖并构建
117
+ bun install
118
+ bun run build
119
+
120
+ # 创建符号链接
121
+ ln -sf $(pwd)/src/index.ts ~/.config/opencode/plugin/session-renamer.ts
122
+ ```
123
+
124
+ #### 方式二:配置本地路径
125
+
126
+ 在 `~/.config/opencode/opencode.jsonc` 中添加本地路径:
127
+
128
+ ```jsonc
129
+ {
130
+ "plugin": [
131
+ "/path/to/opencode-session-renamer/dist/index.js"
132
+ ]
133
+ }
134
+ ```
135
+
136
+ ### 配置
137
+
138
+ 插件会按以下顺序加载配置文件(先找到的优先):
139
+
140
+ 1. `<project>/.opencode/session-renamer.jsonc`
141
+ 2. `<project>/.opencode/session-renamer.json`
142
+ 3. `~/.config/opencode/session-renamer.jsonc`
143
+ 4. `~/.config/opencode/session-renamer.json`
144
+
145
+ 示例:
146
+
147
+ ```jsonc
148
+ {
149
+ "model": "opencode/grok-code",
150
+ "titleMaxLength": 20,
151
+ "dateFormat": "YY-MM-DD HH:mm",
152
+ "minMessageLength": 5,
153
+ "debug": false
154
+ }
155
+ ```
156
+
157
+ 字段说明:
158
+
159
+ - `model`: 生成标题使用的模型,格式为 `providerID/modelID`。默认 `opencode/grok-code`。
160
+ - `titleMaxLength`: 标题最大长度(不含日期后缀)。
161
+ - `dateFormat`: 日期格式(当前实现支持:`YYYY` / `YY` / `MM` / `DD` / `HH` / `mm`)。
162
+ - `minMessageLength`: 触发重命名的最小消息长度(避免过短消息触发)。
163
+ - `debug`: 开启后输出调试日志(前缀为 `[session-renamer]`)。
164
+
165
+ ### 常见问题
166
+
167
+ #### 1) 为什么报 `ProviderModelNotFoundError`?
168
+
169
+ 通常是 `model` 配置了不存在的模型 ID。请改成真实存在的模型(例如 `opencode/grok-code` 或 `opencode/glm-4.7-free`)。
170
+
171
+ 插件内部也会尝试从 OpenCode 的 `/config/providers` 获取可用模型并自动回退,但最稳妥的方式仍是把 `model` 配置成你当前环境确实可用的 ID。
@@ -0,0 +1,10 @@
1
+ export interface SessionRenamerConfig {
2
+ model: string;
3
+ titleMaxLength: number;
4
+ dateFormat: string;
5
+ minMessageLength: number;
6
+ debug: boolean;
7
+ }
8
+ export declare const DEFAULT_CONFIG: SessionRenamerConfig;
9
+ export declare function loadConfig(directory: string): SessionRenamerConfig;
10
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IAGnC,KAAK,EAAE,MAAM,CAAC;IAGd,cAAc,EAAE,MAAM,CAAC;IAIvB,UAAU,EAAE,MAAM,CAAC;IAGnB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,cAAc,EAAE,oBAM5B,CAAC;AAUF,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAqBlE"}
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const plugin: Plugin;
3
+ export declare const sessionRenamer: Plugin;
4
+ export default plugin;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAkRlD,QAAA,MAAM,MAAM,EAAE,MAyEb,CAAC;AAGF,eAAO,MAAM,cAAc,QAAS,CAAC;AAGrC,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,307 @@
1
+ // @bun
2
+ // src/config.ts
3
+ import { readFileSync, existsSync } from "fs";
4
+ import { join } from "path";
5
+ var DEFAULT_CONFIG = {
6
+ model: "opencode/grok-code",
7
+ titleMaxLength: 20,
8
+ dateFormat: "YY-MM-DD HH:mm",
9
+ minMessageLength: 5,
10
+ debug: false
11
+ };
12
+ function parseJsonc(content) {
13
+ const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,(\s*[}\]])/g, "$1");
14
+ return JSON.parse(stripped);
15
+ }
16
+ function loadConfig(directory) {
17
+ const configPaths = [
18
+ join(directory, ".opencode", "session-renamer.jsonc"),
19
+ join(directory, ".opencode", "session-renamer.json"),
20
+ join(process.env.HOME || "~", ".config", "opencode", "session-renamer.jsonc"),
21
+ join(process.env.HOME || "~", ".config", "opencode", "session-renamer.json")
22
+ ];
23
+ for (const configPath of configPaths) {
24
+ if (existsSync(configPath)) {
25
+ try {
26
+ const content = readFileSync(configPath, "utf-8");
27
+ const userConfig = parseJsonc(content);
28
+ return { ...DEFAULT_CONFIG, ...userConfig };
29
+ } catch (error) {
30
+ console.error(`[session-renamer] Failed to parse config at ${configPath}:`, error);
31
+ }
32
+ }
33
+ }
34
+ return DEFAULT_CONFIG;
35
+ }
36
+
37
+ // src/index.ts
38
+ var SYSTEM_PROMPT = `You are a session title generator. Generate a concise, descriptive title for a coding session based on the user's first message.
39
+
40
+ Rules:
41
+ - Title must be in the same language as the user's message
42
+ - Title should capture the main task/topic
43
+ - Keep it short and descriptive (max {maxLength} characters)
44
+ - No quotes, no punctuation at the end
45
+ - Just output the title, nothing else
46
+
47
+ Examples:
48
+ - User: "Help me fix the login bug in auth.ts" -> "Fix login bug"
49
+ - User: "I want to refactor the database module" -> "Refactor database module"
50
+ - User: "Please optimize this React component's performance" -> "Optimize React component performance"`;
51
+ var renamedSessions = new Set;
52
+ var tempSessions = new Set;
53
+ var providersConfigPromise = null;
54
+ function formatDate(format) {
55
+ const now = new Date;
56
+ const pad = (n) => n.toString().padStart(2, "0");
57
+ const year = now.getFullYear();
58
+ const month = pad(now.getMonth() + 1);
59
+ const day = pad(now.getDate());
60
+ const hours = pad(now.getHours());
61
+ const minutes = pad(now.getMinutes());
62
+ return format.replace("YYYY", year.toString()).replace("YY", year.toString().slice(-2)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes);
63
+ }
64
+ function parseModelString(modelStr) {
65
+ const parts = modelStr.split("/");
66
+ if (parts.length >= 2) {
67
+ return { providerID: parts[0], modelID: parts.slice(1).join("/") };
68
+ }
69
+ return { providerID: "opencode", modelID: modelStr };
70
+ }
71
+ function normalizeConfiguredModel(modelStr) {
72
+ const trimmed = modelStr.trim();
73
+ if (!trimmed) {
74
+ return null;
75
+ }
76
+ const parts = trimmed.split("/");
77
+ if (parts.length >= 2) {
78
+ const providerID = parts[0]?.trim();
79
+ const modelID = parts.slice(1).join("/").trim();
80
+ if (!providerID || !modelID) {
81
+ return null;
82
+ }
83
+ return { providerID, modelID };
84
+ }
85
+ return parseModelString(trimmed);
86
+ }
87
+ function isProviderModelNotFoundError(error) {
88
+ if (!error || typeof error !== "object") {
89
+ return false;
90
+ }
91
+ const err = error;
92
+ const name = err.name;
93
+ if (typeof name === "string" && name.toLowerCase().includes("modelnotfound")) {
94
+ return true;
95
+ }
96
+ const message = err.message;
97
+ if (typeof message === "string" && message.toLowerCase().includes("modelnotfound")) {
98
+ return true;
99
+ }
100
+ const data = err.data;
101
+ if (data && typeof data === "object") {
102
+ const d = data;
103
+ return typeof d.providerID === "string" && typeof d.modelID === "string";
104
+ }
105
+ return false;
106
+ }
107
+ async function getProvidersConfig(client, directory) {
108
+ if (!providersConfigPromise) {
109
+ providersConfigPromise = client.config.providers({ query: { directory } }).then((res) => res.data ?? null).catch(() => null);
110
+ }
111
+ return providersConfigPromise;
112
+ }
113
+ async function resolveModelForPrompt(client, directory, configModel) {
114
+ const requested = normalizeConfiguredModel(configModel);
115
+ const providersConfig = await getProvidersConfig(client, directory);
116
+ if (!providersConfig) {
117
+ return requested ?? undefined;
118
+ }
119
+ if (!requested) {
120
+ const opencodeProvider2 = providersConfig.providers.find((p) => p.id === "opencode");
121
+ if (opencodeProvider2) {
122
+ const defaultModelID = providersConfig.default?.[opencodeProvider2.id];
123
+ if (defaultModelID && opencodeProvider2.models?.[defaultModelID]) {
124
+ return { providerID: opencodeProvider2.id, modelID: defaultModelID };
125
+ }
126
+ const firstModelID = Object.keys(opencodeProvider2.models ?? {})[0];
127
+ if (firstModelID) {
128
+ return { providerID: opencodeProvider2.id, modelID: firstModelID };
129
+ }
130
+ }
131
+ for (const provider2 of providersConfig.providers) {
132
+ const defaultModelID = providersConfig.default?.[provider2.id];
133
+ if (defaultModelID && provider2.models?.[defaultModelID]) {
134
+ return { providerID: provider2.id, modelID: defaultModelID };
135
+ }
136
+ }
137
+ for (const provider2 of providersConfig.providers) {
138
+ const firstModelID = Object.keys(provider2.models ?? {})[0];
139
+ if (firstModelID) {
140
+ return { providerID: provider2.id, modelID: firstModelID };
141
+ }
142
+ }
143
+ return;
144
+ }
145
+ const provider = providersConfig.providers.find((p) => p.id === requested.providerID);
146
+ if (provider?.models?.[requested.modelID]) {
147
+ return requested;
148
+ }
149
+ if (provider) {
150
+ const defaultModelID = providersConfig.default?.[provider.id];
151
+ if (defaultModelID && provider.models?.[defaultModelID]) {
152
+ return { providerID: provider.id, modelID: defaultModelID };
153
+ }
154
+ const firstModelID = Object.keys(provider.models ?? {})[0];
155
+ if (firstModelID) {
156
+ return { providerID: provider.id, modelID: firstModelID };
157
+ }
158
+ }
159
+ const opencodeProvider = providersConfig.providers.find((p) => p.id === "opencode");
160
+ if (opencodeProvider) {
161
+ const defaultModelID = providersConfig.default?.[opencodeProvider.id];
162
+ if (defaultModelID && opencodeProvider.models?.[defaultModelID]) {
163
+ return { providerID: opencodeProvider.id, modelID: defaultModelID };
164
+ }
165
+ const firstModelID = Object.keys(opencodeProvider.models ?? {})[0];
166
+ if (firstModelID) {
167
+ return { providerID: opencodeProvider.id, modelID: firstModelID };
168
+ }
169
+ }
170
+ return;
171
+ }
172
+ function log(config, ...args) {
173
+ if (config.debug) {
174
+ console.log("[session-renamer]", ...args);
175
+ }
176
+ }
177
+ async function generateTitle(client, config, userMessage, directory) {
178
+ try {
179
+ const resolvedModel = await resolveModelForPrompt(client, directory, config.model);
180
+ if (resolvedModel) {
181
+ log(config, "Using model:", `${resolvedModel.providerID}/${resolvedModel.modelID}`);
182
+ } else {
183
+ log(config, "Using server default model (no explicit model override)");
184
+ }
185
+ const tempSession = await client.session.create({
186
+ body: {}
187
+ });
188
+ if (!tempSession.data?.id) {
189
+ console.error("[session-renamer] Failed to create temp session");
190
+ return null;
191
+ }
192
+ const tempSessionId = tempSession.data.id;
193
+ tempSessions.add(tempSessionId);
194
+ try {
195
+ const promptBodyBase = {
196
+ system: SYSTEM_PROMPT.replace("{maxLength}", config.titleMaxLength.toString()),
197
+ parts: [
198
+ {
199
+ type: "text",
200
+ text: `Generate a title for this message:
201
+
202
+ ${userMessage}`
203
+ }
204
+ ]
205
+ };
206
+ const doPrompt = async (modelOverride) => client.session.prompt({
207
+ path: { id: tempSessionId },
208
+ body: modelOverride ? { ...promptBodyBase, model: modelOverride } : promptBodyBase
209
+ });
210
+ let response;
211
+ try {
212
+ response = await doPrompt(resolvedModel);
213
+ } catch (error) {
214
+ if (isProviderModelNotFoundError(error)) {
215
+ log(config, "Configured model unavailable, retrying with server default model");
216
+ response = await doPrompt(undefined);
217
+ } else {
218
+ throw error;
219
+ }
220
+ }
221
+ if (!response.data) {
222
+ return null;
223
+ }
224
+ const textPart = response.data.parts?.find((p) => p.type === "text");
225
+ if (!textPart || textPart.type !== "text") {
226
+ return null;
227
+ }
228
+ let title = textPart.text.trim();
229
+ if (title.length > config.titleMaxLength) {
230
+ title = title.slice(0, config.titleMaxLength);
231
+ }
232
+ return title;
233
+ } finally {
234
+ await client.session.delete({ path: { id: tempSessionId } }).catch(() => {});
235
+ }
236
+ } catch (error) {
237
+ console.error("[session-renamer] Failed to generate title:", error);
238
+ return null;
239
+ }
240
+ }
241
+ var plugin = async (ctx) => {
242
+ const config = loadConfig(ctx.directory);
243
+ log(config, "Plugin loaded with config:", config);
244
+ return {
245
+ "chat.message": async (input, output) => {
246
+ const { sessionID } = input;
247
+ const { message, parts } = output;
248
+ log(config, "chat.message hook triggered for session:", sessionID);
249
+ if (tempSessions.has(sessionID)) {
250
+ log(config, "Temp session, skipping:", sessionID);
251
+ return;
252
+ }
253
+ if (renamedSessions.has(sessionID)) {
254
+ log(config, "Session already renamed, skipping:", sessionID);
255
+ return;
256
+ }
257
+ let userMessage = null;
258
+ if (message.summary?.title) {
259
+ userMessage = message.summary.title;
260
+ log(config, "Using message summary title:", userMessage.slice(0, 50) + "...");
261
+ } else if (message.summary?.body) {
262
+ userMessage = message.summary.body;
263
+ log(config, "Using message summary body:", userMessage.slice(0, 50) + "...");
264
+ } else {
265
+ const textPart = parts.find((p) => p.type === "text");
266
+ if (textPart && textPart.type === "text" && "text" in textPart) {
267
+ userMessage = textPart.text;
268
+ log(config, "Using assistant response:", userMessage.slice(0, 50) + "...");
269
+ }
270
+ }
271
+ if (!userMessage) {
272
+ log(config, "No content found for title generation");
273
+ return;
274
+ }
275
+ if (userMessage.length < config.minMessageLength) {
276
+ log(config, "Message too short, skipping");
277
+ return;
278
+ }
279
+ renamedSessions.add(sessionID);
280
+ setImmediate(async () => {
281
+ try {
282
+ const title = await generateTitle(ctx.client, config, userMessage, ctx.directory);
283
+ if (!title) {
284
+ log(config, "Failed to generate title for session:", sessionID);
285
+ return;
286
+ }
287
+ const dateStr = formatDate(config.dateFormat);
288
+ const fullTitle = `${title}(${dateStr})`;
289
+ await ctx.client.session.update({
290
+ path: { id: sessionID },
291
+ body: { title: fullTitle }
292
+ });
293
+ log(config, "Renamed session:", sessionID, "->", fullTitle);
294
+ } catch (error) {
295
+ console.error("[session-renamer] Failed to rename session:", error);
296
+ renamedSessions.delete(sessionID);
297
+ }
298
+ });
299
+ }
300
+ };
301
+ };
302
+ var sessionRenamer = plugin;
303
+ var src_default = plugin;
304
+ export {
305
+ sessionRenamer,
306
+ src_default as default
307
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "opencode-session-renamer",
3
+ "version": "1.0.0",
4
+ "description": "Auto-rename opencode sessions based on conversation content",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "session-renamer.example.jsonc"
11
+ ],
12
+ "keywords": [
13
+ "opencode",
14
+ "plugin",
15
+ "session",
16
+ "rename",
17
+ "ai"
18
+ ],
19
+ "author": "Joseph Han",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/joseph-bing-han/opencode-session-renamer"
24
+ },
25
+ "scripts": {
26
+ "build": "bun build src/index.ts --outdir dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
27
+ "dev": "bun run --watch src/index.ts",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "bun run build"
30
+ },
31
+ "dependencies": {
32
+ "@opencode-ai/plugin": "latest"
33
+ },
34
+ "devDependencies": {
35
+ "@types/bun": "latest",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@opencode-ai/plugin": "*"
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ // 用于生成标题的 LLM 模型,格式: "providerID/modelID";留空表示使用服务端默认模型
3
+ "model": "opencode/grok-code",
4
+
5
+ // 标题最大长度(不含日期后缀)
6
+ "titleMaxLength": 20,
7
+
8
+ // 日期格式:YY=年,MM=月,DD=日,HH=小时,mm=分钟
9
+ "dateFormat": "YY-MM-DD HH:mm",
10
+
11
+ // 触发重命名的最小消息长度
12
+ "minMessageLength": 5,
13
+
14
+ // 是否开启调试日志
15
+ "debug": false
16
+ }