Skill: batype/astock-daily (version 1.0.0)

# A 股每日精选技能 ## 描述 每日获取 A 股新股发行信息和 20 元以下的精选股票，通过邮件发送给用户。 ## 激活方式 当用户提到 A 股、新股、股票筛选、低价股时激活此技能。 ## 配置 在 `TOOLS.md` 中添加以下配置： ```markdown ### A 股股票技能 - 邮箱：[REDACTED_SECRET] - 价格上限：20 元 - 发送时间：每天 09:00 ``` ## 使用方法 ### 手动运行 ```bash node skills/astock-daily/index.js ``` ### 定时运行 通过 cron 或 OpenClaw 的 heartbeat 功能每天运行。 ## 数据源 - 东方财富网 API（新股发行） - 新浪财经 API（股票行情） ## 输出 邮件包含： 1. 近期新股发行列表（代码、名称、申购日期、发行价） 2. 20 元以下精选股票（代码、名称、现价、涨跌幅、成交量）

path: CONFIG.md
content: # ⚙️ A 股股票技能 - 配置指南

## 1️⃣ SMTP 邮箱配置

### 阿里云企业邮箱配置（已预设）

文件位置：`.env`

```bash
# 编辑 .env 文件，将 YOUR_PASSWORD_HERE 替换为你的邮箱密码
SMTP_CONFIG={"host":"smtp.qiye.aliyun.com","port":465,"secure":true,"user":"[REDACTED_SECRET]","pass":"你的密码","from":"[REDACTED_SECRET]"}
```

### 配置说明

| 参数 | 值 | 说明 |
|------|-----|------|
| host | smtp.qiye.aliyun.com | SMTP 服务器 |
| port | 465 | SSL 加密端口（推荐） |
| secure | true | 启用 SSL |
| user | [REDACTED_SECRET] | 邮箱账号 |
| pass | 你的密码 | 邮箱密码或授权码 |
| from | [REDACTED_SECRET] | 发件人地址 |

### 获取授权码（如果启用了安全登录）

1. 登录阿里云企业邮箱：https://qiye.aliyun.com
2. 进入 **设置** → **安全设置**
3. 开启 **客户端授权码**
4. 生成的授权码即为密码

---

## 2️⃣ 定时任务配置

### 方式一：手动添加 crontab

```bash
crontab -e
```

添加以下内容：

```
# A 股每日精选 - 每个交易日 9:30 运行（周一至周五）
30 9 * * 1-5 cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && /opt/homebrew/bin/node index.js >> /tmp/astock-daily.log 2>&1
```

保存后生效。

### 方式二：使用配置脚本

```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
./setup.sh
```

### 查看/管理定时任务

```bash
# 查看当前 cron 任务
crontab -l

# 删除所有 cron（谨慎！）
crontab -r

# 查看日志
tail -f /tmp/astock-daily.log
```

---

## 3️⃣ 测试发送

### 测试邮件发送

```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily

# 加载环境变量并运行
source .env && node index.js
```

### 预期输出

```
🚀 开始获取 A 股数据...
📧 目标邮箱：[REDACTED_SECRET]
💰 价格上限：¥20
📊 获取到新股：XX 只
📊 获取到低价股：XX 只
💾 数据已保存到：data-2026-02-27.json
✅ 邮件已发送（SMTP）
✅ 完成！
```

---

## 4️⃣ 常见问题

### ❌ 邮件发送失败

**检查项：**
1. 密码是否正确
2. 是否开启了 SMTP 服务
3. 防火墙是否阻止 465 端口
4. 查看错误日志：`tail /tmp/astock-daily.log`

**测试 SMTP 连接：**
```bash
telnet smtp.qiye.aliyun.com 465
```

### ❌ 数据获取失败

- 检查网络连接
- API 可能有访问限制，稍后再试
- 查看控制台错误信息

### ❌ Cron 不执行

**检查 cron 状态：**
```bash
# 查看 cron 日志（macOS）
grep cron /var/log/system.log

# 检查 cron 服务
sudo systemctl status cron  # Linux
sudo launchctl list | grep cron  # macOS
```

**确保 node 路径正确：**
```bash
which node
# 输出应该是：/opt/homebrew/bin/node
```

---

## 5️⃣ 修改配置

### 修改目标邮箱

编辑 `index.js`，找到 `CONFIG` 部分：

```javascript
const CONFIG = {
  email: '[REDACTED_SECRET]',   // 修改这里
  priceLimit: 20,
  maxStocks: 50,
};
```

### 修改价格上限

```javascript
const CONFIG = {
  email: '[REDACTED_SECRET]',
  priceLimit: 30,  // 修改这里，例如改为 30 元
  maxStocks: 50,
};
```

### 修改运行时间

编辑 crontab：
```bash
crontab -e
```

时间格式：`分 时 日 月 周`

示例：
- `30 9 * * 1-5` - 周一至周五 9:30
- `0 9 * * 1-5` - 周一至周五 9:00
- `30 9 * * *` - 每天 9:30

---

## 📞 需要帮助？

查看日志：
```bash
tail -f /tmp/astock-daily.log
```

测试运行：
```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
source .env && node index.js
```


path: CRON-JOB.md
content: # ⏰ OpenClaw Cron Job 配置

## 任务信息

| 字段 | 值 |
|------|-----|
| **任务 ID** | `7b941fcb-6355-4ab5-84e1-c0eba7c2cd04` |
| **任务名称** | A 股每日精选 |
| **描述** | 获取 A 股新股发行和 20 元以下精选股票，发送到邮箱 |
| **Cron 表达式** | `0 30 9 * * 1-5` |
| **时区** | Asia/Shanghai |
| **运行时间** | 每周一至周五 9:30 |
| **会话类型** | isolated（独立会话） |
| **超时时间** | 300 秒（5 分钟） |
| **下次运行** | 3 天后 |

---

## 🔧 管理命令

### 查看任务列表
```bash
openclaw cron list
```

### 查看任务详情
```bash
openclaw cron status fcb44571-0b4c-48f4-b675-49a00103e26d
```

### 立即运行测试
```bash
openclaw cron run fcb44571-0b4c-48f4-b675-49a00103e26d
```

### 暂停任务
```bash
openclaw cron disable fcb44571-0b4c-48f4-b675-49a00103e26d
```

### 启用任务
```bash
openclaw cron enable fcb44571-0b4c-48f4-b675-49a00103e26d
```

### 删除任务
```bash
openclaw cron rm fcb44571-0b4c-48f4-b675-49a00103e26d
```

### 查看运行历史
```bash
openclaw cron runs fcb44571-0b4c-48f4-b675-49a00103e26d
```

---

## 📊 运行日志

任务运行后，日志会显示在 OpenClaw 会话中。

也可以查看本地日志：
```bash
tail -f /tmp/astock-daily.log
```

---

## 📝 Cron 表达式说明

```
0 30 9 * * 1-5
│ │  │ │ │  │
│ │  │ │ │  └──── 星期一到周五
│ │  │ │ └────── 每月
│ │  │ └──────── 每月
│ │  └────────── 每天 9 点
│ └───────────── 30 分
└─────────────── 0 秒
```

---

## 🔄 修改运行时间

如果需要修改运行时间，先删除再添加：

```bash
# 删除旧任务
openclaw cron rm fcb44571-0b4c-48f4-b675-49a00103e26d

# 添加新任务（示例：改为 9:00 运行）
openclaw cron add \
  --name "A 股每日精选" \
  --description "获取 A 股新股发行和 20 元以下精选股票，发送到邮箱" \
  --cron "0 0 9 * * 1-5" \
  --tz "Asia/Shanghai" \
  --message "cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && source .env && node index.js" \
  --session "isolated" \
  --timeout-seconds 120 \
  --thinking "low"
```

---

## ✅ 状态确认

任务已成功添加，下次运行时间：**3 天后**（下一个交易日 9:30）


