Skill: cosmofang/jiajiaoy-morning (version 1.1.1)

--- name: jiajiaoy-morning description: | 每日早晚全套推送 skill： - 08:00 早间简报（11个模块：早报/科技/财经/天气/运势/历史/菜谱/名言/正念/运动/英语） - 18:00 明日运势（结合八字个性化推算） 支持任意 agent 执行，首次使用可向用户询问开关偏好后自动注册。 keywords: 早报, 简报, 晚间推送, 运势, morning, evening, 每日推送, 定时推送 metadata: openclaw: runtime: node: ">=18" --- # jiajiaoy-morning — 每日早晚全套推送 ## ⚠️ 首次使用：安装依赖 skill 本 skill 是**组合 skill**，依赖 11 个子 skill。首次使用前必须先安装依赖，否则脚本会报错。 **安装方法（在 skills 目录下执行）：** ```bash # 进入 skills 目录（jiajiaoy-morning 的上级目录） cd <skills目录> # 一键安装所有依赖 clawhub install newstoady clawhub install dailytech clawhub install dailyfinance clawhub install weather-daily clawhub install yunshi clawhub install daily-history clawhub install daily-recipe clawhub install daily-quote clawhub install daily-mindful clawhub install daily-fitness clawhub install english-daily ``` > 所有依赖均已发布在 clawhub registry，可直接安装。 > 安装后目录结构应为：`skills/newstoady/`、`skills/dailytech/` ... 与 `skills/jiajiaoy-morning/` 并列。 如果缺少依赖，运行 `node scripts/build-prompts.js` 时会自动提示缺少哪些 skill 及安装命令。 --- ## 触发场景 | 触发词 | 执行动作 | |--------|---------| | "发早报" / "今天早报" / cron 08:00 | 执行早间简报 | | "发晚报" / "明日运势" / cron 18:00 | 执行晚间运势 | | "设置早报" / "我想订阅" / 首次接入 | 执行安装向导 | | "查看我的设置" | 显示当前配置 | --- ## 脚本速查 ``` skills/jiajiaoy-morning/scripts/ ├── setup.js # 首次安装向导（输出问卷 / 保存配置） ├── build-prompts.js # 早间各模块 prompt 构建器 └── evening-push.js # 晚间明日运势 prompt 生成器 ``` --- ## 场景一：首次安装（用户没有配置文件） **Step 1** — 输出问卷，向用户提问： ```bash node scripts/setup.js ``` **Step 2** — 用户回答后，将答案整理为 JSON，保存配置： ```bash node scripts/setup.js --save '{"userId":"<id>","name":"<名字>","city":"上海","morningChannel":"telegram","morningTo":"<id>","eveningChannel":"telegram","eveningTo":"<id>","modules":{"news":true,"tech":true,"finance":true,"weather":true,"yunshi":true,"history":true,"recipe":true,"quote":true,"mindful":true,"fitness":true,"english":true,"yunshi_tomorrow":true}}' ``` **Step 3** — 脚本会输出 cron 注册指令，按指令在 openclaw 中添加两个定时任务。 --- ## 场景二：早间简报执行（cron 08:00 / 手动触发） **Step 1** — 构建所有模块 prompt： ```bash node scripts/build-prompts.js <userId> ``` 输出 JSON 数组，每项字段： - `key` — 模块标识 - `module` — 模块名称 - `emoji` — 图标 - `group` — 分组（1/2/3） - `prompt` — 待执行的指令（null 表示脚本出错，跳过） - `searchRequired` — 是否需要 WebSearch - `error` — 报错信息（仅在失败时） **Step 2** — 按 group 依次执行每个模块的 prompt，收集结果。 **Step 3** — 按 group 分 3 条消息发送： | 消息 | 模块 | 说明 | |------|------|------| | 消息1（group=1） | 📰早报 + 💻科技 + 💰财经 + 🌤️天气 | 需要 WebSearch | | 消息2（group=2） | 🔮运势 + 📅历史 + 🍳菜谱 | 部分需搜索 | | 消息3（group=3） | 💬名言 + 🧘正念 + 💪运动 + 📚英语 | 纯生成 | 每条消息头部格式： ``` 🌅 早安 <name>！<年月日 星期X> ``` **错误处理**：某模块 prompt=null 时跳过，其余照常发送。 --- ## 场景三：晚间明日运势执行（cron 18:00 / 手动触发） **Step 1** — 生成明日运势 prompt： ```bash node scripts/evening-push.js <userId> ``` **Step 2** — 执行输出的 prompt，结合八字推算明日运势。 **Step 3** — 发送结果。 --- ## 已注册用户 | userId | 名字 | 城市 | 早间渠道 | 晚间渠道 | 开启模块 | |--------|------|------|---------|---------|---------| | 8603011439 | 方靖 | 上海 | Telegram | Telegram | 全部 11 项 + 明日运势 | --- ## 错误处理规则 | 错误 | 处理方式 | |------|---------| | 某模块脚本报错 | 跳过该模块，其余正常发送 | | WebSearch 不可用 | 模块内标注 ⚠️，降级为知识库内容 | | 消息超 4096 字符 | 按 group 分组已控制，单组不会超限 | | 超时（>480s） | 已完成的 group 先发送，未完成标注缺失 | --- ## 模块开关说明 用户配置存储于： ``` skills/jiajiaoy-morning/data/users/<userId>.json ``` `modules.morning` 中各 key 设为 `false` 可关闭对应模块； `modules.evening.yunshi_tomorrow` 设为 `false` 关闭晚间运势。 查看当前配置： ```bash node scripts/setup.js --show <userId> ```

