sophhub 0.4.1 → 0.4.2

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.
@@ -10,7 +10,7 @@
10
10
 
11
11
  `workspace-qa/memory/` 为知识库内容的访问映射,供客服 Agent 查询使用;知识库的实际维护以 `knowledge/` 目录为准。
12
12
 
13
- 可以通过调用 `sessions-analysis` skill 获取问答 Agent 的会话记录,问答 Agent 的会话记录存放在 `/home/node/.openclaw/agents/qa-agent/sessions/` 目录下。
13
+ 可以通过调用 `sessions-analysis` skill 获取问答 Agent 的会话记录,问答 Agent 的会话记录存放在 `/home/node/.openclaw/agents/ai-cs-qa/sessions/` 目录下。
14
14
 
15
15
  本会话面向管理员使用,默认依赖会话隔离进行访问控制,不额外要求口令认证;若部署环境发生变化,应由外层系统补充身份校验。
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "bot-secret",
3
+ "version": "1.0.1",
4
+ "types": [
5
+ "store"
6
+ ],
7
+ "displayName": "客服秘钥管理",
8
+ "description": "管理客服秘钥",
9
+ "changelog": [
10
+ {
11
+ "changes": [
12
+ "pyproject.toml 版本与 skill 对齐"
13
+ ],
14
+ "date": "2026-04-21",
15
+ "version": "1.0.1"
16
+ },
17
+ {
18
+ "changes": [
19
+ "初次提交"
20
+ ],
21
+ "date": "2026-04-21",
22
+ "version": "1.0.0"
23
+ }
24
+ ],
25
+ "createdAt": "2026-04-21",
26
+ "updatedAt": "2026-04-21"
27
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: bot-secret
3
+ description: Query and reset the customer service (qa-agent) API Secret. Use when the user asks to view, query, or reset API Secret, 查询客服 API Secret, 重置 API Secret.
4
+ ---
5
+
6
+ # 客服子系统 API Secret 管理
7
+
8
+ 查询或重置 qa-agent 的 API Secret(读写 openclaw 配置文件)。
9
+
10
+ ## 用法
11
+
12
+ ```bash
13
+ # 查询
14
+ uv run {baseDir}/scripts/secret.py get
15
+
16
+ # 重置
17
+ uv run {baseDir}/scripts/secret.py reset
18
+ ```
19
+
20
+ ## 输出格式
21
+
22
+ - 获取成功:`当前API Secret为:xxx`
23
+ - 重置成功:`API Secret已重置为:yyy`
24
+
25
+ ## 注意事项
26
+
27
+ - `reset` 会直接修改配置文件 `/home/node/.openclaw/openclaw.json`,执行前确认环境正确。
28
+ - 配置中缺少 `channels.bot-api.accounts.qa-agent` 时,`get` 无输出,`reset` 不会自动创建缺失节点。
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "bot-secret"
3
+ version = "1.0.1"
4
+ description = "Query and reset qa-agent API Secret from openclaw config"
5
+ requires-python = ">=3.10"
@@ -0,0 +1,68 @@
1
+ import json
2
+ import os
3
+ import secrets
4
+ import sys
5
+
6
+
7
+ def get_api_secret(config_path: str = "/home/node/.openclaw/openclaw.json"):
8
+ if not os.path.exists(config_path):
9
+ raise FileNotFoundError(f"Config file not found: {config_path}")
10
+ try:
11
+ with open(config_path, "r", encoding="utf-8") as f:
12
+ config = json.load(f)
13
+ except json.JSONDecodeError:
14
+ raise ValueError(f"Invalid JSON in config file: {config_path}")
15
+ channels = config.get("channels", {})
16
+ bot_api_channel_config = channels.get("bot-api", {})
17
+ if not bot_api_channel_config:
18
+ return None
19
+ accounts_config = bot_api_channel_config.get("accounts", {})
20
+ if not accounts_config:
21
+ return None
22
+ qa_agent_config = accounts_config.get("qa-agent", {})
23
+ if not qa_agent_config:
24
+ return None
25
+ return qa_agent_config.get("apiSecret", None)
26
+
27
+
28
+ def reset_api_secret(config_path: str = "/home/node/.openclaw/openclaw.json"):
29
+ if not os.path.exists(config_path):
30
+ raise FileNotFoundError(f"Config file not found: {config_path}")
31
+ try:
32
+ with open(config_path, "r", encoding="utf-8") as f:
33
+ config = json.load(f)
34
+ except json.JSONDecodeError:
35
+ raise ValueError(f"Invalid JSON in config file: {config_path}")
36
+ channels = config.get("channels", {})
37
+ bot_api_channel_config = channels.get("bot-api", {})
38
+ if not bot_api_channel_config:
39
+ return None
40
+ accounts_config = bot_api_channel_config.get("accounts", {})
41
+ if not accounts_config:
42
+ return None
43
+ qa_agent_config = accounts_config.get("qa-agent", {})
44
+ if not qa_agent_config:
45
+ return None
46
+ new_secret = secrets.token_hex(32)
47
+ qa_agent_config["apiSecret"] = new_secret
48
+ config["channels"]["bot-api"]["accounts"]["qa-agent"] = qa_agent_config
49
+ with open(config_path, "w", encoding="utf-8") as f:
50
+ json.dump(config, f, ensure_ascii=False, indent=4)
51
+ return new_secret
52
+
53
+
54
+ if __name__ == "__main__":
55
+ if len(sys.argv) != 2:
56
+ print("Usage: python secret.py <reset|get>")
57
+ sys.exit(1)
58
+ if sys.argv[1] == "reset":
59
+ new_secret = reset_api_secret()
60
+ if new_secret is not None:
61
+ print(f"API Secret已重置为:{new_secret}")
62
+ elif sys.argv[1] == "get":
63
+ secret = get_api_secret()
64
+ if secret is not None:
65
+ print(f"当前API Secret为:{secret}")
66
+ else:
67
+ print("Usage: python secret.py <reset|get>")
68
+ sys.exit(1)
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "image-description",
3
+ "version": "1.0.1",
4
+ "types": [
5
+ "store"
6
+ ],
7
+ "displayName": "图片描述",
8
+ "description": "生成图片描述",
9
+ "changelog": [
10
+ {
11
+ "changes": [
12
+ "在 pyproject.toml 中声明 sophnet-tools 依赖,并与 skill 版本对齐"
13
+ ],
14
+ "date": "2026-04-21",
15
+ "version": "1.0.1"
16
+ },
17
+ {
18
+ "changes": [
19
+ "初次提交"
20
+ ],
21
+ "date": "2026-04-21",
22
+ "version": "1.0.0"
23
+ }
24
+ ],
25
+ "createdAt": "2026-04-21",
26
+ "updatedAt": "2026-04-21"
27
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: image-description
3
+ description: Generate image descriptions with VLM. Use when the user asks to describe images, 图像描述, 看图说话, or extract visible content from an image URL/base64/local file.
4
+ ---
5
+
6
+ ## 用法
7
+
8
+ ```bash
9
+ # 使用 URL
10
+ uv run {baseDir}/scripts/ana_image.py "https://example.com/a.jpg"
11
+
12
+ # 使用本地文件(会先上传 OSS 再调用模型)
13
+ uv run {baseDir}/scripts/ana_image.py "/path/to/local.png"
14
+
15
+ # 使用 base64(直接透传到接口)
16
+ uv run {baseDir}/scripts/ana_image.py "data:image/iVBORw0KGgoAAAANSUhEUgAA..."
17
+
18
+ ```
19
+
20
+ ## 输出格式
21
+
22
+ - 成功:直接输出图片描述文本。
23
+ - 失败:抛出错误信息(HTTP 错误、网络错误、输入格式错误等)。
@@ -0,0 +1,8 @@
1
+ [project]
2
+ name = "image-description"
3
+ version = "1.0.1"
4
+ description = "Generate image descriptions with Sophnet via chat/completions"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "sophnet-tools>=0.0.1",
8
+ ]
@@ -0,0 +1,107 @@
1
+ import json
2
+ import os
3
+ import base64
4
+ import argparse
5
+ import urllib.error
6
+ import urllib.request
7
+ import sophnet_tools
8
+
9
+ API_URL = "https://www.sophnet.com/api/open-apis/v1/chat/completions"
10
+ MODEL = "Qwen2.5-VL-32B-Instruct"
11
+
12
+
13
+ def _is_likely_base64(raw: str) -> bool:
14
+ text = raw.strip()
15
+ if not text or any(ch.isspace() for ch in text):
16
+ return False
17
+ try:
18
+ base64.b64decode(text, validate=True)
19
+ return True
20
+ except Exception:
21
+ return False
22
+
23
+
24
+ def normalize_image_input(image_input: str, upload_timeout: int = 30) -> str:
25
+ text = image_input.strip()
26
+ if not text:
27
+ raise ValueError("图片输入不能为空")
28
+
29
+ if text.startswith(("http://", "https://")):
30
+ return text
31
+
32
+ if text.startswith("data:image/") and ";base64," in text:
33
+ return text
34
+
35
+ if os.path.exists(text):
36
+ return sophnet_tools.upload_oss(text, upload_timeout)
37
+
38
+ if _is_likely_base64(text):
39
+ return text
40
+
41
+ raise ValueError("无法识别图片输入:请传入 URL、base64 或本地文件路径")
42
+
43
+
44
+ def call_vlm(image_input: str) -> str:
45
+ api_key = sophnet_tools.get_api_key()
46
+ image_url = normalize_image_input(image_input)
47
+
48
+ payload = {
49
+ "messages": [
50
+ {
51
+ "role": "system",
52
+ "content": "详细描述图片中的所有内容,包括文字、物体、场景、人物等。直接描述,禁止废话",
53
+ },
54
+ {
55
+ "role": "user",
56
+ "content": [
57
+ {
58
+ "type": "image_url",
59
+ "image_url": {"url": image_url},
60
+ }
61
+ ],
62
+ },
63
+ ],
64
+ "model": MODEL,
65
+ "stream": False,
66
+ }
67
+
68
+ body = json.dumps(payload).encode("utf-8")
69
+ request = urllib.request.Request(
70
+ url=API_URL,
71
+ data=body,
72
+ method="POST",
73
+ headers={
74
+ "Content-Type": "application/json",
75
+ "Accept": "application/json",
76
+ "Authorization": f"Bearer {api_key}",
77
+ },
78
+ )
79
+
80
+ try:
81
+ with urllib.request.urlopen(request, timeout=120) as response:
82
+ raw = response.read().decode("utf-8")
83
+ data = json.loads(raw)
84
+ except urllib.error.HTTPError as exc:
85
+ detail = exc.read().decode("utf-8", errors="ignore")
86
+ raise RuntimeError(f"HTTPError: {exc.code} {exc.reason} | {detail}") from exc
87
+ except urllib.error.URLError as exc:
88
+ raise RuntimeError(f"URLError: {exc.reason}") from exc
89
+
90
+ try:
91
+ return data["choices"][0]["message"]["content"]
92
+ except (KeyError, IndexError, TypeError):
93
+ return json.dumps(data, ensure_ascii=False, indent=2)
94
+
95
+
96
+ def build_parser():
97
+ parser = argparse.ArgumentParser(description="调用 VLM 生成图片描述")
98
+ parser.add_argument(
99
+ "image",
100
+ help="图片输入,支持 http(s) URL、base64、data URL 或本地文件路径",
101
+ )
102
+ return parser
103
+
104
+
105
+ if __name__ == "__main__":
106
+ args = build_parser().parse_args()
107
+ print(call_vlm(args.image))
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "sessions-analysis",
3
+ "version": "1.0.2",
4
+ "types": [
5
+ "store"
6
+ ],
7
+ "displayName": "日志分析",
8
+ "description": "分析客服日志",
9
+ "changelog": [
10
+ {
11
+ "changes": [
12
+ "默认会话日志目录从 qa-agent 改为 ai-cs-qa;与 pyproject 版本对齐"
13
+ ],
14
+ "date": "2026-04-21",
15
+ "version": "1.0.2"
16
+ },
17
+ {
18
+ "changes": [
19
+ "日志目录不存在或不可读时返回空结果;新增 pyproject.toml"
20
+ ],
21
+ "date": "2026-04-21",
22
+ "version": "1.0.1"
23
+ },
24
+ {
25
+ "changes": [
26
+ "初次提交"
27
+ ],
28
+ "date": "2026-04-21",
29
+ "version": "1.0.0"
30
+ }
31
+ ],
32
+ "createdAt": "2026-04-21",
33
+ "updatedAt": "2026-04-21"
34
+ }
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: sessions-analysis
3
+ description: 查询指定日期的 QA 客服问答记录。当用户询问「昨天/今天客户问了哪些问题」「某天的问答记录」「QA 记录查询」时使用。默认从 ai-cs-qa 会话日志目录读取。
4
+ ---
5
+
6
+ # QA 记录查询
7
+
8
+ 从 ai-cs-qa 会话日志中按日期汇总用户提问与助手回答,用于查看某天的客服问答记录。
9
+
10
+ ## Processing Mode
11
+
12
+ **STRICT SERIAL PROCESSING ONLY** — 单次顺序执行,不拆分子任务、不并行。
13
+
14
+ ## Prerequisites
15
+
16
+ - Python 3.10+
17
+ - 日志目录可读(默认 `/home/node/.openclaw/agents/ai-cs-qa/sessions/`)
18
+
19
+ ## 默认日志目录
20
+
21
+ - **默认路径**:`/home/node/.openclaw/agents/ai-cs-qa/sessions/`
22
+ - 支持当前日志(`*.jsonl`)与轮转日志(`*.jsonl.reset.YYYY-MM-DDTHH-MM-SS.*`)
23
+ - 若需使用其他目录,通过 `--log-dir` / `-d` 指定
24
+
25
+ ## Usage
26
+
27
+ 当用户要查看某天的 QA 记录时:
28
+
29
+ 1. **解析日期**:从用户表述推断查询日期(如「昨天」「今天」「2026-03-05」),格式统一为 `YYYY-MM-DD`。
30
+ 2. **执行脚本**(在 workspace 或 skill 目录下用 uv 运行):
31
+
32
+ ```bash
33
+ uv run {baseDir}/scripts/ana_logs.py [YYYY-MM-DD] [--log-dir /path/to/sessions] [--json]
34
+ ```
35
+
36
+ ## Parameters
37
+
38
+ | 参数 | 说明 | 默认值 |
39
+ |------|------|--------|
40
+ | `date` | 查询日期,格式 `YYYY-MM-DD` | 当天 |
41
+ | `--log-dir` / `-d` | 会话日志所在目录 | `/home/node/.openclaw/agents/ai-cs-qa/sessions/` |
42
+ | `--json` | 以 JSON 数组输出 | 否(人类可读文本) |
43
+
44
+ ## Output
45
+
46
+ **不加 `--json` 时**(默认):
47
+
48
+ ```
49
+ [YYYY-MM-DD HH:MM:SS] Q: 用户问题内容
50
+ A: 助手回答内容
51
+
52
+ [YYYY-MM-DD HH:MM:SS] Q: 下一个问题
53
+ A: 对应回答
54
+ ```
55
+
56
+ **加 `--json` 时**:输出 JSON 数组,每项形如 `{"Q": "问题", "DATE": "YYYY-MM-DD HH:MM:SS", "A": "回答"}`。
57
+
58
+ **向用户展示时建议**:先说明查询日期与记录条数,再按时间顺序列出「时间 - 问题 - 回答」摘要;若记录较多可只展示前 N 条并注明「共 N 条,仅展示前若干条」。
59
+
60
+ ## Example Workflow
61
+
62
+ - 用户:「昨天客户都问了哪些问题」
63
+ 1. 计算昨天日期,如 `2026-03-05`
64
+ 2. 执行:`uv run {baseDir}/scripts/ana_logs.py 2026-03-05`
65
+ 3. 解析输出,向用户汇总展示
66
+
67
+ - 用户:「查一下 3 月 4 号的 QA 记录」
68
+ 1. 日期:`2026-03-04`
69
+ 2. 执行:`uv run {baseDir}/scripts/ana_logs.py 2026-03-04`
70
+ 3. 展示结果
71
+
72
+ - 用户:「从 /data/qa-logs 查今天的问答」
73
+ 1. 日期:当天
74
+ 2. 执行:`uv run {baseDir}/scripts/ana_logs.py --log-dir /data/qa-logs`
75
+ 3. 展示结果
76
+
77
+ ## Notes
78
+
79
+ - 脚本仅读取可能包含该日期的日志文件(当前日志视为含「今天+昨天」,轮转日志按文件名日期判断),再按记录内时间戳过滤到指定日期。
80
+ - 若目录不存在或无可读日志,脚本不会报错但结果为空;此时可提示用户检查日志目录或日期。
81
+ - `{baseDir}` 指本 skill 根目录(如 `skills/sessions-analysis`),调用时替换为实际路径。
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "sessions-analysis"
3
+ version = "1.0.2"
4
+ description = "Query QA session logs by date from ai-cs-qa jsonl"
5
+ requires-python = ">=3.10"
@@ -0,0 +1,206 @@
1
+ import os
2
+ from datetime import datetime, timedelta
3
+ import json
4
+ from typing import List
5
+
6
+ # 列出文件夹中所有的日志文件(当前日志 + 轮转日志)
7
+ def list_logs(folder):
8
+ if not os.path.isdir(folder):
9
+ return []
10
+ try:
11
+ names = os.listdir(folder)
12
+ except OSError:
13
+ return []
14
+ logs = []
15
+ for file in names:
16
+ # 当前日志:xxx.jsonl
17
+ if file.endswith(".jsonl") and ".jsonl.reset." not in file:
18
+ logs.append(os.path.join(folder, file))
19
+ # 轮转日志:xxx.jsonl.reset.YYYY-MM-DDTHH-MM-SS.ZZZZ
20
+ elif ".jsonl.reset." in file:
21
+ logs.append(os.path.join(folder, file))
22
+ return logs
23
+
24
+ # 获取日志保存的时间戳,返回该文件覆盖的日期 (当天, 前一天)
25
+ def get_log_save_time(file_path: str):
26
+ """返回 (date1, date2),表示该日志文件最多只包含 date1 和 date2 两天的数据。"""
27
+ name = os.path.basename(file_path)
28
+ # 当前日志:xxx.jsonl → 今天 + 昨天
29
+ if name.endswith(".jsonl") and ".jsonl.reset." not in name:
30
+ today = datetime.now().strftime("%Y-%m-%d")
31
+ prev = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
32
+ return today, prev
33
+ # 轮转日志:xxx.jsonl.reset.YYYY-MM-DDTHH-MM-SS.703Z → 轮转日 + 轮转前一日
34
+ if ".jsonl.reset." in name:
35
+ part = name.split("reset.")[-1]
36
+ save_date = part.split("T")[0] # YYYY-MM-DD
37
+ try:
38
+ dt = datetime.strptime(save_date, "%Y-%m-%d")
39
+ prev = (dt - timedelta(days=1)).strftime("%Y-%m-%d")
40
+ return save_date, prev
41
+ except ValueError:
42
+ return None, None
43
+ return None, None
44
+
45
+ def extract_user_questions(path: str) -> List[dict]:
46
+ """
47
+ 从日志 jsonl 中提取用户提问及对应的回答。
48
+ 返回格式: [{"Q": "问题", "DATE": "Y-M-D H:M:S", "A": "回答"}, ...]
49
+ 只保留包含 message_id 的用户消息。
50
+ """
51
+ records = []
52
+ with open(path, "r", encoding="utf-8") as f:
53
+ for line in f:
54
+ line = line.strip()
55
+ if not line:
56
+ continue
57
+ try:
58
+ records.append(json.loads(line))
59
+ except json.JSONDecodeError:
60
+ continue
61
+
62
+ results: List[dict] = []
63
+
64
+ for i, record in enumerate(records):
65
+ if record.get("type") != "message":
66
+ continue
67
+ message = record.get("message", {})
68
+ if message.get("role") != "user":
69
+ continue
70
+
71
+ question = None
72
+ date_str = None
73
+
74
+ for item in (message.get("content") or []):
75
+ if item.get("type") != "text":
76
+ continue
77
+ text = item.get("text", "")
78
+ if '"message_id"' not in text:
79
+ continue
80
+
81
+ lines = [l.strip() for l in text.splitlines() if l.strip()]
82
+ last_line = lines[-1] if lines else ""
83
+ question = None
84
+
85
+ # 格式一:末行形如 [Fri 2026-03-06 16:09 GMT+8] 算能有哪些产品
86
+ if last_line.startswith("[") and "]" in last_line:
87
+ time_part, question = last_line.split("]", 1)
88
+ question = question.strip()
89
+ ts = record.get("timestamp", "")
90
+ try:
91
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
92
+ dt_local = dt.astimezone()
93
+ date_str = dt_local.strftime("%Y-%m-%d %H:%M:%S")
94
+ except Exception:
95
+ date_str = time_part.strip("[] ").strip()
96
+ else:
97
+ # 格式二:Conversation info + 元数据 JSON 块 + 空行 + 问题(无方括号时间戳)
98
+ # 取最后一行作为问题,跳过纯 JSON/代码块行
99
+ for candidate in reversed(lines):
100
+ if candidate.startswith("```") or candidate.startswith("{") or candidate.startswith("}"):
101
+ continue
102
+ if "message_id" in candidate and "sender" in candidate:
103
+ continue
104
+ question = candidate.strip()
105
+ break
106
+ ts = record.get("timestamp", "")
107
+ try:
108
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
109
+ dt_local = dt.astimezone()
110
+ date_str = dt_local.strftime("%Y-%m-%d %H:%M:%S")
111
+ except Exception:
112
+ date_str = ""
113
+ if question:
114
+ break
115
+
116
+ if question is None:
117
+ continue
118
+
119
+ # 向后收集 assistant 的文本回答,直到遇到下一个 user 消息
120
+ answer_parts = []
121
+ for j in range(i + 1, len(records)):
122
+ r = records[j]
123
+ if r.get("type") != "message":
124
+ continue
125
+ msg = r.get("message", {})
126
+ if msg.get("role") == "user":
127
+ break
128
+ if msg.get("role") == "assistant":
129
+ for item in (msg.get("content") or []):
130
+ if item.get("type") == "text" and item.get("text", "").strip():
131
+ answer_parts.append(item["text"].strip())
132
+
133
+ results.append({
134
+ "Q": question,
135
+ "DATE": date_str,
136
+ "A": "\n".join(answer_parts),
137
+ })
138
+
139
+ return results
140
+
141
+
142
+ def get_daily_qa(date: str, log_folder: str="/home/node/.openclaw/agents/ai-cs-qa/sessions/") -> List[dict]:
143
+ """
144
+ 按日期汇总当天的 QA 结果。仅根据 get_log_save_time 读取可能包含该日期的日志文件。
145
+
146
+ 参数:
147
+ date: 查询日期,格式 'YYYY-MM-DD'
148
+ log_folder: 日志文件所在目录
149
+
150
+ 返回:
151
+ [{"Q": "问题", "DATE": "YYYY-MM-DD HH:MM:SS", "A": "回答"}, ...]
152
+ """
153
+ daily_results: List[dict] = []
154
+
155
+ for log_path in list_logs(log_folder):
156
+ d1, d2 = get_log_save_time(log_path)
157
+ if d1 is None or date not in (d1, d2):
158
+ continue
159
+
160
+ try:
161
+ qas = extract_user_questions(log_path)
162
+ except FileNotFoundError:
163
+ continue
164
+
165
+ for item in qas:
166
+ dt_str = (item.get("DATE") or "").strip()
167
+ if dt_str.startswith(date):
168
+ daily_results.append(item)
169
+
170
+ return daily_results
171
+
172
+
173
+ # 默认 QA 会话日志目录(可被 skill/CLI 覆盖)
174
+ DEFAULT_LOG_FOLDER = "/home/node/.openclaw/agents/ai-cs-qa/sessions/"
175
+
176
+
177
+ if __name__ == "__main__":
178
+ import argparse
179
+ parser = argparse.ArgumentParser(description="查询指定日期的 QA 记录")
180
+ parser.add_argument(
181
+ "date",
182
+ nargs="?",
183
+ default=datetime.now().strftime("%Y-%m-%d"),
184
+ help="查询日期,格式 YYYY-MM-DD,默认今天",
185
+ )
186
+ parser.add_argument(
187
+ "--log-dir",
188
+ "-d",
189
+ default=DEFAULT_LOG_FOLDER,
190
+ help="日志目录,默认 %(default)s",
191
+ )
192
+ parser.add_argument(
193
+ "--json",
194
+ action="store_true",
195
+ help="以 JSON 数组输出",
196
+ )
197
+ args = parser.parse_args()
198
+
199
+ daily_qa = get_daily_qa(args.date, args.log_dir)
200
+ if args.json:
201
+ print(json.dumps(daily_qa, ensure_ascii=False, indent=2))
202
+ else:
203
+ for qa in daily_qa:
204
+ print(f"[{qa.get('DATE', '')}] Q: {qa.get('Q', '')}")
205
+ print(f" A: {qa.get('A', '')}")
206
+ print()