path: DNS-FIX.md
content: # 🔧 DNS 解析问题修复指南

## 🕵️ 问题原因

**FlClash 代理** 劫持了 DNS 请求，将阿里云 SMTP 域名解析到了错误的测试地址：

| 域名 | 正确 IP | 错误 IP |
|------|---------|---------|
| smtp.qiye.aliyun.com | 47.246.165.89 ✅ | 198.18.0.32 ❌ |
| smtp.mxhichina.com | 47.246.165.89 ✅ | 198.18.0.35 ❌ |

**198.18.0.0/15** 是 RFC 2544 网络测试地址段，不应该用于公共 DNS 解析。

---

## ✅ 解决方案

### 方案一：修复 hosts 文件（推荐）

运行修复脚本：

```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
chmod +x fix-hosts.sh
./fix-hosts.sh
```

或手动执行：

```bash
sudo sh -c 'echo "47.246.165.89 smtp.qiye.aliyun.com" >> /etc/hosts'
sudo sh -c 'echo "47.246.165.89 smtp.mxhichina.com" >> /etc/hosts'
```

### 方案二：FlClash 规则配置

在 FlClash 配置中添加直连规则：

1. 打开 FlClash
2. 进入 **规则** 或 **配置**
3. 添加以下规则：

```yaml
rules:
  - DOMAIN,smtp.qiye.aliyun.com,DIRECT
  - DOMAIN,smtp.mxhichina.com,DIRECT
  - DOMAIN-SUFFIX,aliyun.com,DIRECT
```

或者在 UI 中：
1. 找到 **规则设置**
2. 添加域名规则：`smtp.qiye.aliyun.com` → **直连**
3. 添加域名规则：`smtp.mxhichina.com` → **直连**

### 方案三：临时关闭代理

在 FlClash 中：
1. 切换到 **直连模式** (Direct Mode)
2. 或暂时关闭代理
3. 测试 SMTP 发送
4. 恢复代理模式

---

## 🧪 验证修复

```bash
# 测试 DNS 解析
ping smtp.qiye.aliyun.com
# 应该显示 47.246.165.89，而不是 198.18.0.32

# 测试 SMTP 发送
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
source .env && node index.js
```

---

## 📊 当前配置

- SMTP 服务器：`smtp.mxhichina.com:465`
- 邮箱：`[REDACTED_SECRET]`
- 真实 IP：`47.246.165.89`

---

## 🔍 诊断命令

```bash
# 检查 hosts 文件
grep "aliyun\|mxhichina" /etc/hosts

# 测试 DNS 解析
nslookup smtp.qiye.aliyun.com

# 通过 DoH 查询真实 IP
curl -s 'https://dns.alidns.com/resolve?name=smtp.qiye.aliyun.com'

# 检查 FlClash 进程
ps aux | grep -i flclash
```


path: QUICKSTART.md
content: # 🚀 快速开始 - 3 步完成配置

## ✅ 步骤 1：填写邮箱密码

编辑 `.env` 文件：
```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
vi .env
```

将 `YOUR_PASSWORD_HERE` 替换为你的邮箱密码：
```
SMTP_CONFIG={"host":"smtp.qiye.aliyun.com","port":465,"secure":true,"user":"[REDACTED_SECRET]","pass":"你的实际密码","from":"[REDACTED_SECRET]"}
```

保存退出（vi 按 `:wq`）

---

## ✅ 步骤 2：测试发送

```bash
cd /Users/batype/.openclaw/workspace-work/skills/astock-daily
source .env && node index.js
```

检查邮箱是否收到邮件！

---

## ✅ 步骤 3：设置定时任务

```bash
crontab -e
```

粘贴以下内容：
```
# A 股每日精选 - 每个交易日 9:30 运行
30 9 * * 1-5 cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && /opt/homebrew/bin/node index.js >> /tmp/astock-daily.log 2>&1
```

保存退出。

---

## 🎉 完成！

- 📧 邮件会发送到：**[REDACTED_SECRET]**
- 💰 筛选条件：**20 元以下**股票
- ⏰ 运行时间：**周一至周五 9:30**
- 📊 查看日志：`tail -f /tmp/astock-daily.log`

---

## ❓ 遇到问题？

查看完整配置指南：`CONFIG.md`


path: README-SMTP.md
content: # 🔧 SMTP 问题解决方案

## ❌ 当前问题

你的网络环境 DNS 被重定向到测试网络（RFC 2544），导致：
- `smtp.mxhichina.com` → `198.18.0.35` (测试地址)
- `smtp.qiye.aliyun.com` → `198.18.0.32` (测试地址)

这不是密码或配置问题，是网络限制。

---

## ✅ 解决方案

### 方案一：使用系统邮件（当前可用）

技能已经配置为自动降级到 sendmail。如果 macOS 系统邮件已配置，邮件会正常发送。

**检查是否收到邮件**：查看邮箱 [REDACTED_SECRET] 是否有今天发送的测试邮件。

### 方案二：配置 hosts 文件（推荐）

手动指定 SMTP 服务器的真实 IP 地址：

```bash
# 获取真实 IP（在正常网络环境下）
ping smtp.mxhichina.com
# 假设得到：47.118.78.123

# 编辑 hosts 文件
sudo vi /etc/hosts

# 添加：
47.118.78.123 smtp.mxhichina.com
```

### 方案三：使用其他网络

切换到正常网络环境（如手机热点），SMTP 应该可以正常工作。

### 方案四：使用 QQ 邮箱转发

如果你有 QQ 邮箱，可以配置转发：

```bash
# 使用 QQ 邮箱 SMTP
export SMTP_CONFIG='{"host":"smtp.qq.com","port":465,"secure":true,"tls":{"rejectUnauthorized":false},"user":"[REDACTED_SECRET]","pass":"授权码","from":"[REDACTED_SECRET]"}'
```

---

## 📊 当前状态

| 项目 | 状态 |
|------|------|
| 数据获取 | ✅ 成功 |
| 邮件生成 | ✅ 成功 |
| SMTP 发送 | ❌ DNS 问题 |
| sendmail | ✅ 可用（需系统配置） |

---

## 🧪 测试系统邮件

```bash
echo "测试" | mail -s "测试邮件" [REDACTED_SECRET]
```

如果收到邮件，说明系统邮件正常，技能可以直接使用！


path: README.md
content: # A 股每日精选技能

📈 每日自动获取 A 股新股发行信息和 20 元以下精选股票，通过邮件发送。

## 功能

- ✅ 获取最新新股发行数据（代码、名称、申购日期、发行价、上市日期）
- ✅ 筛选 20 元以下的活跃股票（按成交量排序）
- ✅ 生成精美的 HTML 邮件
- ✅ 支持 SMTP 和系统 mail 命令发送邮件
- ✅ 自动保存数据到本地 JSON 文件

## 安装

```bash
cd skills/astock-daily
npm install
```

依赖：
- nodemailer（可选，用于 SMTP 发送邮件）

## 配置

### 📧 1. SMTP 邮箱配置（阿里云企业邮箱）

编辑 `.env` 文件，填入你的邮箱密码：

```bash
# 编辑文件
vi .env

# 将 YOUR_PASSWORD_HERE 替换为你的邮箱密码
SMTP_CONFIG={"host":"smtp.qiye.aliyun.com","port":465,"secure":true,"user":"[REDACTED_SECRET]","pass":"你的密码","from":"[REDACTED_SECRET]"}
```

**其他邮箱配置参考：**
| 邮箱 | SMTP 服务器 | 端口 | SSL |
|------|------------|------|-----|
| QQ | smtp.qq.com | 587/465 | 推荐 465 |
| Gmail | smtp.gmail.com | 587/465 | 推荐 465 |
| 163 | smtp.163.com | 587/465 | 推荐 465 |
| Outlook | smtp-mail.outlook.com | 587 | 否 |

### ⏰ 2. 定时任务配置

```bash
crontab -e
```