path: _meta.json
content: ownerId: kn79bebfnwg15sb0g7cj5z5nyd83gxh0
slug: jiajiaoy-morning
version: 1.1.1
publishedAt: 1776247200000

path: data/users/8603011439.json
content: {
  "userId": "8603011439",
  "name": "方靖",
  "city": "上海",
  "language": "zh",
  "channels": {
    "morning": {
      "channel": "telegram",
      "to": "8603011439"
    },
    "evening": {
      "channel": "telegram",
      "to": "8603011439"
    }
  },
  "modules": {
    "morning": {
      "news": true,
      "tech": true,
      "finance": true,
      "weather": true,
      "yunshi": true,
      "history": true,
      "recipe": true,
      "quote": true,
      "mindful": true,
      "fitness": true,
      "english": true
    },
    "evening": {
      "yunshi_tomorrow": true
    }
  },
  "createdAt": "2026-04-15",
  "updatedAt": "2026-04-15"
}

path: data/users/template.json
content: {
  "userId": "",
  "name": "",
  "city": "上海",
  "language": "zh",
  "channels": {
    "morning": { "channel": "telegram", "to": "" },
    "evening": { "channel": "telegram", "to": "" }
  },
  "modules": {
    "morning": {
      "news":     true,
      "tech":     true,
      "finance":  true,
      "weather":  true,
      "yunshi":   true,
      "history":  true,
      "recipe":   true,
      "quote":    true,
      "mindful":  true,
      "fitness":  true,
      "english":  true
    },
    "evening": {
      "yunshi_tomorrow": true
    }
  },
  "createdAt": "",
  "updatedAt": ""
}


path: scripts/build-prompts.js
content: #!/usr/bin/env node
/**
 * jiajiaoy-morning — 早间简报 prompt 构建器
 *
 * 读取用户配置，依次运行各模块脚本，输出 JSON 数组供 agent 执行。
 *
 * 用法：
 *   node build-prompts.js <userId>               # 输出 JSON
 *   node build-prompts.js <userId> --cron-message # 输出单条 cron payload 消息
 */

'use strict';

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

const USERS_DIR   = path.join(__dirname, '../data/users');
const SKILLS_BASE = path.join(__dirname, '../../');

// ── 依赖检查 ──────────────────────────────────────────────────────────────────
const REQUIRED_SKILLS = [
  { slug: 'newstoady',    script: 'scripts/morning-push.js' },
  { slug: 'dailytech',   script: 'scripts/morning-push.js' },
  { slug: 'dailyfinance',script: 'scripts/morning-push.js' },
  { slug: 'weather-daily',script: 'scripts/morning-push.js' },
  { slug: 'yunshi',      script: 'scripts/daily-push.js' },
  { slug: 'daily-history',script: 'scripts/morning-push.js' },
  { slug: 'daily-recipe',script: 'scripts/morning-push.js' },
  { slug: 'daily-quote', script: 'scripts/morning-push.js' },
  { slug: 'daily-mindful',script: 'scripts/morning-push.js' },
  { slug: 'daily-fitness',script: 'scripts/morning-push.js' },
  { slug: 'english-daily',script: 'scripts/daily-push.js' },
];

