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