添加（每个交易日 9:30 运行）：
```
30 9 * * 1-5 cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && /opt/homebrew/bin/node index.js >> /tmp/astock-daily.log 2>&1
```

详见 `CONFIG.md` 完整配置指南。

### 3. 系统 mail 命令（备选）

确保系统已配置 mail 命令：
```bash
# macOS 安装
brew install mailutils

# Ubuntu/Debian
sudo apt-get install mailutils
```

## 使用

### 手动运行

```bash
cd skills/astock-daily
node index.js
```

### 定时运行（每天 9 点）

#### 方式一：cron

```bash
crontab -e
```

添加：
```
0 9 * * * cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && node index.js >> /tmp/astock-daily.log 2>&1
```

#### 方式二：OpenClaw Heartbeat

在 `HEARTBEAT.md` 中添加：
```markdown
- 每天 09:00 运行 A 股股票筛选技能
```

然后在主会话中运行：
```bash
node skills/astock-daily/index.js
```

## 输出示例

邮件包含两个部分：

### 1. 新股发行
| 代码 | 名称 | 申购日期 | 发行价 | 上市日期 |
|------|------|----------|--------|----------|
| 301234 | 某某科技 | 2026-02-25 | 15.80 | 2026-03-05 |

### 2. 20 元以下精选股票
| 代码 | 名称 | 现价 | 涨跌幅 | 成交量 |
|------|------|------|--------|--------|
| 000123 | 某某股份 | ¥8.50 | +2.35% | 123,456 |

## 数据源

- 新股数据：东方财富网
- 股票行情：东方财富行情中心

## 注意事项

⚠️ **免责声明**：以上数据仅供参考，不构成投资建议。股市有风险，投资需谨慎。

## 自定义

修改 `index.js` 中的 `CONFIG` 对象：

```javascript
const CONFIG = {
  email: '[REDACTED_SECRET]',     // 目标邮箱
  priceLimit: 20,            // 价格上限（元）
  maxStocks: 50,             // 最多返回的股票数量
};
```

## 故障排除

### 邮件发送失败

1. 检查 SMTP 配置是否正确
2. 确认邮箱已开启 SMTP 服务
3. 如使用 Gmail，需要使用应用专用密码
4. 检查防火墙是否阻止 SMTP 端口

### 数据获取失败

1. 检查网络连接
2. API 可能有访问频率限制，稍后再试
3. 查看控制台错误信息

## 文件结构

```
astock-daily/
├── index.js          # 主程序
├── package.json      # 依赖配置
├── SKILL.md          # 技能说明
├── README.md         # 使用文档
└── data-*.json       # 每日数据备份（自动生成）
```

## 更新日志

- v1.0.0 (2026-02-27): 初始版本
  - 新股发行数据获取
  - 20 元以下股票筛选
  - HTML 邮件发送
  - 本地数据备份


path: config.js
content: #!/usr/bin/env node
/**
 * A 股股票技能配置向导
 */

const fs = require('fs');
const path = require('path');
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function ask(question) {
  return new Promise((resolve) => {
    rl.question(question, (answer) => {
      resolve(answer);
    });
  });
}

async function main() {
  console.log('📈 A 股每日精选 - 配置向导\n');
  
  // 读取当前配置
  const indexPath = path.join(__dirname, 'index.js');
  let indexContent = fs.readFileSync(indexPath, 'utf-8');
  
  // 当前邮箱
  const emailMatch = indexContent.match(/email:\s*'([^']+)'/);
  const currentEmail = emailMatch ? emailMatch[1] : '[REDACTED_SECRET]';
  
  // 当前价格上限
  const priceMatch = indexContent.match(/priceLimit:\s*(\d+)/);
  const currentPrice = priceMatch ? priceMatch[1] : 20;
  
  console.log(`当前配置:`);
  console.log(`  邮箱：${currentEmail}`);
  console.log(`  价格上限：¥${currentPrice}\n`);
  
  // 询问是否修改
  const modify = await ask('是否修改配置？(y/n) [n]: ');
  
  if (modify.toLowerCase() === 'y') {
    const newEmail = await ask(`新邮箱 [${currentEmail}]: `);
    const newPrice = await ask(`新价格上限 [${currentPrice}]: `);
    
    const email = newEmail || currentEmail;
    const price = newPrice || currentPrice;
    
    // 更新配置
    indexContent = indexContent.replace(
      /email:\s*'[^']+'/,
      `email: '${email}'`
    );
    indexContent = indexContent.replace(
      /priceLimit:\s*\d+/,
      `priceLimit: ${price}`
    );
    
    fs.writeFileSync(indexPath, indexContent);
    console.log(`\n✅ 配置已更新:`);
    console.log(`  邮箱：${email}`);
    console.log(`  价格上限：¥${price}`);
  }
  
  // SMTP 配置
  console.log('\n--- SMTP 邮件配置 ---');
  const setupSmtp = await ask('是否配置 SMTP 发送邮件？(y/n) [n]: ');
  
  if (setupSmtp.toLowerCase() === 'y') {
    console.log('\n常见邮箱 SMTP 配置:');
    console.log('  Gmail: smtp.gmail.com:587 (需要应用专用密码)');
    console.log('  QQ 邮箱：smtp.qq.com:587 (需要授权码)');
    console.log('  163 邮箱：smtp.163.com:587');
    console.log('  Outlook: smtp-mail.outlook.com:587\n');
    
    const host = await ask('SMTP 服务器：');
    const port = await ask('端口 [587]: ') || '587';
    const user = await ask('用户名 (邮箱): ');
    const pass = await ask('密码/授权码：');
    const from = await ask(`发件人邮箱 [${user}]: `) || user;
    
    const smtpConfig = {
      host,
      port: parseInt(port),
      secure: port === '465',
      user,
      pass,
      from: from || user,
    };
    
    console.log('\n✅ SMTP 配置完成！');
    console.log('\n使用方法:');
    console.log(`  export SMTP_CONFIG='${JSON.stringify(smtpConfig)}'`);
    console.log('\n或者添加到 ~/.zshrc 或 ~/.bashrc:');
    console.log(`  export SMTP_CONFIG='${JSON.stringify(smtpConfig)}'`);
    
    // 保存到 .env 文件
    const envPath = path.join(__dirname, '.env');
    fs.writeFileSync(envPath, `SMTP_CONFIG=${JSON.stringify(smtpConfig)}\n`);
    console.log(`\n💾 配置已保存到：${envPath}`);
  }
  
  console.log('\n--- 完成 ---');
  console.log('运行以下命令测试:');
  console.log('  node index.js\n');
  
  rl.close();
}

main().catch(console.error);


path: cron-example.sh
content: #!/bin/bash
# A 股每日精选 - Cron 定时任务配置示例
# 每天上午 9 点运行（A 股开盘后）

# 添加到 crontab:
# crontab -e
# 然后粘贴以下内容:

# 环境变量（如果需要 SMTP 发送邮件）
# export SMTP_CONFIG='{"host":"smtp.gmail.com","port":587,"secure":false,"user":"[REDACTED_SECRET]","pass":"your-app-password","from":"[REDACTED_SECRET]"}'

# 每天 9:00 运行
0 9 * * 1-5 cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && /usr/local/bin/node index.js >> /tmp/astock-daily.log 2>&1

# 或者每天 9:30 运行（避开开盘高峰）
# 30 9 * * 1-5 cd /Users/batype/.openclaw/workspace-work/skills/astock-daily && /usr/local/bin/node index.js >> /tmp/astock-daily.log 2>&1

# 查看日志:
# tail -f /tmp/astock-daily.log

# 查看 node 路径:
# which node


