@lih-x-x/kmr 1.0.31 → 1.0.33
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 +24 -3
- package/dist/cli.js +31 -3
- package/dist/index.js +121 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
- **会议纪要提取**:发送飞书云文档链接给机器人,自动提取会议摘要、待办事项、风险项、关键共识、可复用知识
|
|
8
8
|
- **飞书任务创建**:提取待办后自动发送确认消息,回复 `/confirm` 创建飞书任务,`/reject` 取消,3 分钟超时自动取消。自动通过群成员列表解析负责人并指派任务
|
|
9
|
+
- **任务状态同步**:每小时自动从飞书拉取任务最新状态,查询时也会实时刷新。通过 `/task` 指令或 `kmr task` 命令查看所有任务详情
|
|
9
10
|
- **AI 对话**:发送非指令文本即可与 AI 自由对话,基于 acpx 持久化 session,按用户隔离,支持 claude 和 codex 两种 agent
|
|
10
11
|
- **历史会议查询**:通过 `/find` 指令用自然语言搜索历史会议记录
|
|
11
|
-
- **记录管理**:`/list` 列出所有记录,`/show <id>` 查看详情,`/del <id>`
|
|
12
|
-
-
|
|
12
|
+
- **记录管理**:`/list` 列出所有记录,`/show <id>` 查看详情,`/del <id>` 删除记录,`/task` 查看所有任务
|
|
13
|
+
- **CLI 命令**:`kmr list`、`kmr show`、`kmr task`、`kmr del`、`kmr find` 等子命令可在终端直接操作,无需启动服务
|
|
14
|
+
- **群聊智能识别**:群聊中非 @消息仅自动识别会议纪要文档链接(通过文档标题判断),其他操作需 @机器人
|
|
15
|
+
- **管理员通知**:配置 `adminOpenId` 后,服务启动和停止时自动向管理员推送通知
|
|
16
|
+
- **版本更新检查**:启动时自动检测 npm 新版本并提示更新
|
|
13
17
|
- **本地持久化**:所有数据以 JSON 文件存储在 `~/.kmr/` 目录下,支持 grep 检索
|
|
14
18
|
- **Web 管理界面**:服务启动时自动打开浏览器,查看会议记录、AI 会话、管理配置
|
|
15
19
|
|
|
@@ -88,7 +92,8 @@ npm run dev # 启动服务
|
|
|
88
92
|
{
|
|
89
93
|
"lark": {
|
|
90
94
|
"appId": "你的飞书应用 App ID",
|
|
91
|
-
"appSecret": "你的飞书应用 App Secret"
|
|
95
|
+
"appSecret": "你的飞书应用 App Secret",
|
|
96
|
+
"adminOpenId": "管理员的 open_id(可选,用于接收服务启停通知)"
|
|
92
97
|
},
|
|
93
98
|
"agent": {
|
|
94
99
|
"provider": "claude",
|
|
@@ -137,6 +142,20 @@ https://xxx.feishu.cn/docx/abc123
|
|
|
137
142
|
|
|
138
143
|
/del meeting_1714380000 # 删除某条记录
|
|
139
144
|
删掉 meeting_1714380000 # 自然语言同样支持
|
|
145
|
+
|
|
146
|
+
/task # 查看所有已创建任务的状态
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### CLI 命令
|
|
150
|
+
|
|
151
|
+
无需启动服务,直接在终端操作:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
kmr list # 列出所有会议记录
|
|
155
|
+
kmr show <id> # 查看记录详情(JSON)
|
|
156
|
+
kmr task # 查看所有任务状态
|
|
157
|
+
kmr del <id> # 删除记录
|
|
158
|
+
kmr find <query> # 搜索会议记录
|
|
140
159
|
```
|
|
141
160
|
|
|
142
161
|
### 创建飞书任务
|
|
@@ -228,6 +247,8 @@ kmr/
|
|
|
228
247
|
│ │ └── app.js
|
|
229
248
|
│ ├── utils/
|
|
230
249
|
│ │ └── claudeEnv.ts # ~/.claude/settings.json 环境变量加载
|
|
250
|
+
│ ├── task/
|
|
251
|
+
│ │ └── sync.ts # 飞书任务状态同步
|
|
231
252
|
│ └── storage/
|
|
232
253
|
│ ├── types.ts # 数据类型定义
|
|
233
254
|
│ └── jsonStore.ts # JSON 文件存储
|
package/dist/cli.js
CHANGED
|
@@ -6,9 +6,9 @@ async function checkUpdate() {
|
|
|
6
6
|
try {
|
|
7
7
|
const res = await fetch(`https://registry.npmjs.org/${"@lih-x-x/kmr"}/latest`, { signal: AbortSignal.timeout(3e3) });
|
|
8
8
|
const data = await res.json();
|
|
9
|
-
if (data.version && data.version !== "1.0.
|
|
9
|
+
if (data.version && data.version !== "1.0.33") {
|
|
10
10
|
console.log(`
|
|
11
|
-
\u2B06\uFE0F \u65B0\u7248\u672C\u53EF\u7528: ${"1.0.
|
|
11
|
+
\u2B06\uFE0F \u65B0\u7248\u672C\u53EF\u7528: ${"1.0.33"} \u2192 ${data.version}`);
|
|
12
12
|
console.log(` \u8FD0\u884C npm install -g ${"@lih-x-x/kmr"} \u66F4\u65B0
|
|
13
13
|
`);
|
|
14
14
|
}
|
|
@@ -49,12 +49,13 @@ KMR\uFF08Key Meetings Record\uFF09\u2014 \u4F1A\u8BAE\u6316\u6398\u673A
|
|
|
49
49
|
kmr \u542F\u52A8\u670D\u52A1\uFF08\u98DE\u4E66\u673A\u5668\u4EBA + Web \u7BA1\u7406\u754C\u9762\uFF09
|
|
50
50
|
kmr list \u5217\u51FA\u6240\u6709\u4F1A\u8BAE\u8BB0\u5F55
|
|
51
51
|
kmr show <id> \u67E5\u770B\u8BB0\u5F55\u8BE6\u60C5
|
|
52
|
+
kmr task \u67E5\u770B\u6240\u6709\u4EFB\u52A1
|
|
52
53
|
kmr del <id> \u5220\u9664\u8BB0\u5F55
|
|
53
54
|
kmr find <q> \u641C\u7D22\u4F1A\u8BAE\u8BB0\u5F55
|
|
54
55
|
kmr --help \u663E\u793A\u5E2E\u52A9
|
|
55
56
|
`);
|
|
56
57
|
} else if (command === "--version" || command === "-v") {
|
|
57
|
-
console.log("1.0.
|
|
58
|
+
console.log("1.0.33");
|
|
58
59
|
} else if (command === "list") {
|
|
59
60
|
const { loadConfig } = await import("./config-L2SVVMAR.js");
|
|
60
61
|
const { JsonStore } = await import("./jsonStore-AL73KEUG.js");
|
|
@@ -95,6 +96,32 @@ KMR\uFF08Key Meetings Record\uFF09\u2014 \u4F1A\u8BAE\u6316\u6398\u673A
|
|
|
95
96
|
process.exit(1);
|
|
96
97
|
}
|
|
97
98
|
console.log(JSON.stringify(record, null, 2));
|
|
99
|
+
} else if (command === "task") {
|
|
100
|
+
const { loadConfig } = await import("./config-L2SVVMAR.js");
|
|
101
|
+
const { JsonStore } = await import("./jsonStore-AL73KEUG.js");
|
|
102
|
+
const { createLarkClient } = await import("./client-CKWRSYEN.js");
|
|
103
|
+
const { TaskCreator } = await import("./taskCreator-QVSOLXXU.js");
|
|
104
|
+
const { syncTaskStatuses } = await import("./sync-E4SW7QFZ.js");
|
|
105
|
+
const config = loadConfig();
|
|
106
|
+
const store = new JsonStore(config.storage.dataDir);
|
|
107
|
+
const client = createLarkClient(config);
|
|
108
|
+
const taskCreator = new TaskCreator(client);
|
|
109
|
+
await syncTaskStatuses(store, taskCreator);
|
|
110
|
+
const meetings = await store.list();
|
|
111
|
+
let count = 0;
|
|
112
|
+
for (const m of meetings) {
|
|
113
|
+
if (!m.createdTasks) continue;
|
|
114
|
+
for (const t of m.createdTasks) {
|
|
115
|
+
const icon = t.status === "completed" ? "\u2705" : "\u23F3";
|
|
116
|
+
console.log(`${icon} ${t.summary}`);
|
|
117
|
+
console.log(` \u6765\u6E90\uFF1A${m.summary.title}`);
|
|
118
|
+
console.log(` \u521B\u5EFA\uFF1A${t.createdAt}${t.completedAt ? " | \u5B8C\u6210\uFF1A" + t.completedAt : ""}`);
|
|
119
|
+
if (t.url) console.log(` ${t.url}`);
|
|
120
|
+
console.log("");
|
|
121
|
+
count++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (count === 0) console.log("\u6682\u65E0\u5DF2\u521B\u5EFA\u7684\u4EFB\u52A1\u3002");
|
|
98
125
|
} else if (command === "del") {
|
|
99
126
|
const id = argv[3];
|
|
100
127
|
if (!id) {
|
|
@@ -140,6 +167,7 @@ KMR\uFF08Key Meetings Record\uFF09\u2014 \u4F1A\u8BAE\u6316\u6398\u673A
|
|
|
140
167
|
\u53EF\u7528\u547D\u4EE4:
|
|
141
168
|
kmr list \u5217\u51FA\u6240\u6709\u4F1A\u8BAE\u8BB0\u5F55
|
|
142
169
|
kmr show <id> \u67E5\u770B\u8BB0\u5F55\u8BE6\u60C5
|
|
170
|
+
kmr task \u67E5\u770B\u6240\u6709\u4EFB\u52A1
|
|
143
171
|
kmr del <id> \u5220\u9664\u8BB0\u5F55
|
|
144
172
|
kmr find <q> \u641C\u7D22\u4F1A\u8BAE\u8BB0\u5F55
|
|
145
173
|
kmr --help \u663E\u793A\u5B8C\u6574\u5E2E\u52A9
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
loadConfig
|
|
27
27
|
} from "./chunk-ZXGVA5QX.js";
|
|
28
28
|
|
|
29
|
+
// src/index.ts
|
|
30
|
+
import fs3 from "fs";
|
|
31
|
+
import path3 from "path";
|
|
32
|
+
|
|
29
33
|
// src/lark/docReader.ts
|
|
30
34
|
function extractDocumentId(url) {
|
|
31
35
|
const patterns = [
|
|
@@ -307,6 +311,9 @@ function parseMessage(text) {
|
|
|
307
311
|
if (/^\/(?:list|listall)$/i.test(trimmed) || /^(展示|列出|显示|查看)(所有|全部|历史)(记录|会议)/.test(trimmed)) {
|
|
308
312
|
return { type: "list_all" /* LIST_ALL */, raw: text };
|
|
309
313
|
}
|
|
314
|
+
if (/^\/task$/i.test(trimmed) || /^(展示|列出|显示|查看)(所有|全部)?(任务|待办)/.test(trimmed)) {
|
|
315
|
+
return { type: "list_tasks" /* LIST_TASKS */, raw: text };
|
|
316
|
+
}
|
|
310
317
|
const findMatch = text.match(/^\/find\s+(.+)/);
|
|
311
318
|
if (findMatch) {
|
|
312
319
|
return {
|
|
@@ -847,6 +854,41 @@ async function main() {
|
|
|
847
854
|
console.log(`[reply] \u5DF2\u53D1\u9001\u8BB0\u5F55\u5217\u8868`);
|
|
848
855
|
break;
|
|
849
856
|
}
|
|
857
|
+
case "list_tasks" /* LIST_TASKS */: {
|
|
858
|
+
console.log(`[process] \u5217\u51FA\u6240\u6709\u4EFB\u52A1`);
|
|
859
|
+
await syncTaskStatuses(store, taskCreator);
|
|
860
|
+
const meetings = await store.list();
|
|
861
|
+
const tasks = [];
|
|
862
|
+
for (const m of meetings) {
|
|
863
|
+
if (!m.createdTasks) continue;
|
|
864
|
+
for (const t of m.createdTasks) {
|
|
865
|
+
tasks.push({
|
|
866
|
+
summary: t.summary,
|
|
867
|
+
status: t.status || "open",
|
|
868
|
+
url: t.url,
|
|
869
|
+
createdAt: t.createdAt,
|
|
870
|
+
completedAt: t.completedAt,
|
|
871
|
+
meetingTitle: m.summary.title
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (tasks.length === 0) {
|
|
876
|
+
await messenger.replyText(messageId, "\u6682\u65E0\u5DF2\u521B\u5EFA\u7684\u4EFB\u52A1\u3002");
|
|
877
|
+
} else {
|
|
878
|
+
const lines = [`\u{1F4CB} \u5171 ${tasks.length} \u6761\u4EFB\u52A1\uFF1A`, ""];
|
|
879
|
+
for (const t of tasks) {
|
|
880
|
+
const icon = t.status === "completed" ? "\u2705" : "\u23F3";
|
|
881
|
+
lines.push(`${icon} ${t.summary}`);
|
|
882
|
+
lines.push(` \u6765\u6E90\uFF1A${t.meetingTitle}`);
|
|
883
|
+
lines.push(` \u521B\u5EFA\uFF1A${t.createdAt}${t.completedAt ? " | \u5B8C\u6210\uFF1A" + t.completedAt : ""}`);
|
|
884
|
+
if (t.url) lines.push(` ${t.url}`);
|
|
885
|
+
lines.push("");
|
|
886
|
+
}
|
|
887
|
+
await messenger.replyText(messageId, lines.join("\n"));
|
|
888
|
+
}
|
|
889
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u4EFB\u52A1\u5217\u8868`);
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
850
892
|
case "delete_record" /* DELETE_RECORD */: {
|
|
851
893
|
console.log(`[process] \u5220\u9664\u8BB0\u5F55: id=${parsed.deleteId}`);
|
|
852
894
|
const deleted = await store.delete(parsed.deleteId);
|
|
@@ -974,6 +1016,85 @@ async function main() {
|
|
|
974
1016
|
}
|
|
975
1017
|
syncTaskStatuses(store, taskCreator);
|
|
976
1018
|
setInterval(() => syncTaskStatuses(store, taskCreator), 60 * 60 * 1e3);
|
|
1019
|
+
if (config.lark.adminOpenId) {
|
|
1020
|
+
const adminId = config.lark.adminOpenId;
|
|
1021
|
+
const lastPushFile = path3.join(KMR_DIR, ".last_task_push");
|
|
1022
|
+
const getToday = () => (/* @__PURE__ */ new Date()).toLocaleDateString("sv-SE", { timeZone: "Asia/Shanghai" });
|
|
1023
|
+
const hasAlreadyPushedToday = () => {
|
|
1024
|
+
try {
|
|
1025
|
+
const last = fs3.readFileSync(lastPushFile, "utf-8").trim();
|
|
1026
|
+
return last === getToday();
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
const markPushed = () => {
|
|
1032
|
+
fs3.writeFileSync(lastPushFile, getToday(), "utf-8");
|
|
1033
|
+
};
|
|
1034
|
+
const pushDailyTaskSummary = async () => {
|
|
1035
|
+
if (hasAlreadyPushedToday()) return;
|
|
1036
|
+
console.log("[daily] \u63A8\u9001\u6BCF\u65E5\u4EFB\u52A1\u6458\u8981...");
|
|
1037
|
+
await syncTaskStatuses(store, taskCreator);
|
|
1038
|
+
const meetings = await store.list();
|
|
1039
|
+
const tasks = [];
|
|
1040
|
+
for (const m of meetings) {
|
|
1041
|
+
if (!m.createdTasks) continue;
|
|
1042
|
+
for (const t of m.createdTasks) {
|
|
1043
|
+
tasks.push({
|
|
1044
|
+
summary: t.summary,
|
|
1045
|
+
status: t.status || "open",
|
|
1046
|
+
createdAt: t.createdAt,
|
|
1047
|
+
completedAt: t.completedAt,
|
|
1048
|
+
meetingTitle: m.summary.title,
|
|
1049
|
+
url: t.url
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (tasks.length === 0) {
|
|
1054
|
+
markPushed();
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const openTasks = tasks.filter((t) => t.status !== "completed");
|
|
1058
|
+
const completedTasks = tasks.filter((t) => t.status === "completed");
|
|
1059
|
+
const lines = [`\u{1F4CB} \u6BCF\u65E5\u4EFB\u52A1\u6458\u8981\uFF08${getToday()}\uFF09`, ""];
|
|
1060
|
+
if (openTasks.length > 0) {
|
|
1061
|
+
lines.push(`\u23F3 \u8FDB\u884C\u4E2D\uFF08${openTasks.length}\uFF09\uFF1A`);
|
|
1062
|
+
for (const t of openTasks) {
|
|
1063
|
+
lines.push(` \u2022 ${t.summary}`);
|
|
1064
|
+
lines.push(` \u6765\u6E90\uFF1A${t.meetingTitle} | \u521B\u5EFA\uFF1A${t.createdAt}`);
|
|
1065
|
+
}
|
|
1066
|
+
lines.push("");
|
|
1067
|
+
}
|
|
1068
|
+
if (completedTasks.length > 0) {
|
|
1069
|
+
lines.push(`\u2705 \u5DF2\u5B8C\u6210\uFF08${completedTasks.length}\uFF09\uFF1A`);
|
|
1070
|
+
for (const t of completedTasks) {
|
|
1071
|
+
lines.push(` \u2022 ${t.summary}${t.completedAt ? "\uFF08" + t.completedAt + "\uFF09" : ""}`);
|
|
1072
|
+
}
|
|
1073
|
+
lines.push("");
|
|
1074
|
+
}
|
|
1075
|
+
try {
|
|
1076
|
+
await messenger.sendToUser(adminId, lines.join("\n"));
|
|
1077
|
+
markPushed();
|
|
1078
|
+
console.log("[daily] \u6BCF\u65E5\u4EFB\u52A1\u6458\u8981\u5DF2\u63A8\u9001");
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
console.error("[daily] \u63A8\u9001\u5931\u8D25:", err.message);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
const now = /* @__PURE__ */ new Date();
|
|
1084
|
+
const shanghaiHour = parseInt(now.toLocaleString("en-US", { timeZone: "Asia/Shanghai", hour: "numeric", hour12: false }));
|
|
1085
|
+
const shanghaiMinute = parseInt(now.toLocaleString("en-US", { timeZone: "Asia/Shanghai", minute: "numeric" }));
|
|
1086
|
+
if ((shanghaiHour > 9 || shanghaiHour === 9 && shanghaiMinute >= 30) && !hasAlreadyPushedToday()) {
|
|
1087
|
+
pushDailyTaskSummary();
|
|
1088
|
+
}
|
|
1089
|
+
setInterval(() => {
|
|
1090
|
+
const n = /* @__PURE__ */ new Date();
|
|
1091
|
+
const h = parseInt(n.toLocaleString("en-US", { timeZone: "Asia/Shanghai", hour: "numeric", hour12: false }));
|
|
1092
|
+
const m = parseInt(n.toLocaleString("en-US", { timeZone: "Asia/Shanghai", minute: "numeric" }));
|
|
1093
|
+
if (h === 9 && m === 30) {
|
|
1094
|
+
pushDailyTaskSummary();
|
|
1095
|
+
}
|
|
1096
|
+
}, 6e4);
|
|
1097
|
+
}
|
|
977
1098
|
if (config.lark.adminOpenId) {
|
|
978
1099
|
const adminId = config.lark.adminOpenId;
|
|
979
1100
|
let exiting = false;
|