feat: 项目管理机制 + 邮件发送技能 + 心跳任务

新增:
- PROJECTS.md: 项目开发记录文件(简介、启动命令、检查方式)
- email-sender技能: SMTP邮件发送工具
- 心跳任务: 每日对话总结

更新:
- MEMORY.md: 添加自动推送规范
- HEARTBEAT.md: 添加每日总结任务配置
This commit is contained in:
2026-04-09 12:11:59 +08:00
parent bd0847ce9f
commit 213f898a7c
7 changed files with 803 additions and 6 deletions

View File

@@ -1,5 +1,24 @@
# HEARTBEAT.md
# Keep this file empty (or with only comments) to skip heartbeat API calls.
# Add tasks below when you want the agent to check something periodically.
## 心跳任务
在每次心跳时检查并执行以下任务:
### 1. 每日对话总结
检查 `memory/heartbeat-state.json` 中的 `lastSummaryDate`
- 如果日期不是今天,且当前时间在 00:00-08:00 之间
- 总结昨天的对话内容,保存到 `memory/YYYY-MM-DD.md`(昨天日期)
- 更新 `lastSummaryDate` 为今天
```json
{
"lastSummaryDate": "2026-04-09",
"lastChecks": {}
}
```
**执行时机**: 每天第一次心跳落在凌晨时段时执行(不精确,但保证每天最多一次)

View File