path: data-2026-02-27.json
content: {
  "date": "2026-02-27T08:13:45.764Z",
  "newStocks": [
    {
      "code": "920036",
      "name": "觅睿科技",
      "applyDate": "2026-02-26 00:00:00",
      "issuePrice": 21.52,
      "listingDate": null
    },
    {
      "code": "301680",
      "name": "固德电材",
      "applyDate": "2026-02-25 00:00:00",
      "issuePrice": 58,
      "listingDate": null
    },
    {
      "code": "920183",
      "name": "海菲曼",
      "applyDate": "2026-02-24 00:00:00",
      "issuePrice": 19.71,
      "listingDate": null
    },
    {
      "code": "920187",
      "name": "通领科技",
      "applyDate": "2026-02-11 00:00:00",
      "issuePrice": 29.62,
      "listingDate": null
    },
    {
      "code": "920168",
      "name": "通宝光电",
      "applyDate": "2026-02-09 00:00:00",
      "issuePrice": 16.17,
      "listingDate": "2026-02-26 00:00:00"
    },
    {
      "code": "920166",
      "name": "海圣医疗",
      "applyDate": "2026-02-04 00:00:00",
      "issuePrice": 12.64,
      "listingDate": "2026-02-12 00:00:00"
    },
    {
      "code": "920180",
      "name": "爱得科技",
      "applyDate": "2026-02-02 00:00:00",
      "issuePrice": 7.67,
      "listingDate": "2026-02-10 00:00:00"
    },
    {
      "code": "688816",
      "name": "易思维",
      "applyDate": "2026-02-02 00:00:00",
      "issuePrice": 55.95,
      "listingDate": "2026-02-11 00:00:00"
    },
    {
      "code": "688818",
      "name": "电科蓝天",
      "applyDate": "2026-01-30 00:00:00",
      "issuePrice": 9.47,
      "listingDate": "2026-02-10 00:00:00"
    },
    {
      "code": "603284",
      "name": "林平发展",
      "applyDate": "2026-01-30 00:00:00",
      "issuePrice": 37.88,
      "listingDate": "2026-02-10 00:00:00"
    },
    {
      "code": "688712",
      "name": "北芯生命",
      "applyDate": "2026-01-26 00:00:00",
      "issuePrice": 17.52,
      "listingDate": "2026-02-05 00:00:00"
    },
    {
      "code": "001220",
      "name": "世盟股份",
      "applyDate": "2026-01-23 00:00:00",
      "issuePrice": 28,
      "listingDate": "2026-02-03 00:00:00"
    },
    {
      "code": "920119",
      "name": "美德乐",
      "applyDate": "2026-01-21 00:00:00",
      "issuePrice": 41.88,
      "listingDate": "2026-01-30 00:00:00"
    },
    {
      "code": "920159",
      "name": "农大科技",
      "applyDate": "2026-01-19 00:00:00",
      "issuePrice": 25,
      "listingDate": "2026-01-28 00:00:00"
    },
    {
      "code": "601112",
      "name": "振石股份",
      "applyDate": "2026-01-19 00:00:00",
      "issuePrice": 11.18,
      "listingDate": "2026-01-29 00:00:00"
    },
    {
      "code": "688785",
      "name": "恒运昌",
      "applyDate": "2026-01-16 00:00:00",
      "issuePrice": 92.18,
      "listingDate": "2026-01-28 00:00:00"
    },
    {
      "code": "920076",
      "name": "国亮新材",
      "applyDate": "2026-01-14 00:00:00",
      "issuePrice": 10.76,
      "listingDate": "2026-01-22 00:00:00"
    },
    {
      "code": "920050",
      "name": "爱舍伦",
      "applyDate": "2026-01-12 00:00:00",
      "issuePrice": 15.98,
      "listingDate": "2026-01-21 00:00:00"
    },
    {
      "code": "920086",
      "name": "科马材料",
      "applyDate": "2026-01-06 00:00:00",
      "issuePrice": 11.66,
      "listingDate": "2026-01-16 00:00:00"
    },
    {
      "code": "603352",
      "name": "至信股份",
      "applyDate": "2026-01-06 00:00:00",
      "issuePrice": 21.88,
      "listingDate": "2026-01-15 00:00:00"
    }
  ],
  "lowPriceStocks": [
    {
      "code": "920021",
      "name": "流金科技",
      "price": 12.95,
      "changePercent": -1.145,
      "volume": 36961201,
      "turnover": 49050.0695,
      "marketCap": 40.0154977985
    },
    {
      "code": "920090",
      "name": "同辉信息",
      "price": 6.56,
      "changePercent": 3.797,
      "volume": 17645189,
      "turnover": 11596.1155,
      "marketCap": 13.0762806176
    },
    {
      "code": "920077",
      "name": "吉林碳谷",
      "price": 18.48,
      "changePercent": 0.654,
      "volume": 8744433,
      "turnover": 16197.5127,
      "marketCap": 108.6035997816
    },
    {
      "code": "920062",
      "name": "科润智控",
      "price": 12.31,
      "changePercent": -2.146,
      "volume": 8662137,
      "turnover": 10649.3849,
      "marketCap": 23.2297426987
    },
    {
      "code": "920010",
      "name": "凯添燃气",
      "price": 11.46,
      "changePercent": 2.965,
      "volume": 8245832,
      "turnover": 9384.8699,
      "marketCap": 26.8737
    },
    {
      "code": "920180",
      "name": "爱得科技",
      "price": 19.44,
      "changePercent": 3.294,
      "volume": 7776840,
      "turnover": 15118.3711,
      "marketCap": 22.9631201424
    },
    {
      "code": "920087",
      "name": "秋乐种业",
      "price": 17.52,
      "changePercent": 0.114,
      "volume": 7622366,
      "turnover": 13224.5407,
      "marketCap": 28.943040000000003
    },
    {
      "code": "920023",
      "name": "田野股份",
      "price": 4.54,
      "changePercent": -0.656,
      "volume": 5143194,
      "turnover": 2340.9426,
      "marketCap": 14.8596016
    },
    {
      "code": "920057",
      "name": "百甲科技",
      "price": 7.06,
      "changePercent": 0.284,
      "volume": 3116041,
      "turnover": 2213.1526,
      "marketCap": 12.7971887682
    },
    {
      "code": "920158",
      "name": "长江能科",
      "price": 16.34,
      "changePercent": 1.998,
      "volume": 2347070,
      "turnover": 3833.6746,
      "marketCap": 23.297572
    },
    {
      "code": "920145",
      "name": "恒合股份",
      "price": 18.2,
      "changePercent": -1.622,
      "volume": 2206443,
      "turnover": 4043.5702,
      "marketCap": 12.8401
    },
    {
      "code": "920014",
      "name": "特瑞斯",
      "price": 12.79,
      "changePercent": -1.006,
      "volume": 2134243,
      "turnover": 2729.959,
      "marketCap": 15.5854774297
    },
    {
      "code": "920146",
      "name": "华阳变速",
      "price": 8.22,
      "changePercent": -1.675,
      "volume": 2013779,
      "turnover": 1657.9198,
      "marketCap": 11.0962144146
    },
    {
      "code": "920124",
      "name": "南特科技",
      "price": 18.47,
      "changePercent": 0.654,
      "volume": 1965620,
      "turnover": 3619.0713,
      "marketCap": 27.471043280499998
    },
    {
      "code": "920175",
      "name": "东方碳素",
      "price": 11.76,
      "changePercent": 1.995,
      "volume": 1940934,
      "turnover": 2281.4643,
      "marketCap": 13.9944
    },
    {
      "code": "920184",
      "name": "国源科技",
      "price": 18.74,
      "changePercent": 0.053,
      "volume": 1825378,
      "turnover": 3417.7401,
      "marketCap": 24.8518090666
    },
    {
      "code": "920039",
      "name": "国义招标",
      "price": 11.62,
      "changePercent": -0.684,
      "volume": 1647387,
      "turnover": 1907.5854,
      "marketCap": 17.873884
    },
    {
      "code": "920030",
      "name": "德众汽车",
      "price": 6.7,
      "changePercent": -0.446,
      "volume": 1565929,
      "turnover": 1046.0238,
      "marketCap": 11.982258627
    },
    {
      "code": "920132",
      "name": "泰鹏智能",
      "price": 17.57,
      "changePercent": 0.285,
      "volume": 1459303,
      "turnover": 2556.3437,
      "marketCap": 14.552176800000002
    },
    {
      "code": "920001",
      "name": "纬达光电",
      "price": 18.47,
      "changePercent": 1.484,
      "volume": 1395009,
      "turnover": 2563.82,
      "marketCap": 28.380300878799996
    },
    {
      "code": "920198",
      "name": "微创光电",
      "price": 10.22,
      "changePercent": 0.393,
      "volume": 1340588,
      "turnover": 1359.8343,
      "marketCap": 16.491387718400002
    },
    {
      "code": "920017",
      "name": "星昊医药",
      "price": 18.88,
      "changePercent": 0.479,
      "volume": 1172895,
      "turnover": 2221.8599,
      "marketCap": 23.568334464
    },
    {
      "code": "920149",
      "name": "旭杰科技",
      "price": 11.76,
      "changePercent": -2.082,
      "volume": 1134265,
      "turnover": 1336.2507,
      "marketCap": 8.994421968
    },
    {
      "code": "920020",
      "name": "泰凯英",
      "price": 17.22,
      "changePercent": 0.643,
      "volume": 1124684,
      "turnover": 1927.7654,
      "marketCap": 38.09925
    },
    {
      "code": "920082",
      "name": "方正阀门",
      "price": 17.27,
      "changePercent": -0.975,
      "volume": 886450,
      "turnover": 1531.7763,
      "marketCap": 25.1028085
    },
    {
      "code": "920033",
      "name": "康普化学",
      "price": 17.57,
      "changePercent": 0.228,
      "volume": 803510,
      "turnover": 1405.8616,
      "marketCap": 20.932063425
    },
    {
      "code": "920221",
      "name": "易实精密",
      "price": 16.3,
      "changePercent": -0.972,
      "volume": 698591,
      "turnover": 1144.4623,
      "marketCap": 18.91452
    },
    {
      "code": "920212",
      "name": "智新电子",
      "price": 13.85,
      "changePercent": 0.362,
      "volume": 599534,
      "turnover": 827.2471,
      "marketCap": 14.69485
    },
    {
      "code": "920123",
      "name": "芭薇股份",
      "price": 15,
      "changePercent": 0.267,
      "volume": 595730,
      "turnover": 890.8707,
      "marketCap": 16.731
    },
    {
      "code": "920204",
      "name": "沪江材料",
      "price": 17.45,
      "changePercent": -0.343,
      "volume": 448963,
      "turnover": 780.3352,
      "marketCap": 16.453220576499998
    },
    {
      "code": "920108",
      "name": "宏海科技",
      "price": 13.29,
      "changePercent": 0,
      "volume": 398315,
      "turnover": 527.3481,
      "marketCap": 22.885379999999998
    },
    {
      "code": "920000",
      "name": "安徽凤凰",
      "price": 18.91,
      "changePercent": -0.369,
      "volume": 371776,
      "turnover": 703.7167,
      "marketCap": 17.336688
    },
    {
      "code": "920163",
      "name": "方大新材",
      "price": 14.95,
      "changePercent": -0.267,
      "volume": 314076,
      "turnover": 468.378,
      "marketCap": 19.24291642
    }
  ]
}

