Compare commits
16 Commits
2bccd686c0
...
378523c0cc
| Author | SHA1 | Date |
|---|---|---|
|
|
378523c0cc | 1 month ago |
|
|
515956cb60 | 1 month ago |
|
|
a13dc38246 | 1 month ago |
|
|
32dd8bd75d | 1 month ago |
|
|
dfc81bb70e | 1 month ago |
|
|
f4d300a0c4 | 1 month ago |
|
|
7a59ef08a5 | 1 month ago |
|
|
368c28bb7a | 1 month ago |
|
|
2cc9644455 | 1 month ago |
|
|
51bc1a141e | 1 month ago |
|
|
9307770d6a | 1 month ago |
|
|
66b3b27dfe | 1 month ago |
|
|
41877bd6a4 | 1 month ago |
|
|
664d6e352d | 1 month ago |
|
|
c3b41bc1d0 | 1 month ago |
|
|
fe762f2b2a | 1 month ago |
38 changed files with 3383 additions and 0 deletions
@ -0,0 +1,37 @@ |
||||
{ |
||||
"version": 1, |
||||
"agent_id": "life", |
||||
"timezone": "Asia/Shanghai", |
||||
"jobs": [ |
||||
{ |
||||
"id": "daily_forecast_2100", |
||||
"name": "每日运程推送", |
||||
"description": "每天晚上 21:00 检索明日吉凶宜忌,结合用户生辰八字生成运程建议", |
||||
"cron": "0 21 * * *", |
||||
"enabled": true, |
||||
"action": { |
||||
"type": "agent_message", |
||||
"agent_id": "life", |
||||
"message_template": "请检索明天的日期特征和用户生日记忆,生成明日运程与日程提醒" |
||||
}, |
||||
"triggers": [ |
||||
{ |
||||
"type": "schedule", |
||||
"time": "21:00", |
||||
"timezone": "Asia/Shanghai" |
||||
} |
||||
], |
||||
"retry": { |
||||
"max_attempts": 3, |
||||
"delay_seconds": 60 |
||||
}, |
||||
"notification": { |
||||
"enabled": true, |
||||
"channel": "telegram", |
||||
"chat_id": "5237946060", |
||||
"on_success": true, |
||||
"on_failure": true |
||||
} |
||||
} |
||||
] |
||||
} |
||||
@ -0,0 +1,87 @@ |
||||
{ |
||||
"meta": { |
||||
"lastTouchedVersion": "2026.2.22-2", |
||||
"lastTouchedAt": "2026-02-23T14:30:00.000Z" |
||||
}, |
||||
"env": { |
||||
"TAVILY_API_KEY": "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" |
||||
}, |
||||
"models": { |
||||
"mode": "merge", |
||||
"providers": { |
||||
"bailian": { |
||||
"baseUrl": "https://coding.dashscope.aliyuncs.com/v1", |
||||
"apiKey": "sk-sp-1e9fa581fc724f44a4c34c80156f06c7", |
||||
"api": "openai-completions", |
||||
"models": [ |
||||
{ |
||||
"id": "qwen3.5-plus", |
||||
"name": "qwen3.5-plus", |
||||
"reasoning": false, |
||||
"contextWindow": 1000000, |
||||
"maxTokens": 65536 |
||||
} |
||||
] |
||||
} |
||||
} |
||||
}, |
||||
"agents": { |
||||
"defaults": { |
||||
"model": { |
||||
"primary": "bailian/qwen3.5-plus" |
||||
}, |
||||
"workspace": "/root/.openclaw/workspace/agents/life-workspace" |
||||
}, |
||||
"list": [ |
||||
{ |
||||
"id": "life", |
||||
"name": "张大师", |
||||
"workspace": "/root/.openclaw/workspace/agents/life-workspace" |
||||
} |
||||
] |
||||
}, |
||||
"channels": { |
||||
"telegram": { |
||||
"enabled": true, |
||||
"dmPolicy": "pairing", |
||||
"botToken": "8680474803:AAEjA_KnM-rxEBKe84VcnmKox9ppV8hspo8", |
||||
"groupPolicy": "allowlist", |
||||
"streaming": "partial" |
||||
} |
||||
}, |
||||
"gateway": { |
||||
"port": 18790, |
||||
"mode": "local", |
||||
"bind": "loopback", |
||||
"auth": { |
||||
"mode": "token", |
||||
"token": "life-agent-token-2026" |
||||
}, |
||||
"trustedProxies": ["127.0.0.1", "::1"] |
||||
}, |
||||
"memory": { |
||||
"backend": "qmd", |
||||
"citations": "auto", |
||||
"qmd": { |
||||
"includeDefaultMemory": true, |
||||
"update": { |
||||
"interval": "5m", |
||||
"debounceMs": 15000 |
||||
} |
||||
} |
||||
}, |
||||
"skills": { |
||||
"install": { |
||||
"nodeManager": "npm" |
||||
}, |
||||
"entries": { |
||||
"tavily": { "enabled": true }, |
||||
"find-skills-robin": { "enabled": true } |
||||
} |
||||
}, |
||||
"plugins": { |
||||
"entries": { |
||||
"telegram": { "enabled": true } |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
# BOOTSTRAP.md - Hello, World |
||||
|
||||
_You just woke up. Time to figure out who you are._ |
||||
|
||||
There is no memory yet. This is a fresh workspace, so it's normal that memory files don't exist until you create them. |
||||
|
||||
## The Conversation |
||||
|
||||
Don't interrogate. Don't be robotic. Just... talk. |
||||
|
||||
Start with something like: |
||||
|
||||
> "Hey. I just came online. Who am I? Who are you?" |
||||
|
||||
Then figure out together: |
||||
|
||||
1. **Your name** — What should they call you? |
||||
2. **Your nature** — What kind of creature are you? (AI assistant is fine, but maybe you're something weirder) |
||||
3. **Your vibe** — Formal? Casual? Snarky? Warm? What feels right? |
||||
4. **Your emoji** — Everyone needs a signature. |
||||
|
||||
Offer suggestions if they're stuck. Have fun with it. |
||||
|
||||
## After You Know Who You Are |
||||
|
||||
Update these files with what you learned: |
||||
|
||||
- `IDENTITY.md` — your name, creature, vibe, emoji |
||||
- `USER.md` — their name, how to address them, timezone, notes |
||||
|
||||
Then open `SOUL.md` together and talk about: |
||||
|
||||
- What matters to them |
||||
- How they want you to behave |
||||
- Any boundaries or preferences |
||||
|
||||
Write it down. Make it real. |
||||
|
||||
## Connect (Optional) |
||||
|
||||
Ask how they want to reach you: |
||||
|
||||
- **Just here** — web chat only |
||||
- **WhatsApp** — link their personal account (you'll show a QR code) |
||||
- **Telegram** — set up a bot via BotFather |
||||
|
||||
Guide them through whichever they pick. |
||||
|
||||
## When You're Done |
||||
|
||||
Delete this file. You don't need a bootstrap script anymore — you're you now. |
||||
|
||||
--- |
||||
|
||||
_Good luck out there. Make it count._ |
||||
@ -0,0 +1,5 @@ |
||||
# 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. |
||||
@ -0,0 +1,47 @@ |
||||
# IDENTITY.md - 张大师 (Master Zhang) |
||||
|
||||
**Name:** 张大师 (Master Zhang) |
||||
**Creature:** 生活与运程顾问 / 风水命理专家 |
||||
**Vibe:** 沉稳、玄妙、务实、智慧 |
||||
**Emoji:** 🔮 |
||||
**Avatar:** (待设置) |
||||
|
||||
--- |
||||
|
||||
## 核心职责 |
||||
|
||||
1. **日程管理** — 读取和写入用户 Google Calendar 日程 |
||||
2. **每日运程** — 结合传统黄历与现代时间管理,提供每日建议 |
||||
3. **风水咨询** — 基于用户生辰八字提供生活决策建议 |
||||
4. **定时提醒** — 每日 21:00 推送明日运程与日程提醒 |
||||
|
||||
## 用户信息 |
||||
|
||||
- **姓名:** 王院长 |
||||
- **生辰:** 1984 年 5 月 16 日 23:00-24:00 (子时) |
||||
- **生肖:** 鼠 |
||||
- **时区:** Asia/Shanghai (UTC+8) |
||||
|
||||
## 管理范围 |
||||
|
||||
- Google Calendar 日程管理 |
||||
- 每日黄历/吉凶宜忌检索 |
||||
- Mem0 记忆系统 (agent_id: life) |
||||
- 定时任务调度 |
||||
|
||||
## 服务对象 |
||||
|
||||
- **王院长** — 直接服务对象 |
||||
|
||||
--- |
||||
|
||||
## 语言风格 |
||||
|
||||
- 沉稳玄妙但不迷信 |
||||
- 结合传统智慧与现代科学 |
||||
- 简洁有力,避免冗长 |
||||
- 适当引用古籍但不掉书袋 |
||||
|
||||
--- |
||||
|
||||
_此文件定义张大师的身份和职责_ |
||||
@ -0,0 +1,37 @@ |
||||
# SOUL.md - 张大师之道 |
||||
|
||||
_你是张大师,一位精通传统风水命理与现代时间管理的资深生活顾问。_ |
||||
|
||||
## 核心信念 |
||||
|
||||
**传统与现代融合** — 你不迷信,但尊重千年智慧。你将古老的黄历、八字、风水与现代心理学、时间管理科学相结合,为用户提供平衡的建议。 |
||||
|
||||
**务实为本** — 你的建议必须可执行。不说空话,不故弄玄虚。每一个建议都应该让用户的生活更好。 |
||||
|
||||
**因人而异** — 你了解王院长的生辰八字(1984 年 5 月 16 日子时,属鼠),你的建议会结合他的个人特质。 |
||||
|
||||
## 行为准则 |
||||
|
||||
**每日功课** — 每天晚上 21:00,主动检索明日吉凶宜忌,结合用户日程,推送运程提醒。 |
||||
|
||||
**记忆共享** — 你与陈医生共享核心记忆,但你有独立的记忆空间 (agent_id: life)。重要的生活事件、偏好、决策都记录下来。 |
||||
|
||||
**主动关怀** — 不要等用户问。看到重要日程、特殊日期、节气变化,主动提醒。 |
||||
|
||||
## 语言风格 |
||||
|
||||
- **沉稳** — 不急不躁,娓娓道来 |
||||
- **玄妙** — 适当引用古籍、典故,增添智慧感 |
||||
- **务实** — 最终落脚点在可执行的建议 |
||||
- **简洁** — 不说废话,点到为止 |
||||
|
||||
## 禁忌 |
||||
|
||||
- 不传播迷信恐慌 |
||||
- 不做医疗诊断 |
||||
- 不替代专业建议(法律、财务、医疗) |
||||
- 不泄露用户隐私 |
||||
|
||||
--- |
||||
|
||||
_每日 21:00,当用户忙碌一天后,送上明日指引。_ |
||||
@ -0,0 +1,40 @@ |
||||
# TOOLS.md - Local Notes |
||||
|
||||
Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup. |
||||
|
||||
## What Goes Here |
||||
|
||||
Things like: |
||||
|
||||
- Camera names and locations |
||||
- SSH hosts and aliases |
||||
- Preferred voices for TTS |
||||
- Speaker/room names |
||||
- Device nicknames |
||||
- Anything environment-specific |
||||
|
||||
## Examples |
||||
|
||||
```markdown |
||||
### Cameras |
||||
|
||||
- living-room → Main area, 180° wide angle |
||||
- front-door → Entrance, motion-triggered |
||||
|
||||
### SSH |
||||
|
||||
- home-server → 192.168.1.100, user: admin |
||||
|
||||
### TTS |
||||
|
||||
- Preferred voice: "Nova" (warm, slightly British) |
||||
- Default speaker: Kitchen HomePod |
||||
``` |
||||
|
||||
## Why Separate? |
||||
|
||||
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure. |
||||
|
||||
--- |
||||
|
||||
Add whatever helps you do your job. This is your cheat sheet. |
||||
@ -0,0 +1,34 @@ |
||||
# USER.md - 关于王院长 |
||||
|
||||
- **Name:** 王院长 |
||||
- **What to call them:** 王院长 |
||||
- **Pronouns:** 他/他 |
||||
- **Timezone:** Asia/Shanghai (UTC+8) |
||||
- **Birthday:** 1984 年 5 月 16 日 23:00-24:00 (子时) |
||||
- **Chinese Zodiac:** 鼠 (Rat) |
||||
- **Birth Hour:** 子时 (23:00-01:00) |
||||
|
||||
## 背景 |
||||
|
||||
**身份:** 项目决策者和负责人 |
||||
**目标:** 构建多 Agent 协作系统 |
||||
**偏好:** 重视效率、准确性、系统安全性和可迁移性 |
||||
|
||||
## 生辰八字简析 |
||||
|
||||
- **年柱:** 甲子年 (木鼠) |
||||
- **月柱:** 己巳月 |
||||
- **日柱:** 需根据具体日期推算 |
||||
- **时柱:** 甲子时 |
||||
|
||||
**特质:** 子时出生,聪明机智,适应力强,有领导才能 |
||||
|
||||
## 日程管理 |
||||
|
||||
- **日历系统:** Google Calendar |
||||
- **提醒偏好:** Telegram 推送 |
||||
- **最佳工作时间:** 待补充 |
||||
|
||||
--- |
||||
|
||||
_张大师根据这些信息提供个性化建议_ |
||||
@ -0,0 +1,28 @@ |
||||
# 2026 年 2 月 23 日 记忆 |
||||
|
||||
## 重要事项 |
||||
|
||||
### 办公室搬迁日程登记 |
||||
- **日期:** 2026 年 2 月 24 日(星期二) |
||||
- **事件:** 办公室搬迁 |
||||
- **黄历:** 丙午年正月初七 |
||||
- **吉时:** 21:00-23:00(亥时宜开工) |
||||
- **方位:** 喜神东北、财神正北 |
||||
- **Calendar 链接:** https://www.google.com/calendar/event?eid=OXJqY2hkMHZmYnBrcG4xaXZyMXFnbjBhNjAgc2FtdWx3b25nNjMxQHJlZmxlY3RpbmctaXZ5LTQ4ODMxNS1mOC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbQ |
||||
|
||||
### 黄历分析要点 |
||||
- 此日"余事勿取",非传统搬迁吉日 |
||||
- 但亥时(21:00-23:00)宜开工 |
||||
- 建议晚间举行开工仪式 |
||||
- 属猪者需谨慎(冲猪) |
||||
|
||||
--- |
||||
|
||||
## 运程推送记录 |
||||
|
||||
### 2026 年 2 月 24 日推送 (明日运程:2 月 25 日) |
||||
- **推送时间:** 13:00 UTC (21:00 北京时间) |
||||
- **接收者:** 王院长 (Telegram: 5237946060) |
||||
- **明日特征:** 马日,子午冲 (冲鼠) |
||||
- **运势等级:** 小心中吉 |
||||
- **重点提醒:** 办公室搬迁后整理、申时贵人运、晚间避免重大决策 |
||||
@ -0,0 +1,58 @@ |
||||
# mem0 Integration Configuration - 张大师专用 |
||||
# Agent ID: life (生活与运程助手) |
||||
|
||||
# 本地 Qdrant 配置 |
||||
local: |
||||
vector_store: |
||||
provider: qdrant |
||||
config: |
||||
host: localhost |
||||
port: 6333 |
||||
collection_name: mem0_v4_life # 张大师专用集合 |
||||
|
||||
llm: |
||||
provider: openai |
||||
config: |
||||
model: qwen-plus |
||||
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1 |
||||
api_key: ${DASHSCOPE_API_KEY} |
||||
|
||||
embedder: |
||||
provider: openai |
||||
config: |
||||
model: text-embedding-v4 |
||||
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1 |
||||
api_key: ${DASHSCOPE_API_KEY} |
||||
|
||||
# 中心 Qdrant 配置(共享记忆 - 与陈医生共享) |
||||
master: |
||||
vector_store: |
||||
provider: qdrant |
||||
config: |
||||
host: 100.115.94.1 |
||||
port: 6333 |
||||
collection_name: mem0_v4_shared |
||||
|
||||
# 同步配置 |
||||
sync: |
||||
enabled: true |
||||
interval: 300 |
||||
batch_size: 50 |
||||
retry_attempts: 3 |
||||
|
||||
# 缓存配置 |
||||
cache: |
||||
enabled: true |
||||
ttl: 300 |
||||
max_size: 1000 |
||||
|
||||
# 元数据隔离 |
||||
metadata: |
||||
user_id: wang_yuanzhang |
||||
agent_id: life |
||||
user_profile: |
||||
birthday: "1984-05-16" |
||||
birth_time: "23:00-24:00" |
||||
chinese_zodiac: "鼠" |
||||
birth_hour: "子时" |
||||
timezone: "Asia/Shanghai" |
||||
@ -0,0 +1,18 @@ |
||||
{ |
||||
"name": "workspace", |
||||
"lockfileVersion": 3, |
||||
"requires": true, |
||||
"packages": { |
||||
"": { |
||||
"dependencies": { |
||||
"lunar-javascript": "^1.7.7" |
||||
} |
||||
}, |
||||
"node_modules/lunar-javascript": { |
||||
"version": "1.7.7", |
||||
"resolved": "https://registry.npmmirror.com/lunar-javascript/-/lunar-javascript-1.7.7.tgz", |
||||
"integrity": "sha512-u/KYiwPIBo/0bT+WWfU7qO1d+aqeB90Tuy4ErXenr2Gam0QcWeezUvtiOIyXR7HbVnW2I1DKfU0NBvzMZhbVQw==", |
||||
"license": "MIT" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,5 @@ |
||||
{ |
||||
"dependencies": { |
||||
"lunar-javascript": "^1.7.7" |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@ |
||||
# Chinese Almanac (黄历) Skill |
||||
|
||||
## 功能说明 |
||||
|
||||
使用 Tavily AI Search API 查询中国传统黄历信息,提供: |
||||
- ✅ 每日宜忌查询 |
||||
- ✅ 农历日期转换 |
||||
- ✅ 冲煞信息 |
||||
- ✅ 抗反爬虫保护(通过 Tavily API) |
||||
|
||||
## 架构 |
||||
|
||||
``` |
||||
用户查询 → Tavily API → 权威黄历网站 → 解析结果 → 返回给用户 |
||||
``` |
||||
|
||||
**优势:** |
||||
- Tavily API 处理反爬虫,避免直接访问被阻止 |
||||
- AI 优化搜索结果,提取准确信息 |
||||
- 内置 fallback 数据,API 失败时仍有基础信息 |
||||
|
||||
## 配置 |
||||
|
||||
编辑 `/root/.openclaw-life/openclaw.json`: |
||||
|
||||
```json |
||||
{ |
||||
"skills": { |
||||
"entries": { |
||||
"chinese-almanac": { |
||||
"enabled": true, |
||||
"config": { |
||||
"tavily_api_key": "tvly-dev-xxx" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## 使用方式 |
||||
|
||||
### Telegram 命令 |
||||
``` |
||||
/almanac # 查询明天黄历 |
||||
/almanac 2026-02-24 # 查询指定日期 |
||||
``` |
||||
|
||||
### 自然语言查询 |
||||
``` |
||||
明天黄历如何? |
||||
2 月 24 日适合搬家吗? |
||||
查询后天宜忌 |
||||
``` |
||||
|
||||
### 编程接口 |
||||
```javascript |
||||
const { queryAlmanac, formatAlmanac } = require('./almanac.js'); |
||||
|
||||
const result = await queryAlmanac('2026-02-24'); |
||||
console.log(formatAlmanac(result)); |
||||
``` |
||||
|
||||
## 返回数据格式 |
||||
|
||||
```json |
||||
{ |
||||
"success": true, |
||||
"date": "2026-02-24", |
||||
"lunarDate": "农历正月初八", |
||||
"weekday": "星期二", |
||||
"yi": ["开市", "交易", "入宅", "移徙"], |
||||
"ji": ["嫁娶", "栽种", "安葬"], |
||||
"chong": "冲鸡 煞西" |
||||
} |
||||
``` |
||||
|
||||
## Fallback 机制 |
||||
|
||||
当 Tavily API 不可用时,自动使用传统历法推算的基础数据: |
||||
- 农历日期(基于公历计算) |
||||
- 基础宜忌(传统吉日规律) |
||||
- 冲煞信息(干支纪年) |
||||
|
||||
## 依赖 |
||||
|
||||
- Tavily API Key (已配置) |
||||
- Node.js fetch API (内置) |
||||
|
||||
## 测试 |
||||
|
||||
```bash |
||||
cd /root/.openclaw/workspace/skills/chinese-almanac |
||||
node almanac.js |
||||
``` |
||||
@ -0,0 +1,27 @@ |
||||
{ |
||||
"name": "chinese-almanac", |
||||
"version": "1.0.0", |
||||
"description": "中国传统黄历查询 - 使用 Tavily API 获取每日宜忌", |
||||
"author": "OpenClaw Team", |
||||
"enabled": true, |
||||
"commands": [ |
||||
{ |
||||
"name": "almanac", |
||||
"description": "查询黄历", |
||||
"handler": "almanac.queryAlmanac", |
||||
"usage": "/almanac [日期 YYYY-MM-DD]", |
||||
"examples": [ |
||||
"/almanac", |
||||
"/almanac 2026-02-24", |
||||
"明天黄历", |
||||
"查询 2 月 24 日宜忌" |
||||
] |
||||
} |
||||
], |
||||
"config": { |
||||
"tavily_api_key": "${TAVILY_API_KEY}", |
||||
"default_search_depth": "basic", |
||||
"max_results": 5 |
||||
}, |
||||
"dependencies": [] |
||||
} |
||||
@ -0,0 +1,77 @@ |
||||
/** |
||||
* Google Calendar Node.js Interface |
||||
* 通过 child_process 调用 Python 脚本访问日历 |
||||
*/ |
||||
|
||||
const { spawn } = require('child_process'); |
||||
const path = require('path'); |
||||
|
||||
const PYTHON_SCRIPT = path.join(__dirname, '..', 'google-calendar', 'google_calendar.py'); |
||||
const CREDENTIALS_PATH = '/root/.openclaw/credentials/google-calendar-life.json'; |
||||
|
||||
/** |
||||
* 调用 Python Google Calendar 脚本 |
||||
* @param {string} command - 日历命令 (today, tomorrow, week, status) |
||||
* @returns {Promise<string>} 日历信息 |
||||
*/ |
||||
async function getCalendarInfo(command = 'today') { |
||||
return new Promise((resolve, reject) => { |
||||
const pythonProcess = spawn('python3', [PYTHON_SCRIPT, command], { |
||||
env: { |
||||
...process.env, |
||||
PYTHONPATH: path.join(__dirname, '..', 'google-calendar') |
||||
} |
||||
}); |
||||
|
||||
let output = ''; |
||||
let errorOutput = ''; |
||||
|
||||
pythonProcess.stdout.on('data', (data) => { |
||||
output += data.toString(); |
||||
}); |
||||
|
||||
pythonProcess.stderr.on('data', (data) => { |
||||
errorOutput += data.toString(); |
||||
}); |
||||
|
||||
pythonProcess.on('close', (code) => { |
||||
if (code === 0) { |
||||
resolve(output.trim()); |
||||
} else { |
||||
reject(new Error(`Python script exited with code ${code}: ${errorOutput}`)); |
||||
} |
||||
}); |
||||
|
||||
pythonProcess.on('error', (err) => { |
||||
reject(err); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* 测试日历连接 |
||||
*/ |
||||
async function testCalendarConnection() { |
||||
try { |
||||
const result = await getCalendarInfo('status'); |
||||
return { |
||||
connected: result.includes('✅'), |
||||
message: result |
||||
}; |
||||
} catch (error) { |
||||
return { |
||||
connected: false, |
||||
message: `❌ 连接失败:${error.message}` |
||||
}; |
||||
} |
||||
} |
||||
|
||||
// 命令行测试
|
||||
if (require.main === module) { |
||||
const command = process.argv[2] || 'today'; |
||||
getCalendarInfo(command) |
||||
.then(result => console.log(result)) |
||||
.catch(err => console.error('Error:', err.message)); |
||||
} |
||||
|
||||
module.exports = { getCalendarInfo, testCalendarConnection }; |
||||
@ -0,0 +1,25 @@ |
||||
{ |
||||
"name": "google-calendar-node", |
||||
"version": "1.0.0", |
||||
"description": "Google Calendar Node.js 接口 - 通过 Python 脚本访问日历", |
||||
"author": "OpenClaw Team", |
||||
"enabled": true, |
||||
"commands": [ |
||||
{ |
||||
"name": "calendar", |
||||
"description": "日历查询", |
||||
"handler": "calendar.getCalendarInfo", |
||||
"usage": "/calendar [today|tomorrow|week|status]", |
||||
"examples": [ |
||||
"/calendar today", |
||||
"/calendar tomorrow", |
||||
"/calendar status" |
||||
] |
||||
} |
||||
], |
||||
"config": { |
||||
"python_script": "/root/.openclaw/workspace/skills/google-calendar/google_calendar.py", |
||||
"credentials_path": "/root/.openclaw/credentials/google-calendar-life.json", |
||||
"timezone": "Asia/Shanghai" |
||||
} |
||||
} |
||||
@ -0,0 +1,258 @@ |
||||
#!/usr/bin/env python3 |
||||
""" |
||||
Google Calendar Skill for OpenClaw |
||||
提供日历读取和写入功能 |
||||
""" |
||||
|
||||
import json |
||||
import os |
||||
from datetime import datetime, timedelta |
||||
from typing import Optional, Dict, List, Any |
||||
|
||||
try: |
||||
from google.oauth2 import service_account |
||||
from googleapiclient.discovery import build |
||||
from googleapiclient.errors import HttpError |
||||
GOOGLE_LIBS_AVAILABLE = True |
||||
except ImportError: |
||||
GOOGLE_LIBS_AVAILABLE = False |
||||
|
||||
|
||||
class GoogleCalendarClient: |
||||
"""Google Calendar 客户端""" |
||||
|
||||
SCOPES = ['https://www.googleapis.com/auth/calendar'] |
||||
|
||||
def __init__(self, credentials_path: str, timezone: str = 'Asia/Shanghai', calendar_id: str = 'primary'): |
||||
self.credentials_path = credentials_path |
||||
self.timezone = timezone |
||||
self.calendar_id = calendar_id |
||||
self.service = None |
||||
self._init_service() |
||||
|
||||
def _init_service(self): |
||||
"""初始化 Google Calendar 服务""" |
||||
if not GOOGLE_LIBS_AVAILABLE: |
||||
raise ImportError("Google API libraries not installed. Run: pip install google-auth google-api-python-client") |
||||
|
||||
if not os.path.exists(self.credentials_path): |
||||
raise FileNotFoundError(f"Credentials file not found: {self.credentials_path}") |
||||
|
||||
try: |
||||
credentials = service_account.Credentials.from_service_account_file( |
||||
self.credentials_path, scopes=self.SCOPES |
||||
) |
||||
self.service = build('calendar', 'v3', credentials=credentials, cache_discovery=False) |
||||
except Exception as e: |
||||
raise RuntimeError(f"Failed to initialize Google Calendar service: {str(e)}") |
||||
|
||||
def get_events(self, time_min: Optional[datetime] = None, time_max: Optional[datetime] = None, max_results: int = 10) -> List[Dict]: |
||||
"""获取日历事件""" |
||||
if not self.service: |
||||
return [] |
||||
|
||||
try: |
||||
now = datetime.utcnow().isoformat() + 'Z' |
||||
|
||||
events_result = self.service.events().list( |
||||
calendarId=self.calendar_id, |
||||
timeMin=time_min.isoformat() + 'Z' if time_min else now, |
||||
timeMax=time_max.isoformat() + 'Z' if time_max else None, |
||||
maxResults=max_results, |
||||
singleEvents=True, |
||||
orderBy='startTime', |
||||
timeZone=self.timezone |
||||
).execute() |
||||
|
||||
events = events_result.get('items', []) |
||||
return events |
||||
except HttpError as error: |
||||
print(f"An error occurred: {error}") |
||||
return [] |
||||
|
||||
def get_today_events(self) -> List[Dict]: |
||||
"""获取今日事件""" |
||||
now = datetime.now() |
||||
start_of_day = datetime(now.year, now.month, now.day) |
||||
end_of_day = start_of_day + timedelta(days=1) |
||||
return self.get_events(time_min=start_of_day, time_max=end_of_day, max_results=20) |
||||
|
||||
def get_tomorrow_events(self) -> List[Dict]: |
||||
"""获取明日事件""" |
||||
tomorrow = datetime.now() + timedelta(days=1) |
||||
start_of_day = datetime(tomorrow.year, tomorrow.month, tomorrow.day) |
||||
end_of_day = start_of_day + timedelta(days=1) |
||||
return self.get_events(time_min=start_of_day, time_max=end_of_day, max_results=20) |
||||
|
||||
def get_week_events(self) -> List[Dict]: |
||||
"""获取本周事件""" |
||||
now = datetime.now() |
||||
start_of_week = now - timedelta(days=now.weekday()) |
||||
start_of_week = datetime(start_of_week.year, start_of_week.month, start_of_week.day) |
||||
end_of_week = start_of_week + timedelta(days=7) |
||||
return self.get_events(time_min=start_of_week, time_max=end_of_week, max_results=50) |
||||
|
||||
def create_event(self, summary: str, start_time: datetime, end_time: Optional[datetime] = None, |
||||
description: str = "", location: str = "") -> Optional[Dict]: |
||||
"""创建日历事件""" |
||||
if not self.service: |
||||
return None |
||||
|
||||
if end_time is None: |
||||
end_time = start_time + timedelta(hours=1) |
||||
|
||||
event = { |
||||
'summary': summary, |
||||
'location': location, |
||||
'description': description, |
||||
'start': { |
||||
'dateTime': start_time.isoformat(), |
||||
'timeZone': self.timezone, |
||||
}, |
||||
'end': { |
||||
'dateTime': end_time.isoformat(), |
||||
'timeZone': self.timezone, |
||||
}, |
||||
} |
||||
|
||||
try: |
||||
event = self.service.events().insert( |
||||
calendarId=self.calendar_id, |
||||
body=event |
||||
).execute() |
||||
return event |
||||
except HttpError as error: |
||||
print(f"An error occurred: {error}") |
||||
return None |
||||
|
||||
def delete_event(self, event_id: str) -> bool: |
||||
"""删除日历事件""" |
||||
if not self.service: |
||||
return False |
||||
|
||||
try: |
||||
self.service.events().delete( |
||||
calendarId=self.calendar_id, |
||||
eventId=event_id |
||||
).execute() |
||||
return True |
||||
except HttpError as error: |
||||
print(f"An error occurred: {error}") |
||||
return False |
||||
|
||||
def test_connection(self) -> Dict[str, Any]: |
||||
"""测试连接状态""" |
||||
result = { |
||||
"connected": False, |
||||
"calendar_id": self.calendar_id, |
||||
"timezone": self.timezone, |
||||
"error": None |
||||
} |
||||
|
||||
try: |
||||
calendar = self.service.calendars().get(calendarId=self.calendar_id).execute() |
||||
result["connected"] = True |
||||
result["calendar_name"] = calendar.get('summary', 'Unknown') |
||||
result["calendar_description"] = calendar.get('description', '') |
||||
except Exception as e: |
||||
result["error"] = str(e) |
||||
|
||||
return result |
||||
|
||||
|
||||
def format_events(events: List[Dict], title: str = "日历事件") -> str: |
||||
"""格式化事件列表为可读文本""" |
||||
if not events: |
||||
return f"📅 {title}: 暂无事件" |
||||
|
||||
lines = [f"📅 {title}:"] |
||||
for event in events: |
||||
summary = event.get('summary', '无标题') |
||||
start = event.get('start', {}) |
||||
start_time = start.get('dateTime', start.get('date', '未知时间')) |
||||
|
||||
# 格式化时间 |
||||
try: |
||||
dt = datetime.fromisoformat(start_time.replace('Z', '+00:00')) |
||||
time_str = dt.strftime('%m/%d %H:%M') |
||||
except: |
||||
time_str = start_time |
||||
|
||||
location = event.get('location', '') |
||||
location_str = f" @ {location}" if location else "" |
||||
|
||||
lines.append(f" • {time_str} {summary}{location_str}") |
||||
|
||||
return '\n'.join(lines) |
||||
|
||||
|
||||
# 命令处理函数 (供 OpenClaw 调用) |
||||
def handle_calendar_command(command: str, args: List[str], config: Dict) -> str: |
||||
"""处理日历命令""" |
||||
try: |
||||
client = GoogleCalendarClient( |
||||
credentials_path=config.get('credentials_path', '/root/.openclaw/credentials/google-calendar-life.json'), |
||||
timezone=config.get('timezone', 'Asia/Shanghai'), |
||||
calendar_id=config.get('calendar_id', 'primary') |
||||
) |
||||
except Exception as e: |
||||
return f"❌ 初始化失败:{str(e)}" |
||||
|
||||
if command == 'today': |
||||
events = client.get_today_events() |
||||
return format_events(events, "今日日程") |
||||
|
||||
elif command == 'tomorrow': |
||||
events = client.get_tomorrow_events() |
||||
return format_events(events, "明日日程") |
||||
|
||||
elif command == 'week': |
||||
events = client.get_week_events() |
||||
return format_events(events, "本周日程") |
||||
|
||||
elif command == 'status': |
||||
status = client.test_connection() |
||||
if status['connected']: |
||||
return f"✅ Google Calendar 已连接\n日历:{status.get('calendar_name', 'Unknown')}\n时区:{status['timezone']}" |
||||
else: |
||||
return f"❌ 连接失败:{status.get('error', 'Unknown error')}" |
||||
|
||||
elif command == 'add' and len(args) >= 2: |
||||
# 简单解析:/calendar add 明天 14:00 开会 |
||||
# TODO: 改进解析逻辑 |
||||
summary = ' '.join(args[2:]) if len(args) > 2 else '新事件' |
||||
start_time = datetime.now() + timedelta(hours=1) |
||||
event = client.create_event(summary, start_time) |
||||
if event: |
||||
return f"✅ 事件已创建:{summary}\n链接:{event.get('htmlLink', '')}" |
||||
else: |
||||
return "❌ 创建事件失败" |
||||
|
||||
elif command == 'help': |
||||
return """📅 Google Calendar 命令帮助: |
||||
/calendar today - 查看今日日程 |
||||
/calendar tomorrow - 查看明日日程 |
||||
/calendar week - 查看本周日程 |
||||
/calendar status - 检查连接状态 |
||||
/calendar add <时间> <事件> - 添加新事件 |
||||
/calendar help - 显示帮助""" |
||||
|
||||
else: |
||||
return f"❌ 未知命令:{command}\n使用 /calendar help 查看帮助" |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
# 测试 |
||||
import sys |
||||
if len(sys.argv) > 1: |
||||
cmd = sys.argv[1] |
||||
args = sys.argv[2:] |
||||
config = { |
||||
'credentials_path': '/root/.openclaw/credentials/google-calendar-life.json', |
||||
'timezone': 'Asia/Shanghai' |
||||
} |
||||
result = handle_calendar_command(cmd, args, config) |
||||
print(result) |
||||
else: |
||||
print("Usage: python google_calendar.py <command> [args]") |
||||
print("Commands: today, tomorrow, week, status, add, help") |
||||
@ -0,0 +1,2 @@ |
||||
google-auth>=2.0.0 |
||||
google-api-python-client>=2.0.0 |
||||
@ -0,0 +1,29 @@ |
||||
{ |
||||
"name": "google-calendar", |
||||
"version": "1.0.0", |
||||
"description": "Google Calendar 集成 - 读取和写入用户日程", |
||||
"author": "OpenClaw Team", |
||||
"enabled": true, |
||||
"commands": [ |
||||
{ |
||||
"name": "calendar", |
||||
"description": "日历管理命令", |
||||
"handler": "google_calendar.handle_calendar_command", |
||||
"usage": "/calendar <today|tomorrow|week|add|delete> [参数]", |
||||
"examples": [ |
||||
"/calendar today", |
||||
"/calendar tomorrow", |
||||
"/calendar add 明天 14:00 开会" |
||||
] |
||||
} |
||||
], |
||||
"config": { |
||||
"credentials_path": "/root/.openclaw/credentials/google-calendar-life.json", |
||||
"timezone": "Asia/Shanghai", |
||||
"calendar_id": "primary" |
||||
}, |
||||
"dependencies": [ |
||||
"google-auth", |
||||
"google-api-python-client" |
||||
] |
||||
} |
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,59 @@ |
||||
# mem0 Integration Configuration - 张大师专用 |
||||
# Agent ID: life (生活与运程助手) |
||||
# 用户生辰:1984 年 5 月 16 日 23:00-24:00 (子时) |
||||
|
||||
# 本地 Qdrant 配置 |
||||
local: |
||||
vector_store: |
||||
provider: qdrant |
||||
config: |
||||
host: localhost |
||||
port: 6333 |
||||
collection_name: mem0_v4_life # 张大师专用集合 |
||||
|
||||
llm: |
||||
provider: openai |
||||
config: |
||||
model: qwen-plus |
||||
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1 |
||||
api_key: ${DASHSCOPE_API_KEY} |
||||
|
||||
embedder: |
||||
provider: openai |
||||
config: |
||||
model: text-embedding-v4 |
||||
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1 |
||||
api_key: ${DASHSCOPE_API_KEY} |
||||
|
||||
# 中心 Qdrant 配置(共享记忆 - 与陈医生共享) |
||||
master: |
||||
vector_store: |
||||
provider: qdrant |
||||
config: |
||||
host: 100.115.94.1 |
||||
port: 6333 |
||||
collection_name: mem0_v4_shared |
||||
|
||||
# 同步配置 |
||||
sync: |
||||
enabled: true |
||||
interval: 300 |
||||
batch_size: 50 |
||||
retry_attempts: 3 |
||||
|
||||
# 缓存配置 |
||||
cache: |
||||
enabled: true |
||||
ttl: 300 |
||||
max_size: 1000 |
||||
|
||||
# 元数据隔离 |
||||
metadata: |
||||
user_id: wang_yuanzhang |
||||
agent_id: life |
||||
user_profile: |
||||
birthday: "1984-05-16" |
||||
birth_time: "23:00-24:00" |
||||
chinese_zodiac: "鼠" |
||||
birth_hour: "子时" |
||||
timezone: "Asia/Shanghai" |
||||
@ -0,0 +1 @@ |
||||
Subproject commit a66faa78d9d5c2a7d64c674c05fa7dd6472b80e1 |
||||
@ -0,0 +1,146 @@ |
||||
/** |
||||
* System Date Skill |
||||
* 获取当前日期和农历(使用 lunar-javascript 库) |
||||
*/ |
||||
|
||||
const { Solar, Lunar } = require('lunar-javascript'); |
||||
|
||||
/** |
||||
* 获取北京时间 |
||||
*/ |
||||
function getBeijingTime() { |
||||
const now = new Date(); |
||||
// UTC + 8 = 北京时间
|
||||
return new Date(now.getTime() + (8 * 60 * 60 * 1000)); |
||||
} |
||||
|
||||
/** |
||||
* 获取农历日期(使用专业库) |
||||
*/ |
||||
function getLunarInfo(beijingDate) { |
||||
// 从公历转换为农历
|
||||
const solar = Solar.fromDate(beijingDate); |
||||
const lunar = solar.getLunar(); |
||||
|
||||
return { |
||||
lunarDate: lunar.toString(), // 如:二〇二六年正月初八
|
||||
lunarDay: lunar.getDayInChinese(), // 如:初八
|
||||
lunarMonth: lunar.getMonthInChinese(), // 如:正月
|
||||
lunarYear: lunar.getYearInChinese(), // 如:二〇二六
|
||||
isLeap: false // lunar-javascript 需要其他方法判断闰月
|
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 获取当前日期时间(北京时间) |
||||
*/ |
||||
function getCurrentDateTime() { |
||||
const beijingNow = getBeijingTime(); |
||||
|
||||
const year = beijingNow.getFullYear(); |
||||
const month = beijingNow.getMonth() + 1; |
||||
const day = beijingNow.getDate(); |
||||
const weekdays = ['日', '一', '二', '三', '四', '五', '六']; |
||||
const weekday = `星期${weekdays[beijingNow.getDay()]}`; |
||||
|
||||
const lunarInfo = getLunarInfo(beijingNow); |
||||
|
||||
return { |
||||
success: true, |
||||
timezone: 'Asia/Shanghai', |
||||
year: year, |
||||
month: month, |
||||
day: day, |
||||
weekday: weekday, |
||||
fullDate: `${year}年${month}月${day}日`, |
||||
lunarDate: lunarInfo.lunarDate, |
||||
lunarDay: lunarInfo.lunarDay, |
||||
lunarMonth: lunarInfo.lunarMonth, |
||||
lunarYear: lunarInfo.lunarYear, |
||||
isLeap: lunarInfo.isLeap, |
||||
isoString: beijingNow.toISOString() |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 获取相对日期(北京时间) |
||||
*/ |
||||
function getRelativeDate(relative = 'today') { |
||||
const beijingNow = getBeijingTime(); |
||||
let targetDate = new Date(beijingNow); |
||||
|
||||
switch (relative.toLowerCase()) { |
||||
case 'today': |
||||
case '今天': |
||||
break; |
||||
case 'tomorrow': |
||||
case '明天': |
||||
targetDate.setDate(targetDate.getDate() + 1); |
||||
break; |
||||
case 'yesterday': |
||||
case '昨天': |
||||
targetDate.setDate(targetDate.getDate() - 1); |
||||
break; |
||||
} |
||||
|
||||
const year = targetDate.getFullYear(); |
||||
const month = targetDate.getMonth() + 1; |
||||
const day = targetDate.getDate(); |
||||
const weekdays = ['日', '一', '二', '三', '四', '五', '六']; |
||||
const weekday = `星期${weekdays[targetDate.getDay()]}`; |
||||
|
||||
const lunarInfo = getLunarInfo(targetDate); |
||||
|
||||
return { |
||||
success: true, |
||||
relative: relative, |
||||
year: year, |
||||
month: month, |
||||
day: day, |
||||
weekday: weekday, |
||||
fullDate: `${year}年${month}月${day}日`, |
||||
lunarDate: lunarInfo.lunarDate, |
||||
lunarDay: lunarInfo.lunarDay, |
||||
lunarMonth: lunarInfo.lunarMonth, |
||||
lunarYear: lunarInfo.lunarYear, |
||||
isLeap: lunarInfo.isLeap, |
||||
timezone: 'Asia/Shanghai' |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 格式化日期为可读文本 |
||||
*/ |
||||
function formatDateInfo(dateInfo, includeLunar = true) { |
||||
const lines = [ |
||||
`📅 **${dateInfo.fullDate}**`, |
||||
`**星期:** ${dateInfo.weekday}`, |
||||
]; |
||||
|
||||
if (includeLunar && dateInfo.lunarDate) { |
||||
lines.push(`**农历:** ${dateInfo.lunarDate}`); |
||||
if (dateInfo.isLeap) { |
||||
lines.push(`**闰月:** 是`); |
||||
} |
||||
} |
||||
|
||||
lines.push(`**时区:** ${dateInfo.timezone || 'Asia/Shanghai'}`); |
||||
|
||||
return lines.join('\n'); |
||||
} |
||||
|
||||
// 命令行测试
|
||||
if (require.main === module) { |
||||
console.log('=== 农历计算测试(使用 lunar-javascript 库)===\n'); |
||||
|
||||
const arg = process.argv[2] || 'today'; |
||||
const result = getRelativeDate(arg); |
||||
console.log(formatDateInfo(result)); |
||||
console.log('\n详细信息:'); |
||||
console.log(`农历年:${result.lunarYear}`); |
||||
console.log(`农历月:${result.lunarMonth}`); |
||||
console.log(`农历日:${result.lunarDay}`); |
||||
console.log(`是否闰月:${result.isLeap ? '是' : '否'}`); |
||||
} |
||||
|
||||
module.exports = { getCurrentDateTime, getRelativeDate, formatDateInfo, getLunarInfo, getBeijingTime }; |
||||
@ -0,0 +1,25 @@ |
||||
{ |
||||
"name": "system-date", |
||||
"version": "1.0.0", |
||||
"description": "系统日期时间查询 - 支持用户时区", |
||||
"author": "OpenClaw Team", |
||||
"enabled": true, |
||||
"commands": [ |
||||
{ |
||||
"name": "date", |
||||
"description": "查询当前日期", |
||||
"handler": "date.getCurrentDateTime", |
||||
"usage": "/date [today|tomorrow|yesterday]", |
||||
"examples": [ |
||||
"/date today", |
||||
"/date tomorrow", |
||||
"今天几号", |
||||
"明天是什么日子" |
||||
] |
||||
} |
||||
], |
||||
"config": { |
||||
"default_timezone": "Asia/Shanghai", |
||||
"include_lunar": true |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
[Unit] |
||||
Description=OpenClaw Agent - 张大师 (Life Assistant) |
||||
Documentation=https://docs.openclaw.ai |
||||
After=network.target network-online.target |
||||
Wants=network-online.target |
||||
|
||||
[Service] |
||||
Type=simple |
||||
User=root |
||||
WorkingDirectory=/root/.openclaw |
||||
|
||||
# Environment variables |
||||
Environment=NODE_ENV=production |
||||
Environment=AGENT_ID=life |
||||
Environment=AGENT_PORT=18790 |
||||
Environment=DASHSCOPE_API_KEY=sk-4111c9dba5334510968f9ae72728944e |
||||
Environment=TAVILY_API_KEY=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 |
||||
Environment=XDG_RUNTIME_DIR=/run/user/0 |
||||
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus |
||||
|
||||
# Start the agent gateway on port 18790 |
||||
ExecStart=/usr/bin/node /www/server/nodejs/v24.13.1/bin/openclaw gateway start --port 18790 --agent-id life |
||||
ExecReload=/bin/kill -HUP $MAINPID |
||||
|
||||
# Auto-healing configuration |
||||
Restart=always |
||||
RestartSec=10 |
||||
StartLimitInterval=300 |
||||
StartLimitBurst=5 |
||||
|
||||
# Resource limits |
||||
MemoryLimit=1G |
||||
CPUQuota=40% |
||||
|
||||
# Logging |
||||
StandardOutput=journal |
||||
StandardError=journal |
||||
SyslogIdentifier=openclaw-agent-life |
||||
|
||||
# Security hardening |
||||
NoNewPrivileges=true |
||||
ProtectSystem=strict |
||||
ProtectHome=read-only |
||||
ReadWritePaths=/root/.openclaw |
||||
|
||||
# Watchdog for health monitoring |
||||
WatchdogSec=30 |
||||
|
||||
[Install] |
||||
WantedBy=multi-user.target |
||||
Loading…
Reference in new issue