const missing = REQUIRED_SKILLS.filter(dep =>
  !fs.existsSync(path.join(SKILLS_BASE, dep.slug, dep.script))
);

if (missing.length > 0) {
  const skillsDir = path.resolve(SKILLS_BASE);
  console.error('❌ 缺少依赖 skill，请先安装：\n');
  console.error(`cd "${skillsDir}"\n`);
  missing.forEach(dep => {
    console.error(`clawhub install ${dep.slug}`);
  });
  console.error('\n安装完成后重新运行本脚本。');
  process.exit(1);
}

function sanitizeId(v) {
  if (typeof v !== 'string' || !/^[a-zA-Z0-9_@-]{1,128}$/.test(v)) {
    console.error('❌ 无效的 userId'); process.exit(1);
  }
  return v;
}

function safeUserPath(userId) {
  const resolved = path.resolve(USERS_DIR, `${userId}.json`);
  if (!resolved.startsWith(path.resolve(USERS_DIR) + path.sep)) {
    console.error('❌ 非法路径'); process.exit(1);
  }
  return resolved;
}

const args    = process.argv.slice(2);
const userId  = sanitizeId(args[0] || '8603011439');
const cronMsg = args.includes('--cron-message');

// ── 读取用户配置 ──────────────────────────────────────────────────────────────
let config = null;
const configPath = safeUserPath(userId);
if (fs.existsSync(configPath)) {
  config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
const mods = config?.modules?.morning || {};

// ── 模块定义（含开关 key） ─────────────────────────────────────────────────────
const ALL_MODULES = [
  {
    key: 'news',
    name: '早报',
    emoji: '📰',
    script: `${SKILLS_BASE}newstoady/scripts/morning-push.js`,
    args: ['--lang', 'zh'],
    searchRequired: true,
    group: 1,
  },
  {
    key: 'tech',
    name: '科技新闻',
    emoji: '💻',
    script: `${SKILLS_BASE}dailytech/scripts/morning-push.js`,
    args: [userId],
    searchRequired: true,
    group: 1,
  },
  {
    key: 'finance',
    name: '财经新闻',
    emoji: '💰',
    script: `${SKILLS_BASE}dailyfinance/scripts/morning-push.js`,
    args: [userId],
    searchRequired: true,
    group: 1,
  },
  {
    key: 'weather',
    name: '天气',
    emoji: '🌤️',
    script: `${SKILLS_BASE}weather-daily/scripts/morning-push.js`,
    args: [userId],
    searchRequired: true,
    group: 1,
  },
  {
    key: 'yunshi',
    name: '今日运势',
    emoji: '🔮',
    script: `${SKILLS_BASE}yunshi/scripts/daily-push.js`,
    args: ['--test', userId],
    searchRequired: false,
    group: 2,
  },
  {
    key: 'history',
    name: '历史上的今天',
    emoji: '📅',
    script: `${SKILLS_BASE}daily-history/scripts/morning-push.js`,
    args: [userId],
    searchRequired: true,
    group: 2,
  },
  {
    key: 'recipe',
    name: '今日菜谱',
    emoji: '🍳',
    script: `${SKILLS_BASE}daily-recipe/scripts/morning-push.js`,
    args: [userId],
    searchRequired: false,
    group: 2,
  },
  {
    key: 'quote',
    name: '每日名言',
    emoji: '💬',
    script: `${SKILLS_BASE}daily-quote/scripts/morning-push.js`,
    args: [userId],
    searchRequired: false,
    group: 3,
  },
  {
    key: 'mindful',
    name: '正念冥想',
    emoji: '🧘',
    script: `${SKILLS_BASE}daily-mindful/scripts/morning-push.js`,
    args: [userId],
    searchRequired: false,
    group: 3,
  },
  {
    key: 'fitness',
    name: '每日运动',
    emoji: '💪',
    script: `${SKILLS_BASE}daily-fitness/scripts/morning-push.js`,
    args: [userId],
    searchRequired: false,
    group: 3,
  },
  {
    key: 'english',
    name: '每日英语',
    emoji: '📚',
    script: `${SKILLS_BASE}english-daily/scripts/daily-push.js`,
    args: [userId],
    searchRequired: false,
    group: 3,
  },
];

// 如果有用户配置，按配置过滤；没有配置则全开
const MODULES = config
  ? ALL_MODULES.filter(m => mods[m.key] !== false)
  : ALL_MODULES;

// ── 执行脚本 ──────────────────────────────────────────────────────────────────
function runScript(scriptPath, scriptArgs) {
  try {
    const cmd = `node "${scriptPath}" ${scriptArgs.map(a => `"${a}"`).join(' ')}`;
    const output = execSync(cmd, { timeout: 10000, encoding: 'utf8', stdio: ['pipe','pipe','pipe'] });
    return { ok: true, prompt: output.trim() };
  } catch (err) {
    const stderr = err.stderr ? err.stderr.toString().trim() : '';
    return { ok: false, error: stderr || err.message };
  }
}

// ── 执行所有模块 ──────────────────────────────────────────────────────────────
const results = [];
for (const mod of MODULES) {
  const res = runScript(mod.script, mod.args);
  results.push({
    key:            mod.key,
    module:         mod.name,
    emoji:          mod.emoji,
    group:          mod.group,
    prompt:         res.ok ? res.prompt : null,
    searchRequired: mod.searchRequired,
    error:          res.ok ? undefined : res.error,
  });
}

// ── 输出模式 ──────────────────────────────────────────────────────────────────
if (cronMsg) {
  // --cron-message：输出供 cron payload 使用的 agent 指令
  const now = new Date();
  const WEEKDAYS = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
  const dateStr = `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日 ${WEEKDAYS[now.getDay()]}`;
  const name = config?.name || userId;

  const groups = { 1: [], 2: [], 3: [] };
  for (const r of results) {
    if (r.prompt) groups[r.group].push(`${r.emoji} ${r.module}\n${r.prompt}`);
    else if (r.error) groups[r.group].push(`${r.emoji} ${r.module}\n⚠️ 加载失败，跳过`);
  }

  const GROUP_LABELS = {
    1: '📡 今日资讯',
    2: '🌿 生活情报',
    3: '✨ 成长能量',
  };

  let msgs = [];
  for (const [g, items] of Object.entries(groups)) {
    if (items.length === 0) continue;
    msgs.push(`━━━━━ ${GROUP_LABELS[g]} ━━━━━\n${items.join('\n\n')}`);
  }

  console.log(`请执行每日早间简报（jiajiaoy-morning skill）。

步骤：
1. 运行脚本获取所有模块的执行指令：
\`\`\`bash
node ${__filename} ${userId}
\`\`\`

2. 将 JSON 中每个模块的 prompt 依次执行（searchRequired=true 的需要 WebSearch）

3. 按 group 字段分 3 组，每组汇总后各发送一条消息：
   - 消息1（group=1）：📰早报 + 💻科技 + 💰财经 + 🌤️天气
   - 消息2（group=2）：🔮运势 + 📅历史 + 🍳菜谱
   - 消息3（group=3）：💬名言 + 🧘正念 + 💪运动 + 📚英语

4. 每条消息头部加：🌅 早安 ${name}！${dateStr}

某个模块报错时跳过，不影响其余正常发送。`);
} else {
  // 默认：输出 JSON
  process.stdout.write(JSON.stringify(results, null, 2));
}


path: scripts/evening-push.js
content: #!/usr/bin/env node
/**
 * jiajiaoy-morning — 晚间明日运势 prompt 生成器
 *
 * 读取用户的八字档案，生成明日运势的分析指令
 *
 * 用法：
 *   node evening-push.js <userId>
 */

'use strict';

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

const USERS_DIR       = path.join(__dirname, '../data/users');
const YUNSHI_PROFILES = path.join(__dirname, '../../yunshi/data/profiles');

function sanitizeId(v) {
  if (typeof v !== 'string' || !/^[a-zA-Z0-9_@-]{1,128}$/.test(v)) {
    console.error('❌ 无效的 userId'); process.exit(1);
  }
  return v;
}

function safeUserPath(dir, userId) {
  const resolved = path.resolve(dir, `${userId}.json`);
  if (!resolved.startsWith(path.resolve(dir) + path.sep)) {
    console.error('❌ 非法路径'); process.exit(1);
  }
  return resolved;
}

const args = process.argv.slice(2);
if (!args[0]) {
  console.error('用法: node evening-push.js <userId>');
  process.exit(1);
}

const userId = sanitizeId(args[0]);

// 读取用户配置
const userConfigPath = safeUserPath(USERS_DIR, userId);
if (!fs.existsSync(userConfigPath)) {
  console.error(`❌ 用户 ${userId} 未注册。请先运行: node setup.js`);
  process.exit(1);
}
const config = JSON.parse(fs.readFileSync(userConfigPath, 'utf8'));

if (!config.modules?.evening?.yunshi_tomorrow) {
  console.error(`ℹ️ 用户 ${userId} 未开启晚间运势推送`);
  process.exit(0);
}

// 读取用户八字档案（来自 yunshi skill）
let bazi = null;
const yunshiProfilePath = safeUserPath(YUNSHI_PROFILES, userId);
if (fs.existsSync(yunshiProfilePath)) {
  const profile = JSON.parse(fs.readFileSync(yunshiProfilePath, 'utf8'));
  bazi = profile.bazi || null;
}

// 计算明日日期
const now = new Date();
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
const year  = tomorrow.getFullYear();
const month = tomorrow.getMonth() + 1;
const day   = tomorrow.getDate();
const WEEKDAYS = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
const weekday = WEEKDAYS[tomorrow.getDay()];
const dateStr = `${year}年${month}月${day}日 ${weekday}`;

const name = config.name || userId;

// 生成 prompt
if (bazi && bazi.year && bazi.month && bazi.day && bazi.hour) {
  console.log(`请为 ${name} 推算明天（${dateStr}）的详细运势。

用户八字：
- 年柱：${bazi.year}　月柱：${bazi.month}　日柱：${bazi.day}　时柱：${bazi.hour}
- 日主：${bazi.dayStem || '乙'}　生肖：${bazi.zodiac || '虎'}

请结合八字、紫微斗数和奇门遁甲，给出明日完整运程：

🔮 **明日运势 · ${dateStr}**

**综合运势**：[★★★★☆ 整体评分和一句话概括]

**四大运势**：
- 💼 事业：[分析+建议]
- 💰 财运：[分析+建议]
- 💕 感情：[分析+建议]
- 🏥 健康：[分析+建议]

**时辰吉凶**：[最佳行事时间2-3个]

**宜忌**：
- 宜：[3项]
- 忌：[3项]

**幸运元素**：颜色 [X] · 数字 [X] · 方位 [X]

**明日提醒**：[一句最重要的个性化建议]`);
} else {
  // 没有八字档案，输出通用提示
  console.log(`请根据今天（${new Date().toLocaleDateString('zh-CN')}）的干支历法，推算明天（${dateStr}）的通用运势。

🔮 **明日运势 · ${dateStr}**

请结合明日的天干地支、奇门遁甲等，给出：
- 综合运势评分（★）
- 事业、财运、感情、健康各方面简析
- 今日宜忌
- 幸运颜色/数字/方位
- 最佳行事时辰

直接输出运势内容，格式简洁清晰。`);
}


path: scripts/setup.js
content: #!/usr/bin/env node
/**
 * jiajiaoy-morning — 首次安装向导
 *
 * 输出一段结构化问卷 prompt，由 agent 向用户提问后
 * 将回答传回：node setup.js --save '<json>'
 *
 * 用法：
 *   node setup.js                        # 输出问卷 prompt（供 agent 展示给用户）
 *   node setup.js --save '<json>'        # 保存用户回答并输出 cron 指令
 *   node setup.js --show <userId>        # 查看用户当前配置
 */

'use strict';

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

const USERS_DIR   = path.join(__dirname, '../data/users');
const TEMPLATE    = path.join(USERS_DIR, 'template.json');
const SKILLS_BASE = path.join(__dirname, '../../');

// ── 依赖检查（与 build-prompts.js 保持一致）────────────────────────────────────
const REQUIRED_SKILLS = [
  'newstoady', 'dailytech', 'dailyfinance', 'weather-daily',
  'yunshi', 'daily-history', 'daily-recipe', 'daily-quote',
  'daily-mindful', 'daily-fitness', 'english-daily',
];
const missingSkills = REQUIRED_SKILLS.filter(slug =>
  !fs.existsSync(path.join(SKILLS_BASE, slug))
);
if (missingSkills.length > 0) {
  const skillsDir = path.resolve(SKILLS_BASE);
  console.error('❌ 缺少依赖 skill，请先安装：\n');
  console.error(`cd "${skillsDir}"\n`);
  missingSkills.forEach(slug => console.error(`clawhub install ${slug}`));
  console.error('\n安装完成后重新运行本脚本。');
  process.exit(1);
}

function sanitizeId(v) {
  if (typeof v !== 'string' || !/^[a-zA-Z0-9_@-]{1,128}$/.test(v)) {
    console.error('❌ 无效的 userId'); process.exit(1);
  }
  return v;
}

function safeUserPath(userId) {
  const resolved = path.resolve(USERS_DIR, `${userId}.json`);
  if (!resolved.startsWith(path.resolve(USERS_DIR) + path.sep)) {
    console.error('❌ 非法路径'); process.exit(1);
  }
  return resolved;
}

// ── 问卷 prompt（输出给 agent，让 agent 问用户）──────────────────────────────
function printSetupPrompt() {
  console.log(`请按以下步骤帮用户完成 jiajiaoy-morning 早晚简报设置，**逐项询问**，收集完后用 JSON 汇总：

---
**📋 jiajiaoy-morning 设置向导**

请依次询问用户以下问题（可以一次性列出让用户回答）：

**基本信息**
1. 你的名字是？（用于问候语）
2. 所在城市？（用于天气，默认：上海）
3. 早间简报接收渠道？（telegram / feishu，默认：telegram）
4. Telegram 用户ID 或 飞书 OpenID？
5. 晚间运势接收渠道？（同早间 / 单独设置，默认同早间）

**早间模块选择（08:00推送）**
6. 以下模块哪些要开启？（默认全开，可以说"全开"或列出不要的）
   - 📰 早报（今日新闻）
   - 💻 科技新闻
   - 💰 财经新闻
   - 🌤️ 天气
   - 🔮 今日运势
   - 📅 历史上的今天
   - 🍳 今日菜谱
   - 💬 每日名言
   - 🧘 正念冥想
   - 💪 每日运动
   - 📚 每日英语

**晚间模块选择（18:00推送）**
7. 是否开启 🔮 明日运势（18:00推送）？（默认：开启）

---
收集完所有回答后，将答案整理为以下 JSON 格式，然后运行：
\`\`\`bash
node <skills目录>/jiajiaoy-morning/scripts/setup.js --save '<JSON>'
\`\`\`

JSON 格式：
{
  "userId": "<telegram_id 或 feishu_openid>",
  "name": "<名字>",
  "city": "<城市>",
  "morningChannel": "telegram 或 feishu",
  "morningTo": "<接收ID>",
  "eveningChannel": "telegram 或 feishu",
  "eveningTo": "<接收ID>",
  "modules": {
    "news": true, "tech": true, "finance": true, "weather": true,
    "yunshi": true, "history": true, "recipe": true, "quote": true,
    "mindful": true, "fitness": true, "english": true,
    "yunshi_tomorrow": true
  }
}`);
}

// ── 保存配置并输出 cron 指令 ────────────────────────────────────────────────
function saveConfig(jsonStr) {
  let input;
  try {
    input = JSON.parse(jsonStr);
  } catch (e) {
    console.error('❌ JSON 解析失败：', e.message);
    process.exit(1);
  }

  const userId = sanitizeId(input.userId || '');
  const template = JSON.parse(fs.readFileSync(TEMPLATE, 'utf8'));
  const now = new Date().toISOString().split('T')[0];

  const config = {
    ...template,
    userId,
    name:     input.name     || userId,
    city:     input.city     || '上海',
    language: input.language || 'zh',
    channels: {
      morning: { channel: input.morningChannel || 'telegram', to: input.morningTo || userId },
      evening: { channel: input.eveningChannel || input.morningChannel || 'telegram', to: input.eveningTo || input.morningTo || userId },
    },
    modules: {
      morning: {
        news:    input.modules?.news    !== false,
        tech:    input.modules?.tech    !== false,
        finance: input.modules?.finance !== false,
        weather: input.modules?.weather !== false,
        yunshi:  input.modules?.yunshi  !== false,
        history: input.modules?.history !== false,
        recipe:  input.modules?.recipe  !== false,
        quote:   input.modules?.quote   !== false,
        mindful: input.modules?.mindful !== false,
        fitness: input.modules?.fitness !== false,
        english: input.modules?.english !== false,
      },
      evening: {
        yunshi_tomorrow: input.modules?.yunshi_tomorrow !== false,
      },
    },
    createdAt: now,
    updatedAt: now,
  };

  fs.mkdirSync(USERS_DIR, { recursive: true });
  fs.writeFileSync(safeUserPath(userId), JSON.stringify(config, null, 2));

  // 输出 cron 指令供 agent 执行
  const SKILL = __dirname;
  const morningCh  = config.channels.morning.channel;
  const morningTo  = config.channels.morning.to;
  const eveningCh  = config.channels.evening.channel;
  const eveningTo  = config.channels.evening.to;

  console.log(`✅ 用户配置已保存：${userId}（${config.name}）\n`);
  console.log(`📋 请执行以下命令注册定时任务：\n`);

  console.log(`# 早间简报 08:00`);
  console.log(`openclaw cron add \\`);
  console.log(`  --name "jiajiaoy-morning-${userId}" \\`);
  console.log(`  --agent cosmo \\`);
  console.log(`  --schedule "0 8 * * *" \\`);
  console.log(`  --tz "Asia/Shanghai" \\`);
  console.log(`  --timeout 480 \\`);
  console.log(`  --channel ${morningCh} \\`);
  console.log(`  --to ${morningTo} \\`);
  console.log(`  --message "$(node ${SKILL}/build-prompts.js ${userId} --cron-message)"\n`);

  if (config.modules.evening.yunshi_tomorrow) {
    console.log(`# 晚间明日运势 18:00`);
    console.log(`openclaw cron add \\`);
    console.log(`  --name "jiajiaoy-evening-yunshi-${userId}" \\`);
    console.log(`  --agent cosmo \\`);
    console.log(`  --schedule "0 18 * * *" \\`);
    console.log(`  --tz "Asia/Shanghai" \\`);
    console.log(`  --timeout 120 \\`);
    console.log(`  --channel ${eveningCh} \\`);
    console.log(`  --to ${eveningTo} \\`);
    console.log(`  --message "$(node ${SKILL}/evening-push.js ${userId})"\n`);
  }

  console.log(`\n已开启模块：`);
  const mods = config.modules.morning;
  const labels = { news:'📰早报', tech:'💻科技', finance:'💰财经', weather:'🌤️天气', yunshi:'🔮今日运势', history:'📅历史', recipe:'🍳菜谱', quote:'💬名言', mindful:'🧘正念', fitness:'💪运动', english:'📚英语' };
  Object.entries(mods).forEach(([k, v]) => {
    console.log(`  ${v ? '✓' : '✗'} ${labels[k] || k}`);
  });
  if (config.modules.evening.yunshi_tomorrow) {
    console.log(`  ✓ 🔮明日运势（18:00）`);
  }
}

// ── 查看用户配置 ──────────────────────────────────────────────────────────────
function showConfig(userId) {
  const f = safeUserPath(sanitizeId(userId));
  if (!fs.existsSync(f)) {
    console.log(`❌ 用户 ${userId} 未注册。运行 node setup.js 开始设置。`);
    process.exit(1);
  }
  const c = JSON.parse(fs.readFileSync(f, 'utf8'));
  console.log(`👤 ${c.name}（${c.userId}）`);
  console.log(`🏙️  城市：${c.city}`);
  console.log(`📡 早间：${c.channels.morning.channel} → ${c.channels.morning.to}`);
  console.log(`📡 晚间：${c.channels.evening.channel} → ${c.channels.evening.to}`);
  console.log(`\n已开启模块：`);
  const labels = { news:'📰早报', tech:'💻科技', finance:'💰财经', weather:'🌤️天气', yunshi:'🔮今日运势', history:'📅历史', recipe:'🍳菜谱', quote:'💬名言', mindful:'🧘正念', fitness:'💪运动', english:'📚英语' };
  Object.entries(c.modules.morning).forEach(([k, v]) => {
    console.log(`  ${v ? '✓' : '✗'} ${labels[k] || k}`);
  });
  console.log(`  ${c.modules.evening.yunshi_tomorrow ? '✓' : '✗'} 🔮明日运势（18:00）`);
}

// ── 入口 ──────────────────────────────────────────────────────────────────────
const args = process.argv.slice(2);
if (args[0] === '--save' && args[1]) {
  saveConfig(args[1]);
} else if (args[0] === '--show' && args[1]) {
  showConfig(args[1]);
} else {
  printSetupPrompt();
}


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