path: fix-hosts.sh
content: #!/bin/bash
# 修复阿里云 SMTP DNS 解析问题

echo "🔧 修复阿里云 SMTP DNS 解析"
echo ""

# 检查是否已有记录
if grep -q "smtp.qiye.aliyun.com" /etc/hosts 2>/dev/null; then
    echo "⚠️  /etc/hosts 中已有 smtp.qiye.aliyun.com 记录"
else
    echo "添加 hosts 记录..."
    sudo sh -c 'echo "47.246.165.89 smtp.qiye.aliyun.com" >> /etc/hosts'
    echo "✅ 已添加 smtp.qiye.aliyun.com"
fi

if grep -q "smtp.mxhichina.com" /etc/hosts 2>/dev/null; then
    echo "⚠️  /etc/hosts 中已有 smtp.mxhichina.com 记录"
else
    echo "添加 hosts 记录..."
    sudo sh -c 'echo "47.246.165.89 smtp.mxhichina.com" >> /etc/hosts'
    echo "✅ 已添加 smtp.mxhichina.com"
fi

echo ""
echo "📋 当前 hosts 文件中的阿里云记录:"
grep "aliyun\|mxhichina" /etc/hosts

echo ""
echo "🧪 测试解析:"
ping -c 1 smtp.qiye.aliyun.com 2>&1 | head -2

echo ""
echo "✅ 完成！现在可以重新测试 SMTP 发送了"
echo "   cd /Users/batype/.openclaw/workspace-work/skills/astock-daily"
echo "   source .env && node index.js"


path: index.js
content: #!/usr/bin/env node
/**
 * A 股每日精选股票技能
 * 获取新股发行信息和 20 元以下精选股票，发送邮件
 */

const https = require('https');
const http = require('http');

// 配置
const CONFIG = {
  email: '[REDACTED_SECRET]',
  priceLimit: 20, // 价格上限（元）
  maxStocks: 50,  // 最多返回的股票数量
};

/**
 * HTTP GET 请求
 */
function httpGet(url, timeout = 15000) {
  return new Promise((resolve, reject) => {
    const client = url.startsWith('https') ? https : http;
    const req = client.get(url, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'application/json, text/plain, */*',
      }
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try {
          resolve(JSON.parse(data));
        } catch (e) {
          resolve(data);
        }
      });
    });
    req.on('error', reject);
    req.setTimeout(timeout, () => {
      req.destroy();
      reject(new Error(`Request timeout after ${timeout}ms`));
    });
  });
}

/**
 * 获取新股发行数据（东方财富 API）
 */
async function getNewStocks() {
  try {
    // 东方财富新股申购数据
    const url = 'http://data.eastmoney.com/xg/xg/default.aspx';
    const apiData = await httpGet(
      'http://datacenter-web.eastmoney.com/api/data/v1/get?' + 
      'sortColumns=APPLY_DATE,SECURITY_CODE&sortTypes=-1,-1&' +
      'pageSize=20&pageNumber=1&reportName=RPTA_APP_IPOAPPLY&' +
      'columns=SECURITY_CODE,SECURITY_NAME,APPLY_DATE,ISSUE_PRICE,LISTING_DATE&' +
      'source=WEB&client=WEB'
    );
    
    if (apiData && apiData.result && apiData.result.data) {
      return apiData.result.data.map(item => ({
        code: item.SECURITY_CODE,
        name: item.SECURITY_NAME,
        applyDate: item.APPLY_DATE,
        issuePrice: item.ISSUE_PRICE,
        listingDate: item.LISTING_DATE,
      }));
    }
    return [];
  } catch (error) {
    console.error('获取新股数据失败:', error.message);
    return [];
  }
}

/**
 * 获取 A 股股票列表（新浪财经 API）
 * 筛选 20 元以下的股票
 */
async function getLowPriceStocks() {
  try {
    // 使用新浪财经 API 获取 A 股数据（返回 GBK 编码的 JSON）
    const apiUrl = 'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?' +
      'page=1&num=500&sort=symbol&asc=1&node=hs_a&symbol=&_s_r_a=page';
    
    const data = await httpGet(apiUrl);
    
    if (!data || !Array.isArray(data)) {
      console.log('股票 API 返回数据格式异常');
      return [];
    }
    
    console.log(`API 返回 ${data.length} 只股票`);
    
    // 筛选 20 元以下的股票
    const lowPriceStocks = data.filter(stock => {
      const price = parseFloat(stock.trade);
      return price !== null && price !== undefined && price > 0 && price <= CONFIG.priceLimit;
    });
    
    console.log(`筛选后 ${lowPriceStocks.length} 只股票（<=¥${CONFIG.priceLimit}）`);
    
    // 按成交量排序，取前 N 只
    lowPriceStocks.sort((a, b) => (parseInt(b.volume) || 0) - (parseInt(a.volume) || 0)); // 按成交量降序
    
    return lowPriceStocks.slice(0, CONFIG.maxStocks).map(stock => ({
      code: stock.code,
      name: stock.name,
      price: parseFloat(stock.trade),
      changePercent: parseFloat(stock.changepercent),
      volume: parseInt(stock.volume),
      turnover: parseFloat(stock.amount) / 10000, // 转为万
      marketCap: parseFloat(stock.mktcap) / 10000, // 转为亿
    }));
  } catch (error) {
    console.error('获取股票数据失败:', error.message);
    return [];
  }
}