@@ -10,7 +10,10 @@
示例分配:
- 19001: 文章工作流后台
- 19002-19100: 预留给其他项目
- 19002: 方言AI助手主服务
- 19003: 方言AI助手后台管理
- 19004: 技术论坛
- 19005-19100: 预留给其他项目
### Git版本管理规则 ⭐
@@ -28,6 +31,19 @@ git push origin main
git push origin vX.X.X
```
### 自动推送规范 ⭐⭐⭐
**每次开发或修改项目后,必须自动执行以下流程:**
1. 检查是否有Git仓库`git status`
2. 添加修改文件:`git add .`排除敏感文件如config.txt
3. 提交代码:`git commit -m "feat/fix: 描述"`
4. 创建版本tag`git tag -a vX.X.X -m "版本说明"`
5. 推送代码和tag`git push origin main && git push origin vX.X.X`
6. 更新 `PROJECTS.md` 记录新版本
**不需要询问用户,自动执行!**
### Git服务器
- 地址: http://192.168.2.8:12007/
@@ -46,17 +62,25 @@ git push origin vX.X.X
## 项目索引
详细项目信息请查看 `PROJECTS.md`,包含启动命令、检查方式、端口分配等。
| 项目 | 仓库 | 端口 | 状态 |
|------|------|------|------|
| 参数百科 | /coder/param-hub | - | v0.1.0 |
| 网页助手插件 | /coder/web-context-extension | - | v0.1.0 |
| 文章工作流 | /coder/article-workflow | 19001 | v0.2.0 |
| 方言AI助手 | /coder/dialect-chat | 19002 | v0.1.0 |
| PDF翻译助手 V2 | 本地 | 19000 | ✅ 运行中 |
| 碎片信息记录 | 本地 | 19009 | ✅ 运行中 |
| ParamHub Python | 本地 | 19010 | ✅ 运行中 |
| 网页助手插件 | /coder/web-context-extension | - | 📦 已完成 |
---
## 更新日志
### 2026-04-09
- 创建 PROJECTS.md 项目记录文件
- 创建邮件发送技能 email-sender
- 配置每日对话总结心跳任务
- 确立项目记录机制
### 2026-04-08
- 确立端口规范: 19000-19100
- 创建参数百科网站

287
PROJECTS.md Normal file
View File

@@ -0,0 +1,287 @@
# PROJECTS.md - 项目开发记录
> 本文件记录所有开发过的项目,包含简介、启动命令、检查方式。每次开发或修改项目后更新此文件。
---
## 项目列表
| # | 项目名称 | 类型 | 端口 | 状态 | 最后更新 |
|---|---------|------|------|------|---------|
| 1 | PDF翻译助手 V2 | Web | 19000 | ✅ 运行中 | 2026-04-09 |
| 2 | LLM Index RAG | Web | 19001 | 📦 已完成 | 2026-04-08 |
| 3 | 碎片信息记录 | Web | 19009 | ✅ 运行中 | 2026-04-09 |
| 4 | ParamHub Python版 | Web | 19010 | ✅ 运行中 | 2026-04-09 |
| 5 | 网页助手插件 | Extension | - | 📦 已完成 | 2026-04-08 |
| 6 | PDF翻译助手 V1 | Web | - | 📦 已完成 | 2026-04-08 |
| 7 | PDF翻译脚本 | CLI | - | 📦 已完成 | 2026-04-07 |
| 8 | A股历史数据系统 | CLI | - | 🔄 优化中 | 2026-04-09 |
---
## 项目详情
### 1. PDF翻译助手 V2 (pdf-translate-web-v2)
**简介**: 英文PDF翻译中文网站支持用户系统、会员体系、数据包购买、后台动态配置。
**目录**: `works/pdf-translate-web-v2`
**启动命令**:
```bash
cd ~/.openclaw/workspace-coder/works/pdf-translate-web-v2
python3 app.py
```
**访问地址**: http://localhost:19000
**后台管理**: http://localhost:19000/admin (账号: admin / admin123)
**检查服务状态**:
```bash
curl -s http://localhost:19000/api/health || echo "服务未启动"
```
**依赖**:
- Python 3 + Flask
- SQLite (自动创建)
- 本地LLM服务 (http://192.168.2.5:1234/v1)
---
### 2. LLM Index RAG (llm-index-rag)
**简介**: 基于索引和搜索的知识检索系统不依赖向量模型使用BM25排序和LLM增强索引。
**目录**: `works/llm-index-rag`
**启动命令**:
```bash
cd ~/.openclaw/workspace-coder/works/llm-index-rag
python3 app.py
```
**访问地址**: http://localhost:19001
**检查服务状态**:
```bash
curl -s http://localhost:19001/api/stats || echo "服务未启动"
```
**依赖**:
- Python 3 + Flask
- PostgreSQL (需配置 DATABASE_URL)
- 本地LLM服务
---
### 3. 碎片信息记录 (snippet-notes)
**简介**: 简洁的碎片信息记录工具支持实时保存、AI自动生成标题、搜索、置顶、导出Markdown。
**目录**: `works/snippet-notes`
**启动命令**:
```bash
cd ~/.openclaw/workspace-coder/works/snippet-notes
python3 app.py
```
**访问地址**: http://localhost:19009
**检查服务状态**:
```bash
curl -s http://localhost:19009/api/notes || echo "服务未启动"
```
**依赖**:
- Python 3 + Flask
- 本地文件存储 (JSON)
- 本地LLM API (生成标题)
---
### 4. ParamHub Python版 (param-hub-python)
**简介**: AI大模型与硬件参数速查平台支持模型/GPU/CPU数据库、对比工具、显存计算器。
**目录**: `works/param-hub-python`
**启动命令**:
```bash
cd ~/.openclaw/workspace-coder/works/param-hub-python
python3 app.py
```
**访问地址**: http://localhost:19010
**检查服务状态**:
```bash
curl -s http://localhost:19010/api/models || echo "服务未启动"
```
**依赖**:
- Python 3 + Flask
- Tailwind CSS (CDN)
- JSON文件存储
---
### 5. 网页助手插件 (web-context-extension)
**简介**: 浏览器扩展插件右键菜单支持收藏网页、AI总结、深度研究分析。
**目录**: `works/web-context-extension`
**安装方法**:
1. Chrome浏览器打开 `chrome://extensions/`
2. 开启"开发者模式"
3. 点击"加载已解压的扩展程序"
4. 选择 `works/web-context-extension` 目录
**配置**: 需在插件设置中配置OpenClaw API地址
**依赖**:
- Chrome/Edge浏览器
- OpenClaw服务
---
### 6. PDF翻译助手 V1 (pdf-translate-web)
**简介**: 英文PDF翻译中文网站第一版支持翻译、缓存、用户系统。
**目录**: `works/pdf-translate-web`
**状态**: 已废弃,推荐使用 V2 版本
---
### 7. PDF翻译脚本 (pdf-translator)
**简介**: 基于本地LLM的英文PDF翻译命令行工具支持Markdown/TXT/JSON输出。
**目录**: `works/pdf-translator`
**使用命令**:
```bash
cd ~/.openclaw/workspace-coder/works/pdf-translator
python3 translate_pdf.py input.pdf output.md
```
**依赖**:
- Python 3
- 本地LLM服务 (http://192.168.2.5:1234/v1)
---
### 8. A股历史数据系统 (stock_system)
**简介**: 获取所有A股从2010年至今的历史行情数据支持断点续传。V2版本优化了CPU和磁盘占用。
**目录**: `/home/xian/.openclaw/common/stock_system`
**启动命令**:
```bash
cd /home/xian/.openclaw/common/stock_system
bash run_v2.sh
# 或直接运行
python3 fetch_history_v2.py
```
**V2优化点**:
- 分文件存储每只股票存独立小文件避免每次读写203MB大文件
- 批量合并每50只股票合并一次到主文件
- SQLite进度库更可靠的断点续传机制
- 请求间隔减少从5秒降到0.3秒,用轻量存储补偿
**检查进度**:
```bash
# 查看SQLite进度统计
sqlite3 /home/xian/.openclaw/common/stock_system/data/progress.db \
"SELECT status, COUNT(*) FROM progress GROUP BY status"
# 查看数据文件大小
ls -lh /home/xian/.openclaw/common/stock_system/data/stock_daily_data.parquet
```
**配置**:
- Tushare Token: 放入 `config.txt` 文件
- 股票列表: `A股股票列表.csv`
**依赖**:
- Python 3 + tushare + pandas
- Tushare API Token
---
## 端口分配表
| 端口 | 项目 | 说明 |
|------|------|------|
| 19000 | PDF翻译助手 V2 | 主服务 + 后台 |
| 19001 | LLM Index RAG | API服务 |
| 19009 | 碎片信息记录 | Web服务 |
| 19010 | ParamHub Python | Web服务 |
| 19002-19008 | 预留 | 待分配 |
| 19011-19100 | 预留 | 待分配 |
**端口规范**: 所有Web服务必须使用 19000-19100 范围内的端口!
---
## Git仓库索引
| 项目 | 仓库地址 | 版本 |
|------|---------|------|
| ParamHub | http://192.168.2.8:12007/coder/param-hub | v0.2.0 |
| 网页助手插件 | http://192.168.2.8:12007/coder/web-context-extension | v0.1.0 |
| A股历史数据系统 | http://192.168.2.8:12007/coder/stock_system | v0.2.0 |
**Git服务器**: http://192.168.2.8:12007/
**账号**: coder / Hps123@!
---
## 快速操作命令
### 查看所有服务状态
```bash
for port in 19000 19001 19009 19010; do
echo "端口 $port:"
curl -s http://localhost:$port --max-time 1 > /dev/null && echo " ✅ 运行中" || echo " ❌ 未启动"
done
```
### 启动所有Web服务
```bash
# PDF翻译助手 V2
cd ~/.openclaw/workspace-coder/works/pdf-translate-web-v2 && nohup python3 app.py > /tmp/pdf-v2.log 2>&1 &
# 碎片信息记录
cd ~/.openclaw/workspace-coder/works/snippet-notes && nohup python3 app.py > /tmp/snippet.log 2>&1 &
# ParamHub Python
cd ~/.openclaw/workspace-coder/works/param-hub-python && nohup python3 app.py > /tmp/paramhub.log 2>&1 &
```
### 停止所有Web服务
```bash
pkill -f "app.py.*port=19000"
pkill -f "app.py.*port=19009"
pkill -f "app.py.*port=19010"
```
---
## 更新日志
### 2026-04-09
- 创建 PROJECTS.md 项目记录文件
- 记录现有8个项目详情
- 整理端口分配表和快速操作命令
- 优化 stock_system 获取脚本 V2分文件+批量合并+SQLite
### 待办
- [ ] 为每个项目创建 Git 仓库和版本 tag
- [ ] 配置 systemd 服务实现开机自启动

View File

@@ -0,0 +1,4 @@
{
"lastSummaryDate": null,
"lastChecks": {}
}

View File

@@ -0,0 +1,168 @@
---
name: email-sender
description: Send emails via SMTP with support for attachments, CC/BCC, HTML content, and multiple SMTP accounts. Use when user wants to send emails through Python/SMTP, configure email settings, or send emails with attachments.
---
# Email Sender / 邮件发送工具
通过 SMTP 发送邮件,支持附件、抄送/密送、HTML 格式和多账号管理。
## 快速开始
### 1. 配置 SMTP
```bash
cd ~/.openclaw/workspace-coder/skills/email-sender/scripts
python3 send_email.py config my-email \
--server mail.tphai.com \
--port 587 \
--email guwen@tphai.com \
--password "your-password" \
--no-tls
```
参数说明:
- `my-email`: 配置名称(可自定义)
- `--server`: SMTP 服务器地址
- `--port`: SMTP 端口
- `--email`: 邮箱地址
- `--password`: 邮箱密码
- `--no-tls`: 不使用 TLS 加密(默认使用 TLS
### 2. 查看配置
```bash
python3 send_email.py list
```
### 3. 发送邮件
**简单邮件:**
```bash
python3 send_email.py send \
--to wlq@tphai.com \
--subject "测试邮件" \
--body "这是一封测试邮件"
```
**带附件的邮件:**
```bash
python3 send_email.py send \
--to wlq@tphai.com \
--subject "带附件的邮件" \
--body "请查收附件" \
--attach /path/to/file1.pdf \
--attach /path/to/file2.jpg
```
**HTML 邮件:**
```bash
python3 send_email.py send \
--to wlq@tphai.com \
--subject "HTML 邮件" \
--body "<h1>标题</h1><p>内容</p>" \
--html
```
**抄送和密送:**
```bash
python3 send_email.py send \
--to wlq@tphai.com \
--cc "cc1@example.com,cc2@example.com" \
--bcc "bcc@example.com" \
--subject "抄送测试" \
--body "这是一封抄送测试邮件"
```
## Python API 使用
```python
from send_email import send_email, setup_account
# 配置 SMTP
setup_account(
name="work",
smtp_server="mail.tphai.com",
smtp_port=587,
email="guwen@tphai.com",
password="your-password",
use_tls=False
)
# 发送简单邮件
send_email(
to="wlq@tphai.com",
subject="测试",
body="内容"
)
# 发送带附件的邮件
send_email(
to="wlq@tphai.com",
subject="附件测试",
body="请查收附件",
attachments=["/path/to/file.pdf", "/path/to/image.jpg"],
verbose=True
)
# 发送 HTML 邮件
send_email(
to="wlq@tphai.com",
subject="HTML 测试",
body="<h1>Hello</h1><p>World</p>",
html=True
)
# 使用特定账号
send_email(
to="wlq@tphai.com",
subject="使用指定账号",
body="内容",
account_name="work"
)
```
## 配置文件
配置保存在 `scripts/smtp_config.json` 中:
```json
{
"accounts": [
{
"name": "my-email",
"smtp_server": "mail.tphai.com",
"smtp_port": 587,
"email": "guwen@tphai.com",
"password": "your-password",
"use_tls": false
}
]
}
```
## 常用 SMTP 设置
| 服务商 | SMTP 服务器 | 端口 | TLS |
|--------|-------------|------|-----|
| Gmail | smtp.gmail.com | 587 | 是 |
| Outlook | smtp.office365.com | 587 | 是 |
| QQ邮箱 | smtp.qq.com | 587 | 是 |
| 163邮箱 | smtp.163.com | 465 | 是 |
| 企业邮箱 | mail.tphai.com | 587 | 否 |
## 故障排除
**认证失败:**
- 检查邮箱密码是否正确
- Gmail 需要使用「应用专用密码」而非登录密码
- QQ邮箱需要使用授权码而非登录密码
**连接失败:**
- 检查 SMTP 服务器地址和端口
- 检查防火墙设置
- 尝试切换 TLS/SSL 模式
**邮件被退回:**
- 检查收件人地址是否正确
- 检查邮件头是否完整(本工具自动添加 Date 和 Message-Id

View File

@@ -0,0 +1,2 @@
# SMTP配置文件包含密码
smtp_config.json

View File

@@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""
邮件发送脚本
支持纯文字/HTML 邮件、抄送/密送、附件和批量发送
"""
import smtplib
import os
import sys
import json
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.utils import formatdate, make_msgid
from typing import List, Optional
# 配置文件路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(SCRIPT_DIR, "smtp_config.json")
def load_config() -> dict:
"""加载 SMTP 配置"""
if not os.path.exists(CONFIG_FILE):
return {"accounts": []}
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_config(config: dict):
"""保存 SMTP 配置"""
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
def setup_account(
name: str,
smtp_server: str,
smtp_port: int,
email: str,
password: str,
use_tls: bool = True
):
"""配置 SMTP 账号"""
config = load_config()
# 检查是否已存在同名配置
for i, acc in enumerate(config["accounts"]):
if acc["name"] == name:
print(f"配置 '{name}' 已存在,正在更新...")
config["accounts"][i] = {
"name": name,
"smtp_server": smtp_server,
"smtp_port": smtp_port,
"email": email,
"password": password, # 明文存储,仅用于本地
"use_tls": use_tls
}
save_config(config)
return
# 添加新配置
config["accounts"].append({
"name": name,
"smtp_server": smtp_server,
"smtp_port": smtp_port,
"email": email,
"password": password,
"use_tls": use_tls
})
save_config(config)
print(f"✅ 配置 '{name}' 已保存")
def list_accounts():
"""列出所有 SMTP 配置"""
config = load_config()
if not config["accounts"]:
print("暂无 SMTP 配置")
return
print("\n=== SMTP 配置列表 ===")
for i, acc in enumerate(config["accounts"], 1):
print(f"\n{i}. {acc['name']}")
print(f" 服务器: {acc['smtp_server']}:{acc['smtp_port']}")
print(f" 邮箱: {acc['email']}")
print(f" TLS: {'' if acc['use_tls'] else ''}")
def get_account(name: Optional[str] = None) -> Optional[dict]:
"""获取 SMTP 配置"""
config = load_config()
if not config["accounts"]:
return None
if name is None:
return config["accounts"][0]
for acc in config["accounts"]:
if acc["name"] == name:
return acc
return None
def send_email(
to: str,
subject: str,
body: str,
account_name: Optional[str] = None,
html: bool = False,
cc: Optional[str] = None,
bcc: Optional[str] = None,
from_name: Optional[str] = None,
attachments: Optional[List[str]] = None,
verbose: bool = False
) -> bool:
"""
发送邮件
参数:
to: 收件人邮箱地址
subject: 邮件主题
body: 邮件内容
account_name: SMTP 配置名称(可选,默认使用第一个)
html: 是否为 HTML 邮件(默认 False
cc: 抄送邮箱地址,多个用逗号分隔(可选)
bcc: 密送邮箱地址,多个用逗号分隔(可选)
from_name: 发件人显示名称(可选)
attachments: 附件文件路径列表(可选)
verbose: 是否显示详细日志(默认 False
返回:
bool: 发送成功返回 True失败返回 False
"""
try:
# 获取 SMTP 配置
account = get_account(account_name)
if account is None:
print("❌ 错误:未找到 SMTP 配置,请先运行配置")
return False
# 创建邮件
if attachments:
msg = MIMEMultipart()
msg.attach(MIMEText(body, "html" if html else "plain", "utf-8"))
else:
msg = MIMEText(body, "html" if html else "plain", "utf-8")
# 设置邮件头
msg["Subject"] = subject
msg["From"] = f"{from_name} <{account['email']}>" if from_name else account["email"]
msg["To"] = to
msg["Date"] = formatdate(localtime=True)
msg["Message-Id"] = make_msgid(domain=account["email"].split("@")[1])
if cc:
msg["Cc"] = cc
# 添加附件
if attachments:
for file_path in attachments:
if not os.path.exists(file_path):
print(f"⚠️ 附件不存在,已跳过: {file_path}")
continue
with open(file_path, "rb") as f:
part = MIMEApplication(f.read())
filename = os.path.basename(file_path)
part.add_header(
"Content-Disposition",
"attachment",
filename=filename
)
msg.attach(part)
if verbose:
print(f"📎 已添加附件: {filename}")
# 连接 SMTP 服务器
if verbose:
print(f"📡 正在连接 {account['smtp_server']}:{account['smtp_port']}...")
if account["use_tls"]:
server = smtplib.SMTP(account["smtp_server"], account["smtp_port"])
server.starttls()
else:
server = smtplib.SMTP(account["smtp_server"], account["smtp_port"])
# 登录
if verbose:
print(f"🔑 正在登录 {account['email']}...")
server.login(account["email"], account["password"])
# 准备收件人列表
recipients = [to]
if cc:
recipients.extend([addr.strip() for addr in cc.split(",")])
if bcc:
recipients.extend([addr.strip() for addr in bcc.split(",")])
# 发送邮件
if verbose:
print(f"📤 正在发送邮件...")
server.sendmail(account["email"], recipients, msg.as_string())
server.quit()
print(f"✅ 邮件发送成功")
print(f" 收件人: {to}")
if cc:
print(f" 抄送: {cc}")
if attachments:
valid_attachments = [a for a in attachments if os.path.exists(a)]
print(f" 附件: {len(valid_attachments)}")
return True
except Exception as e:
print(f"❌ 邮件发送失败: {e}")
return False
def main():
"""命令行入口"""
import argparse
parser = argparse.ArgumentParser(description="邮件发送工具")
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 配置命令
config_parser = subparsers.add_parser("config", help="配置 SMTP")
config_parser.add_argument("name", help="配置名称")
config_parser.add_argument("--server", required=True, help="SMTP 服务器地址")
config_parser.add_argument("--port", type=int, required=True, help="SMTP 端口")
config_parser.add_argument("--email", required=True, help="邮箱地址")
config_parser.add_argument("--password", required=True, help="邮箱密码")
config_parser.add_argument("--no-tls", action="store_true", help="不使用 TLS默认使用")
# 列出配置命令
subparsers.add_parser("list", help="列出所有配置")
# 发送命令
send_parser = subparsers.add_parser("send", help="发送邮件")
send_parser.add_argument("--to", "-t", required=True, help="收件人邮箱")
send_parser.add_argument("--subject", "-s", required=True, help="邮件主题")
send_parser.add_argument("--body", "-b", required=True, help="邮件内容")
send_parser.add_argument("--account", "-a", help="使用指定配置")
send_parser.add_argument("--html", action="store_true", help="HTML 格式")
send_parser.add_argument("--cc", help="抄送邮箱(多个用逗号分隔)")
send_parser.add_argument("--bcc", help="密送邮箱(多个用逗号分隔)")
send_parser.add_argument("--from-name", help="发件人显示名称")
send_parser.add_argument("--attach", "-f", action="append", help="附件文件路径(可多次使用)")
send_parser.add_argument("-v", "--verbose", action="store_true", help="显示详细日志")
args = parser.parse_args()
if args.command == "config":
setup_account(
name=args.name,
smtp_server=args.server,
smtp_port=args.port,
email=args.email,
password=args.password,
use_tls=not args.no_tls
)
elif args.command == "list":
list_accounts()
elif args.command == "send":
success = send_email(
to=args.to,
subject=args.subject,
body=args.body,
account_name=args.account,
html=args.html,
cc=args.cc,
bcc=args.bcc,
from_name=args.from_name,
attachments=args.attach,
verbose=args.verbose
)
sys.exit(0 if success else 1)
else:
parser.print_help()
if __name__ == "__main__":
main()