/**
 * 格式化数字（添加千分位）
 */
function formatNumber(num) {
  if (num === null || num === undefined) return '-';
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

/**
 * 生成邮件内容
 */
function generateEmailContent(newStocks, lowPriceStocks) {
  const date = new Date().toLocaleDateString('zh-CN');
  
  let html = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
    .container { max-width: 800px; margin: 0 auto; padding: 20px; }
    h1 { color: #1a73e8; border-bottom: 2px solid #1a73e8; padding-bottom: 10px; }
    h2 { color: #333; margin-top: 30px; }
    table { width: 100%; border-collapse: collapse; margin: 15px 0; }
    th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
    th { background-color: #f5f5f5; font-weight: bold; }
    tr:hover { background-color: #f9f9f9; }
    .price { color: #e74c3c; font-weight: bold; }
    .positive { color: #e74c3c; }
    .negative { color: #27ae60; }
    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
  </style>
</head>
<body>
  <div class="container">
    <h1>📈 A 股每日精选</h1>
    <p>日期：${date}</p>
    
    <h2>🆕 新股发行</h2>
  `;
  
  if (newStocks.length > 0) {
    html += `
    <table>
      <thead>
        <tr>
          <th>代码</th>
          <th>名称</th>
          <th>申购日期</th>
          <th>发行价</th>
          <th>上市日期</th>
        </tr>
      </thead>
      <tbody>
    `;
    
    newStocks.forEach(stock => {
      html += `
        <tr>
          <td>${stock.code}</td>
          <td>${stock.name}</td>
          <td>${stock.applyDate || '-'}</td>
          <td class="price">¥${stock.issuePrice || '-'}</td>
          <td>${stock.listingDate || '-'}</td>
        </tr>
      `;
    });
    
    html += `</tbody></table>`;
  } else {
    html += `<p>近期无新股发行数据</p>`;
  }
  
  html += `
    <h2>💰 ${CONFIG.priceLimit}元以下精选股票</h2>
    <p>按成交量排序，共 ${lowPriceStocks.length} 只</p>
  `;
  
  if (lowPriceStocks.length > 0) {
    html += `
    <table>
      <thead>
        <tr>
          <th>代码</th>
          <th>名称</th>
          <th>现价</th>
          <th>涨跌幅</th>
          <th>成交量 (手)</th>
          <th>成交额 (万)</th>
          <th>总市值 (亿)</th>
        </tr>
      </thead>
      <tbody>
    `;
    
    lowPriceStocks.forEach(stock => {
      const changeClass = stock.changePercent >= 0 ? 'positive' : 'negative';
      const changeSign = stock.changePercent >= 0 ? '+' : '';
      html += `
        <tr>
          <td>${stock.code}</td>
          <td>${stock.name}</td>
          <td class="price">¥${stock.price.toFixed(2)}</td>
          <td class="${changeClass}">${changeSign}${stock.changePercent.toFixed(2)}%</td>
          <td>${formatNumber(stock.volume)}</td>
          <td>${formatNumber((stock.turnover / 10000).toFixed(2))}</td>
          <td>${formatNumber((stock.marketCap / 100000000).toFixed(2))}</td>
        </tr>
      `;
    });
    
    html += `</tbody></table>`;
  } else {
    html += `<p>暂无符合条件的股票数据</p>`;
  }
  
  html += `
    <div class="footer">
      <p>⚠️ 免责声明：以上数据仅供参考，不构成投资建议。股市有风险，投资需谨慎。</p>
      <p>数据来源：东方财富网、新浪财经</p>
    </div>
  </div>
</body>
</html>
  `;
  
  return html;
}

/**
 * 发送邮件（支持多种方式）
 */
async function sendEmail(htmlContent) {
  const { exec } = require('child_process');
  const fs = require('fs');
  const path = require('path');
  const nodemailer = require('nodemailer');
  
  const subject = `📈 A 股每日精选 - ${new Date().toLocaleDateString('zh-CN')}`;
  
  // 方法 1: 使用 nodemailer（需要配置 SMTP）- 推荐
  try {
    const smtpConfig = process.env.SMTP_CONFIG;
    if (smtpConfig) {
      console.log('正在尝试 SMTP 发送...');
      const config = JSON.parse(smtpConfig);
      console.log({
        host: config.host,
        port: parseInt(config.port) || 587,
        secure: config.secure || false,
        auth: {
          user: config.user,
          pass: config.pass,
        },
      });
      // 添加 TLS 配置避免证书问题
      const tlsConfig = config.tls || (config.secure ? { rejectUnauthorized: false } : undefined);
      
      const transporter = nodemailer.createTransport({
        host: config.host,
        port: parseInt(config.port) || 587,
        secure: config.secure || false,
        tls: tlsConfig,
        auth: {
          user: config.user,
          pass: config.pass,
        },
        connectionTimeout: 10000, // 连接超时 10 秒
        socketTimeout: 10000,     //  socket 超时 10 秒
      });
      
      // 测试连接（带超时）
      await Promise.race([
        transporter.verify(),
        new Promise((_, reject) => setTimeout(() => reject(new Error('SMTP verify timeout')), 15000))
      ]);
      console.log('SMTP 连接成功！');
      
      // 发送邮件（带超时）
      await Promise.race([
        transporter.sendMail({
          from: config.from || config.user,
          to: CONFIG.email,
          subject: subject,
          html: htmlContent,
        }),
        new Promise((_, reject) => setTimeout(() => reject(new Error('SMTP sendMail timeout')), 30000))
      ]);
      
      console.log('✅ 邮件已发送（SMTP）');
      return true;
    }
  } catch (error) {
    console.error('❌ SMTP 错误:', error.message);
    console.log('尝试其他方式...');
  }
  
  // 方法 2: 使用 sendmail（macOS 默认支持）
  try {
    const tempFile = path.join(__dirname, 'email.tmp');
    const emailContent = `To: ${CONFIG.email}
Subject: ${subject}
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8

${htmlContent}
`;
    
    fs.writeFileSync(tempFile, emailContent);
    
    return new Promise((resolve) => {
      exec(`/usr/sbin/sendmail -t < "${tempFile}"`, (error) => {
        fs.unlinkSync(tempFile);
        if (error) {
          console.error('❌ sendmail 发送失败:', error.message);
          resolve(false);
        } else {
          console.log('✅ 邮件已发送（sendmail）');
          resolve(true);
        }
      });
    });
  } catch (error) {
    console.error('❌ sendmail 发送失败:', error.message);
  }
  
  // 方法 3: 保存到文件，手动发送
  const outputFile = path.join(__dirname, `email-${new Date().toISOString().split('T')[0]}.html`);
  fs.writeFileSync(outputFile, htmlContent);
  console.log(`💾 邮件内容已保存到：${outputFile}`);
  console.log('💡 请配置 SMTP 或手动发送该 HTML 文件');
  
  return false;
}

/**
 * 保存数据到文件（备用）
 */
function saveToFile(data) {
  const fs = require('fs');
  const path = require('path');
  const date = new Date().toISOString().split('T')[0];
  const filePath = path.join(__dirname, `data-${date}.json`);
  
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
  console.log(`💾 数据已保存到：${filePath}`);
}

/**
 * 主函数
 */
async function main() {
  console.log('🚀 开始获取 A 股数据...');
  console.log(`📧 目标邮箱：${CONFIG.email}`);
  console.log(`💰 价格上限：¥${CONFIG.priceLimit}`);
  
  // 获取数据
  const [newStocks, lowPriceStocks] = await Promise.all([
    getNewStocks(),
    getLowPriceStocks(),
  ]);
  
  console.log(`📊 获取到新股：${newStocks.length} 只`);
  console.log(`📊 获取到低价股：${lowPriceStocks.length} 只`);
  
  // 生成邮件内容
  const emailContent = generateEmailContent(newStocks, lowPriceStocks);
  
  // 保存数据到文件
  saveToFile({
    date: new Date().toISOString(),
    newStocks,
    lowPriceStocks,
  });
  
  // 发送邮件
  await sendEmail(emailContent);
  
  console.log('✅ 完成！');
}

// 运行
main().catch(console.error);


path: package-lock.json
content: {
  "name": "astock-daily",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "astock-daily",
      "version": "1.0.0",
      "license": "MIT",
      "dependencies": {
        "nodemailer": "^6.9.0"
      }
    },
    "node_modules/nodemailer": {
      "version": "6.10.1",
      "resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-6.10.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT-0",
      "engines": {
        "node": ">=6.0.0"
      }
    }
  }
}


path: package.json
content: {
  "name": "astock-daily",
  "version": "1.0.0",
  "description": "A 股每日精选股票技能 - 获取新股和低价股信息并发送邮件",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "node index.js"
  },
  "dependencies": {
    "nodemailer": "^6.9.0"
  },
  "keywords": ["astock", "股票", "A 股", "新股", "邮件"],
  "author": "OpenClaw",
  "license": "MIT"
}


path: send-mail-applescript.js
content: #!/usr/bin/env node
/**
 * 使用 macOS 邮件应用发送邮件（绕过 SMTP）
 */

const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');

/**
 * 使用 AppleScript 通过 macOS 邮件应用发送邮件
 */
async function sendViaAppleMail(htmlContent, subject, to) {
  const { exec } = require('child_process');
  
  // 将 HTML 保存为临时文件
  const tempFile = path.join(__dirname, 'email-temp.html');
  fs.writeFileSync(tempFile, htmlContent);
  
  // AppleScript 发送邮件
  const script = `
    tell application "Mail"
      set newMessage to make new outgoing message with properties {subject:"${subject}", content:"${htmlContent.replace(/"/g, '"')}", visible:true}
      tell newMessage
        make new to recipient at end of to recipients with properties {address:"${to}"}
      end tell
      send newMessage
    end tell
  `.replace(/\n/g, ' ');
  
  return new Promise((resolve, reject) => {
    exec(`osascript -e '${script}'`, (error) => {
      fs.unlinkSync(tempFile);
      if (error) {
        reject(error);
      } else {
        resolve(true);
      }
    });
  });
}

/**
 * 使用 sendmail 发送
 */
async function sendViaSendmail(htmlContent, subject, to) {
  const { exec } = require('child_process');
  
  const emailContent = `To: ${to}
Subject: ${subject}
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8

${htmlContent}
`;
  
  const tempFile = path.join(__dirname, 'email-temp.txt');
  fs.writeFileSync(tempFile, emailContent);
  
  return new Promise((resolve, reject) => {
    exec(`/usr/sbin/sendmail -t < "${tempFile}"`, (error) => {
      fs.unlinkSync(tempFile);
      if (error) {
        reject(error);
      } else {
        resolve(true);
      }
    });
  });
}

module.exports = { sendViaAppleMail, sendViaSendmail };


path: setup-cron.sh
content: #!/bin/bash
# A 股每日精选 - 定时任务设置脚本

echo "⏰ 设置 A 股每日精选定时任务"
echo ""

# 获取当前 crontab
CURRENT_CRON=$(crontab -l 2>/dev/null || echo "")

# 检查是否已存在
if echo "$CURRENT_CRON" | grep -q "astock-daily"; then
    echo "⚠️  已存在 astock-daily 定时任务"
    echo ""
    echo "当前任务:"
    echo "$CURRENT_CRON" | grep "astock-daily"
    echo ""
    read -p "是否删除并重新添加？(y/n): " confirm
    if [ "$confirm" != "y" ]; then
        echo "已取消"
        exit 0
    fi
    # 删除旧任务
    echo "$CURRENT_CRON" | grep -v "astock-daily" | crontab -
fi

# 添加新任务
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CRON_LINE="30 9 * * 1-5 cd $SCRIPT_DIR && source .env && /opt/homebrew/bin/node index.js >> /tmp/astock-daily.log 2>&1"

# 添加到 crontab
(echo "$CURRENT_CRON"; echo "$CRON_LINE") | crontab -

echo ""
echo "✅ 定时任务已添加！"
echo ""
echo "📋 当前 crontab:"
crontab -l
echo ""
echo "📊 任务说明:"
echo "   - 运行时间：每周一至周五 9:30"
echo "   - 日志文件：/tmp/astock-daily.log"
echo ""
echo "🔧 管理命令:"
echo "   查看日志：tail -f /tmp/astock-daily.log"
echo "   查看任务：crontab -l"
echo "   删除任务：crontab -e (手动删除对应行)"


path: setup.sh
content: #!/bin/bash
# A 股股票技能 - 快速配置脚本

echo "📈 A 股每日精选 - 快速配置"
echo ""

# 检测邮箱服务商
echo "请选择你的邮箱服务商:"
echo "  1) QQ 邮箱"
echo "  2) Gmail"
echo "  3) 163 邮箱"
echo "  4) Outlook/Hotmail"
echo "  5) 其他（手动配置）"
echo ""
read -p "选择 [1-5]: " choice

case $choice in
  1)
    SMTP_HOST="smtp.qq.com"
    SMTP_PORT="587"
    echo ""
    echo "📧 QQ 邮箱配置说明:"
    echo "  1. 登录 https://mail.qq.com"
    echo "  2. 设置 → 账户 → 开启 POP3/SMTP 服务"
    echo "  3. 生成授权码（不是 QQ 密码！）"
    echo ""
    read -p "输入 QQ 邮箱号码: " EMAIL_USER
    read -p "输入授权码: " -s EMAIL_PASS
    echo ""
    ;;
  2)
    SMTP_HOST="smtp.gmail.com"
    SMTP_PORT="587"
    echo ""
    echo "📧 Gmail 配置说明:"
    echo "  1. 登录 Google 账户"
    echo "  2. 开启两步验证"
    echo "  3. 生成应用专用密码: https://myaccount.google.com/apppasswords"
    echo ""
    read -p "输入 Gmail 地址: " EMAIL_USER
    read -p "输入应用专用密码: " -s EMAIL_PASS
    echo ""
    ;;
  3)
    SMTP_HOST="smtp.163.com"
    SMTP_PORT="587"
    echo ""
    echo "📧 163 邮箱配置说明:"
    echo "  1. 登录 https://mail.163.com"
    echo "  2. 设置 → POP3/SMTP/IMAP → 开启 SMTP 服务"
    echo "  3. 生成授权码"
    echo ""
    read -p "输入 163 邮箱地址: " EMAIL_USER
    read -p "输入授权码: " -s EMAIL_PASS
    echo ""
    ;;
  4)
    SMTP_HOST="smtp-mail.outlook.com"
    SMTP_PORT="587"
    echo ""
    echo "📧 Outlook 邮箱配置说明:"
    echo "  1. 登录 Outlook 账户"
    echo "  2. 可能需要开启应用访问权限"
    echo ""
    read -p "输入 Outlook 邮箱地址: " EMAIL_USER
    read -p "输入密码: " -s EMAIL_PASS
    echo ""
    ;;
  *)
    read -p "输入 SMTP 服务器: " SMTP_HOST
    read -p "输入端口 [587]: " SMTP_PORT
    SMTP_PORT=${SMTP_PORT:-587}
    read -p "输入用户名 (邮箱): " EMAIL_USER
    read -p "输入密码: " -s EMAIL_PASS
    echo ""
    ;;
esac

# 创建 SMTP 配置
SMTP_CONFIG="{\"host\":\"$SMTP_HOST\",\"port\":$SMTP_PORT,\"secure\":false,\"user\":\"$EMAIL_USER\",\"pass\":\"$EMAIL_PASS\",\"from\":\"$EMAIL_USER\"}"

echo ""
echo "⚙️  正在保存配置..."

# 保存到 .env 文件
cat > .env << EOF
SMTP_CONFIG='$SMTP_CONFIG'
EOF

echo "✅ 配置已保存到 .env 文件"

# 添加到 shell 配置文件
SHELL_RC=""
if [ -f ~/.zshrc ]; then
  SHELL_RC=~/.zshrc
elif [ -f ~/.bashrc ]; then
  SHELL_RC=~/.bashrc
elif [ -f ~/.bash_profile ]; then
  SHELL_RC=~/.bash_profile
fi

if [ -n "$SHELL_RC" ]; then
  echo ""
  read -p "是否添加到 $SHELL_RC 以便全局使用？(y/n): " add_to_rc
  if [ "$add_to_rc" = "y" ]; then
    # 检查是否已存在
    if ! grep -q "SMTP_CONFIG" "$SHELL_RC" 2>/dev/null; then
      echo "" >> "$SHELL_RC"
      echo "# A 股股票技能 SMTP 配置" >> "$SHELL_RC"
      echo "export SMTP_CONFIG='$SMTP_CONFIG'" >> "$SHELL_RC"
      echo "✅ 已添加到 $SHELL_RC"
      echo "💡 运行 'source $SHELL_RC' 使其生效"
    else
      echo "⚠️  $SHELL_RC 中已存在 SMTP_CONFIG，跳过"
    fi
  fi
fi

# 配置 cron
echo ""
echo "⏰ 配置定时任务..."
read -p "是否添加每天 9:30 自动运行（周一至周五）? (y/n): " setup_cron

if [ "$setup_cron" = "y" ]; then
  NODE_PATH=$(which node)
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  CRON_LINE="30 9 * * 1-5 cd $SCRIPT_DIR && $NODE_PATH index.js >> /tmp/astock-daily.log 2>&1"
  
  # 检查 crontab 中是否已存在
  if crontab -l 2>/dev/null | grep -q "astock-daily"; then
    echo "⚠️  crontab 中已存在 astock-daily 任务"
  else
    (crontab -l 2>/dev/null | grep -v "astock-daily"; echo "$CRON_LINE") | crontab -
    echo "✅ 已添加 cron 任务：每天 9:30 运行（周一至周五）"
  fi
  
  echo ""
  echo "💡 查看日志：tail -f /tmp/astock-daily.log"
  echo "💡 查看 cron: crontab -l"
fi

echo ""
echo "=========================================="
echo "✅ 配置完成！"
echo "=========================================="
echo ""
echo "🧪 测试运行:"
echo "   source .env && node index.js"
echo ""
echo "📧 目标邮箱：[REDACTED_SECRET]"
echo "💰 价格上限：20 元"
echo ""


path: test-api.js
content: #!/usr/bin/env node
/**
 * 测试股票 API
 */

const https = require('https');
const http = require('http');

function httpGet(url) {
  return new Promise((resolve, reject) => {
    const client = url.startsWith('https') ? https : http;
    client.get(url, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'application/json, text/plain, */*',
        'Referer': 'http://quote.eastmoney.com/',
      }
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        console.log('HTTP 状态码:', res.statusCode);
        console.log('响应头:', JSON.stringify(res.headers));
        try {
          resolve(JSON.parse(data));
        } catch (e) {
          resolve(data);
        }
      });
    }).on('error', reject);
  });
}

async function test() {
  // 尝试多个 API
  const apis = [
    // 东方财富 API (HTTPS)
    'https://push2.eastmoney.com/api/qt/clist/get?' +
      'pn=1&pz=10&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&' +
      'fltt=2&invt=2&wbp2u=|0|0|0|web&' +
      'fid=f3&fs=m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23&' +
      'fields=f12,f14,f2,f3,f5,f6,f115,f162',
    
    // 新浪财经 API
    'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=1&num=10&sort=symbol&asc=1&node=hs_a&symbol=&_s_r_a=page',
  ];
  
  for (const apiUrl of apis) {
    console.log('\n========================================');
    console.log('请求 URL:', apiUrl.slice(0, 100) + '...');
    
    try {
      const data = await httpGet(apiUrl);
      console.log('返回数据类型:', typeof data);
      if (typeof data === 'string') {
        console.log('返回数据 (前 500 字符):', data.slice(0, 500));
      } else {
        console.log('返回数据:', JSON.stringify(data, null, 2).slice(0, 1000));
      }
    } catch (error) {
      console.error('错误:', error.message);
    }
  }
}

test();


path: test-smtp.js
content: #!/usr/bin/env node
/**
 * SMTP 连接测试脚本
 */

const nodemailer = require('nodemailer');

const configs = [
  {
    name: '端口 465 (SSL)',
    host: 'smtp.mxhichina.com',
    port: 465,
    secure: true,
    tls: { rejectUnauthorized: false },
  },
  {
    name: '端口 587 (STARTTLS)',
    host: 'smtp.mxhichina.com',
    port: 587,
    secure: false,
    requireTLS: true,
    tls: { rejectUnauthorized: false },
  },
  {
    name: '端口 25 (非加密)',
    host: 'smtp.mxhichina.com',
    port: 25,
    secure: false,
  },
];

const user = '[REDACTED_SECRET]';
const pass = '[REDACTED_SECRET]';

async function testConfig(config) {
  console.log(`\n${'='.repeat(50)}`);
  console.log(`测试：${config.name}`);
  console.log(`${'='.repeat(50)}`);
  
  const transporter = nodemailer.createTransport({
    host: config.host,
    port: config.port,
    secure: config.secure,
    requireTLS: config.requireTLS,
    tls: config.tls,
    auth: { user, pass },
    debug: true,
    logger: true,
  });
  
  try {
    console.log('正在验证连接...');
    await transporter.verify();
    console.log(`✅ ${config.name} - 连接成功！`);
    
    // 尝试发送测试邮件
    console.log('正在发送测试邮件...');
    await transporter.sendMail({
      from: user,
      to: user,
      subject: 'SMTP 测试邮件',
      text: '这是一封测试邮件，用于验证 SMTP 配置是否正确。',
    });
    console.log(`✅ ${config.name} - 邮件发送成功！`);
    return true;
  } catch (error) {
    console.error(`❌ ${config.name} - 失败：${error.message}`);
    if (error.response) {
      console.error(`   服务器响应：${error.response}`);
    }
    return false;
  } finally {
    await transporter.close();
  }
}

async function main() {
  console.log('📧 SMTP 连接测试');
  console.log(`用户：${user}`);
  console.log(`密码：${'*'.repeat(pass.length)}`);
  
  let success = false;
  for (const config of configs) {
    const result = await testConfig(config);
    if (result) {
      success = true;
      console.log(`\n✅ 推荐配置：使用 ${config.name}`);
      break;
    }
  }
  
  if (!success) {
    console.log('\n❌ 所有配置都失败了');
    console.log('\n可能原因：');
    console.log('  1. 密码错误（需要使用客户端授权码，不是登录密码）');
    console.log('  2. SMTP 服务未开启');
    console.log('  3. 账号被限制');
    console.log('\n解决方案：');
    console.log('  1. 登录 https://qiye.aliyun.com/alimail/');
    console.log('  2. 设置 → 安全设置 → 生成客户端授权码');
    console.log('  3. 使用授权码替换密码');
  }
}

main().catch(console.error);


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)