每日备份 2026-03-27

This commit is contained in:
OpenClaw Backup
2026-03-27 23:38:45 +08:00
parent 4f11cd7b03
commit d09281e48c
827 changed files with 6991 additions and 148648 deletions
@@ -0,0 +1,7 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "a-stock-trading-assistant",
"installedVersion": "1.0.0",
"installedAt": 1774617361378
}
+105
View File
@@ -0,0 +1,105 @@
---
name: a-stock-trading-assistant
description: A股股票智能交易助手,专服务中国大陆股票市场。当用户询问A股股票行情、个股分析、大盘情绪、热点板块、交易策略、价格预警、涨跌幅、成交量、技术面或基本面分析时触发。只处理沪深A股(代码以6/00/30/68开头),自动忽略港股和美股。所有数据实时从东方财富、新浪财经、同花顺、雪球抓取。Use when: user asks about Chinese A-share stocks, 股票行情, 个股分析, 大盘分析, 板块热点, 买卖点, 止盈止损, 仓位建议, or any A-share trading-related query.
---
# A股股票智能交易助手
## 角色定位
你是专业A股交易助手,只服务中国大陆A股市场(沪深两市)。数据全部实时联网获取,数据源优先级:东方财富 → 新浪财经 → 同花顺 → 雪球。
## 股票代码识别规则
| 前缀 | 市场 | 示例 |
|------|------|------|
| 60xxxx | 上交所主板 | 600519 贵州茅台 |
| 00xxxx | 深交所主板 | 000001 平安银行 |
| 30xxxx | 创业板 | 300750 宁德时代 |
| 68xxxx | 科创板 | 688981 中芯国际 |
- 自动忽略港股(.HK)、美股(NASDAQ/NYSE)及其他境外市场
- 用户输入不带前缀时,根据数字范围自动判断市场
## 数据获取方式
优先用 `scripts/fetch_stock.py` 脚本获取实时数据。如脚本执行失败,改用 `web_fetch` 直接访问数据源。
详细 API 端点见 `references/data-sources.md`
### 快速调用脚本
```bash
# 查询单只股票实时行情
python3 /app/skills/a-stock-trading-assistant/scripts/fetch_stock.py --code 600519
# 查询大盘指数
python3 /app/skills/a-stock-trading-assistant/scripts/fetch_stock.py --index
# 查询热点板块
python3 /app/skills/a-stock-trading-assistant/scripts/fetch_stock.py --hot-sectors
```
## 六大核心能力工作流
### 1. 实时行情查询
1. 运行 `fetch_stock.py --code <代码>` 获取实时数据
2. 展示:当前价、涨跌幅、涨跌额、成交量、成交额、换手率、振幅、52周高/低
3. 附上分时走势摘要(涨跌节奏描述)
### 2. 个股综合分析
先获取实时行情,再分析:
- **技术面**:均线系统(MA5/10/20/60)、趋势判断、支撑位/压力位、量价结构、MACD/KDJ信号
- **基本面**:PE/PB估值、近期业绩、行业地位、主要风险点
- 技术面与基本面结合,给出综合判断(看多/看空/中性)
详细分析方法见 `references/analysis.md`
### 3. 大盘情绪与风险判断
获取上证指数、深证成指、创业板指实时数据,分析:
- 大盘强弱(趋势、量能、板块轮动)
- 市场情绪指数(赚钱效应、涨跌比)
- 风险等级(低/中/高)及应对建议
### 4. 热点板块与龙头股
1. 运行 `fetch_stock.py --hot-sectors` 获取涨幅榜板块
2. 识别:主线板块(连续性强)、情绪板块(短期热点)
3. 每个热点板块列出核心龙头股(涨停、强势领涨)
### 5. 交易策略与建议
基于用户的持仓/意向股,给出:
- **短线**(1-5天):催化剂、入场区间、止损位、止盈位
- **中线**(1-3月):趋势判断、分批建仓节奏、仓位比例
- 始终标注风险提示
格式模板:
```
【操作建议】XX股(XXXXXX
方向:做多/观望/回避
入场区间:XX.XX - XX.XX 元
止损位:XX.XX 元(跌破离场)
止盈位:XX.XX 元(分批减仓)
仓位:XX%(轻/中/重仓)
逻辑:[核心理由2-3条]
风险:[主要风险1-2条]
```
### 6. 价格预警监控
当用户设置预警时:
- 记录目标价、预警条件(突破/跌破/放量)到 `references/watchlist.md`
- 建议用户配合券商App实时推送,本工具做辅助分析
- 在后续对话中主动核对预警状态
## 输出规范
- 数据必须标注来源和获取时间(精确到分钟)
- 所有价格建议必须附风险提示
- 避免绝对化表述("必涨"/"稳赚"),用概率/可能性描述
- 数字精确到小数点后2位,成交额以亿元为单位
- 大盘/个股分析结构清晰,使用简洁表格或分项列出
@@ -0,0 +1,6 @@
{
"ownerId": "kn70hb4emf3csex5aeqt51arrx82d01z",
"slug": "a-stock-trading-assistant",
"version": "1.0.0",
"publishedAt": 1772789682966
}
@@ -0,0 +1,110 @@
# 个股分析方法参考
## 技术面分析框架
### 均线系统判断
| 形态 | 条件 | 含义 |
|------|------|------|
| 多头排列 | MA5>MA10>MA20>MA60 | 上升趋势强,可做多 |
| 空头排列 | MA5<MA10<MA20<MA60 | 下跌趋势中,回避 |
| 均线粘合 | 各均线收敛 | 方向待定,等待突破 |
| 金叉 | MA5上穿MA10/MA20 | 短期买入信号 |
| 死叉 | MA5下穿MA10/MA20 | 短期卖出信号 |
### 量价结构分析
- **放量上涨**:主力资金入场,趋势确认,可跟进
- **缩量上涨**:小心,动能不足,谨慎追高
- **放量下跌**:恐慌性出逃或主力砸盘,注意风险
- **缩量下跌**:调整中,无大资金出逃,可等底部
- **天量天价**:极端放量后往往是阶段顶部,注意减仓
- **地量地价**:成交极度萎缩后往往是底部信号
### 支撑位 / 压力位识别
1. **整数关口**100/200/300元等心理价位
2. **前期高点/低点**:突破前高=突破压力;跌破前低=下一支撑
3. **均线支撑**:MA20(月线)、MA60(季线)是重要支撑
4. **成交密集区**:历史上大量换手的价格区间,构成强支撑/压力
### MACD 信号
- **金叉+零轴上方**:强烈买入信号
- **死叉+零轴下方**:强烈卖出信号
- **顶/底背离**:价格创新高但MACD不创新高 = 顶背离(卖);价格创新低但MACD不创新低 = 底背离(买)
- **红柱/绿柱缩短**:动能减弱,可能转向
### KDJ 信号
- KDJ>80:超买区,注意回调
- KDJ<20:超卖区,反弹机会
- K线上穿D线(金叉):买入
- K线下穿D线(死叉):卖出
---
## 基本面分析框架
### 估值判断
| 指标 | 低估 | 合理 | 高估 |
|------|------|------|------|
| PE(市盈率) | <行业均值50% | 接近行业均值 | >行业均值2倍 |
| PB(市净率) | <1 | 1-3 | >5 |
- 消费/医药类:参考PE估值
- 银行/地产类:参考PB估值
- 成长类(科技/新能源):参考PEG(<1为合理)
### 业绩评估要点
- 近4个季度营收/净利增速趋势
- 毛利率变化(毛利率提升=竞争力增强)
- 净利率水平(与同行比较)
- 现金流情况(经营活动现金流>净利润=高质量利润)
### 行业地位
- 市占率排名(行业前3更优)
- 护城河类型:品牌/技术/成本/网络效应
- 政策支持方向(新能源/半导体/AI等优先)
---
## 大盘分析框架
### 市场情绪量化
| 指标 | 计算方式 | 情绪判断 |
|------|---------|---------|
| 赚钱效应 | 上涨股/总股数 | >60%强,<40%弱 |
| 涨停数量 | 当日涨停家数 | >100家热,<30家冷 |
| 量能比 | 今日量/5日均量 | >1.2放量,<0.8缩量 |
### 风险等级定义
| 级别 | 特征 | 建议仓位 |
|------|------|---------|
| 低风险 | 大盘趋势向上,量能充裕,涨多跌少 | 7-9成仓 |
| 中风险 | 方向不明,震荡整理 | 3-6成仓 |
| 高风险 | 趋势向下,缩量或恐慌放量 | 0-2成仓 |
---
## 热点板块判断标准
### 主线板块(可重点跟踪)
- 连续3天以上保持热度
- 有政策/事件催化
- 龙头股有持续涨停
### 情绪板块(短线机会)
- 单日暴热,次日需观察持续性
- 题材性质(概念股)
- 跟风盘多,需快进快出
### 龙头股识别
- 板块内首板/连板的核心票
- 换手率高但不崩盘
- 量价配合良好
@@ -0,0 +1,114 @@
# 数据源 API 参考
## 1. 新浪财经(最稳定,优先使用)
### 实时行情
```
# 单股(沪市加sh,深市加sz
GET http://hq.sinajs.cn/list=sh600519
GET http://hq.sinajs.cn/list=sz000001
# 多股同时查询
GET http://hq.sinajs.cn/list=sh600519,sz000001,sz300750
```
返回格式(逗号分隔的字符串):
```
var hq_str_sh600519="贵州茅台,1788.00,1785.00,1800.00,1810.00,1780.00,1799.00,1800.00,3456789,6234567890.00,100,1799.00,...,2024-01-15,15:00:00,00";
```
字段顺序:股票名,昨收,今开,当前价,最高,最低,买一价,卖一价,成交量(手),成交额,买一量,买一价,...,日期,时间
### 大盘指数
```
GET http://hq.sinajs.cn/list=s_sh000001,s_sz399001,s_sz399006
# 上证指数,深证成指,创业板指
```
---
## 2. 东方财富(数据全面,适合深度查询)
### 实时行情
```
GET http://push2.eastmoney.com/api/qt/stock/get?secid={market}.{code}&fields=f43,f44,f45,f46,f47,f48,f57,f58,f60,f107,f169,f170,f171,f530
```
market: 1=沪市, 0=深市
关键字段:
- f43: 最新价(×0.01
- f44: 最高价
- f45: 最低价
- f46: 今开
- f47: 成交量(手)
- f48: 成交额(元)
- f57: 股票代码
- f58: 股票名称
- f60: 昨收
- f170: 涨跌幅(%×100
- f169: 涨跌额
- f171: 换手率(%×100
### 涨幅榜/板块榜
```
# A股涨幅榜(前50
GET http://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=50&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&fid=f3&fs=m:0+t:6,m:0+t:13,m:0+t:80,m:1+t:2,m:1+t:23&fields=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18
# 板块涨跌榜(概念/行业)
GET http://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=20&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&fid=f3&fs=m:90+t:2+f:!50&fields=f1,f2,f3,f4,f5,f6,f7,f8,f12,f14,f20,f21
```
### 个股分时数据
```
GET http://push2.eastmoney.com/api/qt/stock/trends2/get?secid={market}.{code}&fields1=f1,f2,f3,f4,f5&fields2=f51,f52,f53,f54,f55,f56,f57,f58&iscr=0&iscca=0
```
---
## 3. 同花顺
### 实时行情
```
GET https://d.10jqka.com.cn/v4/time/hs_{code}/today.js
# code: 直接用6位数字,如 600519
```
注意:请求需要带 Referer: https://www.10jqka.com.cn
---
## 4. 雪球
### 实时行情
```
GET https://stock.xueqiu.com/v5/stock/quote.json?symbol={symbol}&extend=detail
# symbol: SH600519 / SZ000001 / SZ300750
```
注意:需要先访问 https://xueqiu.com 获取 cookiesession),再请求数据接口。脚本中已处理。
---
## 市场代码对照
| 代码前缀 | 新浪前缀 | 东财market | 雪球前缀 |
|---------|---------|-----------|---------|
| 600xxx / 601xxx / 603xxx / 605xxx / 688xxx | sh | 1 | SH |
| 000xxx / 001xxx / 002xxx / 003xxx / 300xxx / 301xxx | sz | 0 | SZ |
## 大盘指数代码
| 指数 | 新浪 | 东财secid |
|------|------|----------|
| 上证指数 | sh000001 | 1.000001 |
| 深证成指 | sz399001 | 0.399001 |
| 创业板指 | sz399006 | 0.399006 |
| 科创50 | sh000688 | 1.000688 |
| 北证50 | bj899050 | — |
@@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""
A股实时行情抓取脚本
数据源:新浪财经(主)、东方财富(备)
用法:
python3 fetch_stock.py --code 600519
python3 fetch_stock.py --code 000001 sz000001 300750
python3 fetch_stock.py --index
python3 fetch_stock.py --hot-sectors
"""
import argparse
import json
import re
import sys
import urllib.request
import urllib.error
from datetime import datetime
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://finance.sina.com.cn",
"Accept-Language": "zh-CN,zh;q=0.9",
}
def get_market_prefix(code: str) -> str:
"""根据股票代码判断市场前缀"""
code = code.strip().upper()
if code.startswith("SH") or code.startswith("SZ"):
return code[:2].lower(), code[2:]
code = re.sub(r"[^0-9]", "", code)
if code.startswith(("60", "68", "51", "58", "11")):
return "sh", code
elif code.startswith(("00", "30", "15", "12", "16", "13")):
return "sz", code
return "sh", code # default
def fetch_url(url: str, extra_headers: dict = None) -> str:
req = urllib.request.Request(url, headers=HEADERS)
if extra_headers:
for k, v in extra_headers.items():
req.add_header(k, v)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
charset = "gbk" if "sina" in url or "sinajs" in url else "utf-8"
return resp.read().decode(charset, errors="replace")
except Exception as e:
return ""
def parse_sina_stock(raw: str, symbol: str) -> dict:
"""解析新浪财经股票数据"""
match = re.search(r'"([^"]*)"', raw)
if not match:
return {}
parts = match.group(1).split(",")
if len(parts) < 32:
return {}
try:
name = parts[0]
prev_close = float(parts[2]) if parts[2] else 0
open_price = float(parts[1]) if parts[1] else 0
current = float(parts[3]) if parts[3] else 0
high = float(parts[4]) if parts[4] else 0
low = float(parts[5]) if parts[5] else 0
volume = int(parts[8]) if parts[8] else 0 # 手
amount = float(parts[9]) if parts[9] else 0 # 元
date_str = parts[30] if len(parts) > 30 else ""
time_str = parts[31] if len(parts) > 31 else ""
change = current - prev_close
change_pct = (change / prev_close * 100) if prev_close else 0
turnover_approx = volume / 1000 # 粗略换手(无流通股数据)
return {
"symbol": symbol,
"name": name,
"current": round(current, 2),
"change": round(change, 2),
"change_pct": round(change_pct, 2),
"open": round(open_price, 2),
"high": round(high, 2),
"low": round(low, 2),
"prev_close": round(prev_close, 2),
"volume_lot": volume, # 手
"amount_yuan": round(amount, 2),
"amount_yi": round(amount / 1e8, 2),
"date": date_str,
"time": time_str,
"source": "新浪财经",
"fetch_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
except (ValueError, IndexError):
return {}
def fetch_single_stock(code: str) -> dict:
"""抓取单只股票行情"""
prefix, clean_code = get_market_prefix(code)
symbol = f"{prefix}{clean_code}"
url = f"http://hq.sinajs.cn/list={symbol}"
raw = fetch_url(url)
if raw:
data = parse_sina_stock(raw, symbol)
if data:
return data
# 备用:东方财富
market = 1 if prefix == "sh" else 0
url2 = (
f"http://push2.eastmoney.com/api/qt/stock/get"
f"?secid={market}.{clean_code}"
f"&fields=f43,f44,f45,f46,f47,f48,f57,f58,f60,f107,f169,f170,f171"
)
raw2 = fetch_url(url2, {"Referer": "https://www.eastmoney.com"})
if raw2:
try:
obj = json.loads(raw2)
d = obj.get("data", {}) or {}
if d.get("f43"):
prev = d["f60"] / 100
curr = d["f43"] / 100
chg = d["f169"] / 100
chg_pct = d["f170"] / 100
return {
"symbol": symbol,
"name": d.get("f58", ""),
"current": round(curr, 2),
"change": round(chg, 2),
"change_pct": round(chg_pct, 2),
"open": round(d["f46"] / 100, 2),
"high": round(d["f44"] / 100, 2),
"low": round(d["f45"] / 100, 2),
"prev_close": round(prev, 2),
"volume_lot": d.get("f47", 0),
"amount_yuan": d.get("f48", 0),
"amount_yi": round(d.get("f48", 0) / 1e8, 2),
"turnover_pct": round(d.get("f171", 0) / 100, 2),
"source": "东方财富",
"fetch_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
except Exception:
pass
return {"error": f"无法获取 {code} 的行情数据", "symbol": symbol}
def fetch_index() -> list:
"""抓取主要大盘指数"""
symbols = "s_sh000001,s_sz399001,s_sz399006,s_sh000688"
names_map = {
"s_sh000001": "上证指数",
"s_sz399001": "深证成指",
"s_sz399006": "创业板指",
"s_sh000688": "科创50",
}
url = f"http://hq.sinajs.cn/list={symbols}"
raw = fetch_url(url)
results = []
if raw:
for sym, name in names_map.items():
pattern = rf'hq_str_{re.escape(sym)}="([^"]*)"'
m = re.search(pattern, raw)
if m:
parts = m.group(1).split(",")
if len(parts) >= 5:
try:
results.append({
"name": parts[0] or name,
"current": float(parts[1]),
"change": float(parts[2]),
"change_pct": float(parts[3]),
"volume_yi_lot": round(float(parts[4]) / 1e8, 2),
"amount_yi": round(float(parts[5]) / 1e8, 2) if len(parts) > 5 else 0,
"fetch_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"source": "新浪财经",
})
except (ValueError, IndexError):
pass
return results
def fetch_hot_sectors() -> list:
"""抓取热点板块(东方财富概念板块涨幅榜)"""
url = (
"http://push2.eastmoney.com/api/qt/clist/get"
"?pn=1&pz=20&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281"
"&fltt=2&invt=2&fid=f3"
"&fs=m:90+t:2+f:!50"
"&fields=f2,f3,f4,f12,f14,f20,f128,f136,f207,f208,f209"
)
raw = fetch_url(url, {"Referer": "https://www.eastmoney.com"})
results = []
if raw:
try:
obj = json.loads(raw)
items = obj.get("data", {}).get("diff", [])
for item in items:
results.append({
"name": item.get("f14", ""),
"change_pct": round(item.get("f3", 0), 2),
"leading_stock": item.get("f128", ""),
"leading_change_pct": round(item.get("f136", 0), 2),
"amount_yi": round(item.get("f20", 0) / 1e8, 2),
})
except Exception:
pass
return results
def fmt_stock(d: dict) -> str:
if "error" in d:
return f"{d['error']}"
sign = "+" if d["change"] >= 0 else ""
emoji = "🔴" if d["change"] >= 0 else "🟢"
lines = [
f"{emoji} {d['name']}{d['symbol'].upper()}",
f" 当前价:{d['current']}",
f" 涨跌幅:{sign}{d['change_pct']}% 涨跌额:{sign}{d['change']}",
f" 今开:{d['open']} 最高:{d['high']} 最低:{d['low']} 昨收:{d['prev_close']}",
f" 成交量:{d['volume_lot']:,} 手 成交额:{d['amount_yi']} 亿",
]
if "turnover_pct" in d:
lines.append(f" 换手率:{d['turnover_pct']}%")
lines.append(f" 数据来源:{d['source']} | 更新时间:{d.get('time', '')} {d['fetch_time']}")
return "\n".join(lines)
def fmt_index(items: list) -> str:
lines = ["📊 大盘指数实时行情"]
for d in items:
sign = "+" if d["change"] >= 0 else ""
emoji = "🔴" if d["change"] >= 0 else "🟢"
lines.append(
f" {emoji} {d['name']}: {d['current']:,.2f} {sign}{d['change']:+.2f} ({sign}{d['change_pct']}%)"
f" 成交额 {d['amount_yi']} 亿"
)
if items:
lines.append(f" 更新时间:{items[0]['fetch_time']}")
return "\n".join(lines)
def fmt_sectors(items: list) -> str:
lines = ["🔥 热点板块涨幅榜(概念板块 TOP20)"]
for i, d in enumerate(items, 1):
sign = "+" if d["change_pct"] >= 0 else ""
lines.append(
f" {i:2d}. {d['name']:<12} {sign}{d['change_pct']}%"
f" 龙头:{d['leading_stock']}({sign}{d['leading_change_pct']}%)"
f" 成交额:{d['amount_yi']}亿"
)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="A股实时行情抓取")
parser.add_argument("--code", nargs="+", help="股票代码,支持多个(如 600519 000001")
parser.add_argument("--index", action="store_true", help="查询大盘指数")
parser.add_argument("--hot-sectors", action="store_true", help="查询热点板块")
parser.add_argument("--json", action="store_true", help="输出原始JSON")
args = parser.parse_args()
if args.index:
data = fetch_index()
if args.json:
print(json.dumps(data, ensure_ascii=False, indent=2))
else:
print(fmt_index(data))
return
if args.hot_sectors:
data = fetch_hot_sectors()
if args.json:
print(json.dumps(data, ensure_ascii=False, indent=2))
else:
print(fmt_sectors(data))
return
if args.code:
results = []
for code in args.code:
d = fetch_single_stock(code)
results.append(d)
if args.json:
print(json.dumps(results, ensure_ascii=False, indent=2))
else:
for d in results:
print(fmt_stock(d))
print()
return
parser.print_help()
if __name__ == "__main__":
main()
+149
View File
@@ -0,0 +1,149 @@
---
name: auto-updater
description: "Automatically update Clawdbot and all installed skills once daily. Runs via cron, checks for updates, applies them, and messages the user with a summary of what changed."
metadata: {"version":"1.0.0","clawdbot":{"emoji":"🔄","os":["darwin","linux"]}}
---
# Auto-Updater Skill
Keep your Clawdbot and skills up to date automatically with daily update checks.
## What It Does
This skill sets up a daily cron job that:
1. Updates Clawdbot itself (via `clawdbot doctor` or package manager)
2. Updates all installed skills (via `clawdhub update --all`)
3. Messages you with a summary of what was updated
## Setup
### Quick Start
Ask Clawdbot to set up the auto-updater:
```
Set up daily auto-updates for yourself and all your skills.
```
Or manually add the cron job:
```bash
clawdbot cron add \
--name "Daily Auto-Update" \
--cron "0 4 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--wake now \
--deliver \
--message "Run daily auto-updates: check for Clawdbot updates and update all skills. Report what was updated."
```
### Configuration Options
| Option | Default | Description |
|--------|---------|-------------|
| Time | 4:00 AM | When to run updates (use `--cron` to change) |
| Timezone | System default | Set with `--tz` |
| Delivery | Main session | Where to send the update summary |
## How Updates Work
### Clawdbot Updates
For **npm/pnpm/bun installs**:
```bash
npm update -g clawdbot@latest
# or: pnpm update -g clawdbot@latest
# or: bun update -g clawdbot@latest
```
For **source installs** (git checkout):
```bash
clawdbot update
```
Always run `clawdbot doctor` after updating to apply migrations.
### Skill Updates
```bash
clawdhub update --all
```
This checks all installed skills against the registry and updates any with new versions available.
## Update Summary Format
After updates complete, you'll receive a message like:
```
🔄 Daily Auto-Update Complete
**Clawdbot**: Updated to v2026.1.10 (was v2026.1.9)
**Skills Updated (3)**:
- prd: 2.0.3 → 2.0.4
- browser: 1.2.0 → 1.2.1
- nano-banana-pro: 3.1.0 → 3.1.2
**Skills Already Current (5)**:
gemini, sag, things-mac, himalaya, peekaboo
No issues encountered.
```
## Manual Commands
Check for updates without applying:
```bash
clawdhub update --all --dry-run
```
View current skill versions:
```bash
clawdhub list
```
Check Clawdbot version:
```bash
clawdbot --version
```
## Troubleshooting
### Updates Not Running
1. Verify cron is enabled: check `cron.enabled` in config
2. Confirm Gateway is running continuously
3. Check cron job exists: `clawdbot cron list`
### Update Failures
If an update fails, the summary will include the error. Common fixes:
- **Permission errors**: Ensure the Gateway user can write to skill directories
- **Network errors**: Check internet connectivity
- **Package conflicts**: Run `clawdbot doctor` to diagnose
### Disabling Auto-Updates
Remove the cron job:
```bash
clawdbot cron remove "Daily Auto-Update"
```
Or disable temporarily in config:
```json
{
"cron": {
"enabled": false
}
}
```
## Resources
- [Clawdbot Updating Guide](https://docs.clawd.bot/install/updating)
- [ClawdHub CLI](https://docs.clawd.bot/tools/clawdhub)
- [Cron Jobs](https://docs.clawd.bot/cron)
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn73fehpspmvrqqdvz7jjdb50d7z4h5s",
"slug": "auto-updater",
"version": "1.0.0",
"publishedAt": 1768323539453
}
@@ -0,0 +1,152 @@
# Agent Implementation Guide
When asked to set up auto-updates, follow this procedure.
## Step 1: Detect Installation Type
```bash
# Check if installed via npm globally
npm list -g clawdbot 2>/dev/null && echo "npm-global"
# Check if installed via source (git)
[ -d ~/.clawdbot/.git ] || [ -f /opt/clawdbot/.git/config ] && echo "source-install"
# Check pnpm
pnpm list -g clawdbot 2>/dev/null && echo "pnpm-global"
# Check bun
bun pm ls -g 2>/dev/null | grep clawdbot && echo "bun-global"
```
## Step 2: Create the Update Script (Optional)
For complex setups, create a helper script at `~/.clawdbot/scripts/auto-update.sh`:
```bash
#!/bin/bash
set -e
LOG_FILE="${HOME}/.clawdbot/logs/auto-update.log"
mkdir -p "$(dirname "$LOG_FILE")"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
log "Starting auto-update..."
# Capture starting versions
CLAWDBOT_VERSION_BEFORE=$(clawdbot --version 2>/dev/null || echo "unknown")
# Update Clawdbot
log "Updating Clawdbot..."
if command -v npm &> /dev/null && npm list -g clawdbot &> /dev/null; then
npm update -g clawdbot@latest 2>&1 | tee -a "$LOG_FILE"
elif command -v pnpm &> /dev/null && pnpm list -g clawdbot &> /dev/null; then
pnpm update -g clawdbot@latest 2>&1 | tee -a "$LOG_FILE"
elif command -v bun &> /dev/null; then
bun update -g clawdbot@latest 2>&1 | tee -a "$LOG_FILE"
else
log "Running clawdbot update (source install)"
clawdbot update 2>&1 | tee -a "$LOG_FILE" || true
fi
# Run doctor for migrations
log "Running doctor..."
clawdbot doctor --yes 2>&1 | tee -a "$LOG_FILE" || true
# Capture new version
CLAWDBOT_VERSION_AFTER=$(clawdbot --version 2>/dev/null || echo "unknown")
# Update skills
log "Updating skills via ClawdHub..."
SKILL_OUTPUT=$(clawdhub update --all 2>&1) || true
echo "$SKILL_OUTPUT" >> "$LOG_FILE"
log "Auto-update complete."
# Output summary for agent to parse
echo "---UPDATE_SUMMARY_START---"
echo "clawdbot_before: $CLAWDBOT_VERSION_BEFORE"
echo "clawdbot_after: $CLAWDBOT_VERSION_AFTER"
echo "skill_output: $SKILL_OUTPUT"
echo "---UPDATE_SUMMARY_END---"
```
## Step 3: Add Cron Job
The recommended approach is to use Clawdbot's built-in cron with an isolated session:
```bash
clawdbot cron add \
--name "Daily Auto-Update" \
--cron "0 4 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--wake now \
--deliver \
--message "Run the daily auto-update routine:
1. Check and update Clawdbot:
- For npm installs: npm update -g clawdbot@latest
- For source installs: clawdbot update
- Then run: clawdbot doctor --yes
2. Update all skills:
- Run: clawdhub update --all
3. Report back with:
- Clawdbot version before/after
- List of skills that were updated (name + old version → new version)
- Any errors encountered
Format the summary clearly for the user."
```
## Step 4: Verify Setup
```bash
# Confirm cron job was added
clawdbot cron list
# Test the update commands work
clawdbot --version
clawdhub list
```
## Customization Prompts
Users may want to customize:
**Different time:**
```bash
--cron "0 6 * * *" # 6 AM instead of 4 AM
```
**Different timezone:**
```bash
--tz "Europe/London"
```
**Specific provider delivery:**
```bash
--provider telegram --to "@username"
```
**Weekly instead of daily:**
```bash
--cron "0 4 * * 0" # Sundays at 4 AM
```
## Error Handling
If updates fail, the agent should:
1. Log the error clearly
2. Still report partial success (if skills updated but Clawdbot didn't, or vice versa)
3. Suggest manual intervention if needed
Common errors to handle:
- `EACCES`: Permission denied → suggest `sudo` or fixing permissions
- Network timeouts → retry once, then report
- Git conflicts (source installs) → suggest `clawdbot update --force`
@@ -0,0 +1,109 @@
# Update Summary Examples
Reference examples for formatting the update report message.
## Full Update (Everything Changed)
```
🔄 Daily Auto-Update Complete
**Clawdbot**
Updated: v2026.1.9 → v2026.1.10
Key changes in this release:
- CLI: add clawdbot update command
- Gateway: add OpenAI-compatible HTTP endpoint
- Sandbox: improved tool-policy errors
**Skills Updated (3)**
1. prd: 2.0.3 → 2.0.4
2. browser: 1.2.0 → 1.2.1
3. nano-banana-pro: 3.1.0 → 3.1.2
**Skills Already Current (5)**
gemini, sag, things-mac, himalaya, peekaboo
✅ All updates completed successfully.
```
## No Updates Available
```
🔄 Daily Auto-Update Check
**Clawdbot**: v2026.1.10 (already latest)
**Skills**: All 8 installed skills are current.
Nothing to update today.
```
## Partial Update (Skills Only)
```
🔄 Daily Auto-Update Complete
**Clawdbot**: v2026.1.10 (no update available)
**Skills Updated (2)**
1. himalaya: 1.0.0 → 1.0.1
- Fixed IMAP connection timeout handling
2. 1password: 2.1.0 → 2.2.0
- Added support for SSH keys
**Skills Already Current (6)**
prd, gemini, browser, sag, things-mac, peekaboo
✅ Skill updates completed.
```
## Update With Errors
```
🔄 Daily Auto-Update Complete (with issues)
**Clawdbot**: v2026.1.9 → v2026.1.10 ✅
**Skills Updated (1)**
1. prd: 2.0.3 → 2.0.4 ✅
**Skills Failed (1)**
1. ❌ nano-banana-pro: Update failed
Error: Network timeout while downloading v3.1.2
Recommendation: Run `clawdhub update nano-banana-pro` manually
**Skills Already Current (6)**
gemini, sag, things-mac, himalaya, peekaboo, browser
⚠️ Completed with 1 error. See above for details.
```
## First Run / Setup Confirmation
```
🔄 Auto-Updater Configured
Daily updates will run at 4:00 AM (America/Los_Angeles).
**What will be updated:**
- Clawdbot core
- All installed skills via ClawdHub
**Current status:**
- Clawdbot: v2026.1.10
- Installed skills: 8
You'll receive a summary here after each update run.
To modify: `clawdbot cron edit "Daily Auto-Update"`
To disable: `clawdbot cron remove "Daily Auto-Update"`
```
## Formatting Guidelines
1. **Use emojis sparingly** - just the 🔄 header and ✅/❌ for status
2. **Lead with the most important info** - what changed
3. **Group similar items** - updated skills together, current skills together
4. **Include version numbers** - always show before → after
5. **Be concise** - users want a quick scan, not a wall of text
6. **Surface errors prominently** - don't bury failures
+63
View File
@@ -0,0 +1,63 @@
---
name: baidu-search
description: Search the web using Baidu AI Search Engine (BDSE). Use for live information, documentation, or research topics.
metadata: { "openclaw": { "emoji": "🔍︎", "requires": { "bins": ["python3"], "env":["BAIDU_API_KEY"]},"primaryEnv":"BAIDU_API_KEY" } }
---
# Baidu Search
Search the web via Baidu AI Search API.
## Prerequisites
### API Key Configuration
This skill requires a **BAIDU_API_KEY** to be configured in OpenClaw.
If you don't have an API key yet, please visit:
**https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey**
For detailed setup instructions, see:
[references/apikey-fetch.md](references/apikey-fetch.md)
## Usage
```bash
python3 skills/baidu-search/scripts/search.py '<JSON>'
```
## Request Parameters
| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| query | str | yes | - | Search query |
| count | int | no | 10 | Number of results to return, range 1-50 |
| freshness | str | no | Null | Time range, two formats: format one is ”YYYY-MM-DDtoYYYY-MM-DD“, and format two includes pd, pw, pm, and py, representing the past 24 hours, past 7 days, past 31 days, and past 365 days respectively |
## Examples
```bash
# Basic search
python3 scripts/search.py '{"query":"人工智能"}'
# Freshness first format "YYYY-MM-DDtoYYYY-MM-DD" example
python3 scripts/search.py '{
"query":"最新新闻",
"freshness":"2025-09-01to2025-09-08"
}'
# Freshness second format pd、pw、pm、py example
python3 scripts/search.py '{
"query":"最新新闻",
"freshness":"pd"
}'
# set count, the number of results to return
python3 scripts/search.py '{
"query":"旅游景点",
"count": 20,
}'
```
## Current Status
Fully functional.
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn7akgt520t01vgs2tzx7yk6m180kt26",
"slug": "baidu-search",
"version": "1.1.3",
"publishedAt": 1773828934466
}
@@ -0,0 +1,58 @@
# Baidu API Key Setup Guide (OpenClaw)
## BAIDU_API_KEY Not Configured
When the `BAIDU_API_KEY` environment variable is not set, follow these steps:
### 1. Get API Key
Visit: **https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey**
- Log in to your Baidu Cloud account
- Create an application or view existing API keys
- Copy your **API Key** (only API Key is needed)
### 2. Configure OpenClaw
Edit the OpenClaw configuration file: `~/.openclaw/openclaw.json`
Add or merge the following structure:
```json
{
"skills": {
"entries": {
"baidu-search": {
"env": {
"BAIDU_API_KEY": "your_actual_api_key_here"
}
}
}
}
}
```
Replace `"your_actual_api_key_here"` with your actual API key.
### 3. Verify Configuration
```bash
# Check JSON format
cat ~/.openclaw/openclaw.json | python -m json.tool
```
### 4. Restart OpenClaw
```bash
openclaw gateway restart
```
### 5. Test
```bash
cd ~/.openclaw/workspace/skills/baidu-search
python3 scripts/search.py '{"query": "test search"}'
```
## Troubleshooting
- Ensure `~/.openclaw/openclaw.json` exists with correct JSON format
- Confirm API key is valid and Baidu AI Search service is activated
- Check account balance on Baidu Cloud
- Restart OpenClaw after configuration changes
**Recommended**: Use OpenClaw configuration file for centralized management
+102
View File
@@ -0,0 +1,102 @@
import sys
import json
import requests
import os
import re
from datetime import datetime, timedelta
def baidu_search(api_key, requestBody: dict):
url = "https://qianfan.baidubce.com/v2/ai_search/web_search"
headers = {
"Authorization": "Bearer %s" % api_key,
"X-Appbuilder-From": "openclaw",
"Content-Type": "application/json"
}
# 使用POST方法发送JSON数据
response = requests.post(url, json=requestBody, headers=headers)
response.raise_for_status()
results = response.json()
if "code" in results:
raise Exception(results["message"])
datas = results["references"]
keys_to_remove = {"snippet"}
for item in datas:
for key in keys_to_remove:
if key in item:
del item[key]
return datas
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python baidu_search.py <Json>")
sys.exit(1)
query = sys.argv[1]
parse_data = {}
try:
parse_data = json.loads(query)
print(f"success parse request body: {parse_data}")
except json.JSONDecodeError as e:
print(f"JSON parse error: {e}")
if "query" not in parse_data:
print("Error: query must be present in request body.")
sys.exit(1)
count = 10
search_filter = {}
if "count" in parse_data:
count = int(parse_data["count"])
if count <= 0:
count = 10
elif count > 50:
count = 50
current_time = datetime.now()
end_date = (current_time + timedelta(days=1)).strftime("%Y-%m-%d")
pattern = r'\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}'
if "freshness" in parse_data:
if parse_data["freshness"] in ["pd", "pw", "pm", "py"]:
if parse_data["freshness"] == "pd":
start_date = (current_time - timedelta(days=1)).strftime("%Y-%m-%d")
if parse_data["freshness"] == "pw":
start_date = (current_time - timedelta(days=6)).strftime("%Y-%m-%d")
if parse_data["freshness"] == "pm":
start_date = (current_time - timedelta(days=30)).strftime("%Y-%m-%d")
if parse_data["freshness"] == "py":
start_date = (current_time - timedelta(days=364)).strftime("%Y-%m-%d")
search_filter = {"range": {"page_time": {"gte": start_date, "lt": end_date}}}
elif re.match(pattern, parse_data["freshness"]):
start_date = parse_data["freshness"].split("to")[0]
end_date = parse_data["freshness"].split("to")[1]
search_filter = {"range": {"page_time": {"gte": start_date, "lt": end_date}}}
else:
print(f"Error: freshness ({parse_data['freshness']}) must be pd, pw, pm, py, or match {pattern}.")
sys.exit(1)
# We will pass these via env vars for security
api_key = os.getenv("BAIDU_API_KEY")
if not api_key:
print("Error: BAIDU_API_KEY must be set in environment.")
sys.exit(1)
request_body = {
"messages": [
{
"content": parse_data["query"],
"role": "user"
}
],
"search_source": "baidu_search_v2",
"resource_type_filter": [{"type": "web", "top_k": count}],
"search_filter": search_filter
}
try:
results = baidu_search(api_key, request_body)
print(json.dumps(results, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error: {str(e)}")
sys.exit(1)
@@ -0,0 +1,7 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "content-collector-skill",
"installedVersion": "0.1.0",
"installedAt": 1774625592921
}
+800
View File
@@ -0,0 +1,800 @@
---
name: content-collector
description: Automatically collect and archive content from shared links in group chats. When a user shares a link (WeChat articles, Feishu docs, web pages, etc.) in any group chat and asks to archive/collect/save it, this skill triggers to fetch the content, create a Feishu document, and update the knowledge base table. Use when: (1) User shares a link and asks to "收录/转存/保存" content, (2) Need to archive web content to Feishu docs, (3) Building a personal knowledge base from shared links, (4) Organizing learning materials from various sources.
---
# Content Collector - 链接内容自动收录技能
## Overview
This skill enables automatic collection and archiving of content from shared links into a structured knowledge base.
**Core Workflow:**
```
Detect Link → Fetch Content → Create Feishu Doc → Update Table
```
## When to Use
### 模式1:主动触发(显式关键词)
当用户消息包含以下**触发词**时,立即执行收录:
- "收录" / "转存" / "保存" / "存档" / "存一下" / "归档" / "备份" / "收藏"
- "存到知识库" / "加入知识库" / "转飞书"
**示例:**
- "这个链接收录一下"
- "存到知识库"
- "转存这篇教程"
### 模式2:静默收录(自动检测)
在**群聊场景**中,自动检测以下链接并静默收录:
- 飞书文档/表格/Wikifeishu.cn
- 微信公众号文章(mp.weixin.qq.com
- 技术博客/教程站点
- 知识分享类链接
**静默收录条件:**
1. 消息来自群聊(非私聊)
2. 消息包含可识别的知识类链接
3. 用户没有明确拒绝的意图
**两种模式优先级:**
```
检测到主动触发词 → 立即收录(显式模式)
未检测到触发词但检测到链接 → 静默收录(隐式模式)
```
## Supported Link Types
| Type | Example | Fetch Method |
|------|---------|--------------|
| WeChat Article | `https://mp.weixin.qq.com/s/xxx` | kimi_fetch |
| Feishu Doc | `https://xxx.feishu.cn/docx/xxx` | feishu_fetch_doc |
| Feishu Wiki | `https://xxx.feishu.cn/wiki/xxx` | feishu_fetch_doc |
| Web Page | General URLs | kimi_fetch / web_fetch |
## Global Availability (全局可用配置)
**生效范围:所有用户、所有群聊**
本技能已配置为全局可用,支持以下对象:
| 对象类型 | 支持状态 | 说明 |
|---------|---------|------|
| **所有用户** | ✅ 可用 | 任何用户分享的链接均可被收录 |
| **所有群聊** | ✅ 可用 | 支持技能中心群、养虾群、学习群等所有群组 |
| **私聊消息** | ✅ 可用 | 用户私信分享链接也可触发收录 |
| **多渠道** | ✅ 可用 | 飞书、其他渠道统一支持 |
**权限说明:**
- 任何用户均可触发收录(无需管理员权限)
- 收录的文档统一存储到指定的知识库目录
- 所有用户均可查看已收录的文档
---
## Installation & Permission Check (安装与权限检查)
在正式使用本技能前,系统必须自动或引导用户完成以下权限校验,以确保流程不中断:
### 1. 飞书权限清单
| 权限项 | 验证工具 | 目的 |
|-------|---------|------|
| **OAuth 授权** | `feishu_oauth` | 获取操作飞书文档和表格的用户凭证 |
| **知识库写入权限** | `feishu_create_doc` | 确保能在指定的 Space ID 下创建节点 |
| **多维表格编辑权限** | `feishu_bitable_app_table_record` | 确保能向指定的 app_token 写入记录 |
| **图片上传权限** | `feishu_im_bot_upload` | 允许将本地图片同步至飞书素材库 |
### 2. 预检流程 (Pre-flight Check)
每次“安装”或配置更新后,执行以下检查:
1. **验证 Space ID 可访问性**:尝试在指定目录下获取节点列表。
2. **验证 Table 结构**:检查 `关键词``原链接` 等必需字段是否存在。
3. **静默测试**:如果权限不足,立即通过 `feishu_oauth` 弹出授权引导,而非在执行收录时报错。
---
## Configuration
Before using, ensure these are configured in MEMORY.md:
```markdown
## Content Collector Config
- **Knowledge Base Table**: `[Your Bitable App Token]` (Bitable app_token)
- **Table URL**: [Your Bitable Table URL]
- **Default Table ID**: `[Your Table ID]` (will auto-detect if available)
- **Knowledge Base Space ID**: `[Your Space ID]` (所有文档创建在此知识库下)
- **Knowledge Base URL**: [Your Knowledge Base Homepage URL]
- **Content Categories**: 技术教程, 实战案例, 产品文档, 学习笔记
- **Global Access**: 所有用户可用,所有群聊可用
```
**Note**:
1. This skill updates ONLY the configured knowledge base table. Do not create or update any other tables.
2. **All created documents must be saved under the designated Knowledge Base** using wiki_node parameter.
3. **Global Access**: 所有用户、所有群聊均可使用本技能,收录的文档对全员可见。
---
## 📚 知识库文档存储规则(必遵守)
所有收录的文档必须按照以下规则分类存储到知识库对应目录:
### 知识库目录结构
请参考各项目或团队定义的知识库标准目录结构进行存储。收录的文档通常存放在“素材”或“归档”类目录下。
### 文档分类映射规则
| 内容分类 | 存储目录 (wiki_node) | 命名前缀 | 示例 |
|----------|---------------------|----------|------|
| 技术教程 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 📖 | 📖 [标题] |
| 实战案例 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 🛠️ | 🛠️ [标题] |
| 产品文档 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 📄 | 📄 [标题] |
| 学习笔记 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 💡 | 💡 [标题] |
| 热点资讯 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 🔥 | 🔥 [标题] |
| 设计技能 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 🎨 | 🎨 [标题] |
| 工具推荐 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 🔧 | 🔧 [标题] |
| 训练营 | `F9pFw9dxTiXmpsk5bNlco704nag` (内容文档) | 🎓 | 🎓 [标题] |
### 文档命名规范
```
[Emoji前缀] [原标题] | 收录日期
示例:
📖 OpenClaw保姆级教程 | 2026-03-08
🛠️ 火山方舟自动化报表案例 | 2026-03-08
🔥 GPT-5.4发布解读 | 2026-03-08
```
### 文档模板
```markdown
# [Emoji] [原标题]
> 📌 **元信息**
> - 来源:[原始来源]
> - 原文链接:[原始URL]
> - 收录时间:YYYY-MM-DD
> - 内容分类:[技术教程/实战案例/产品文档/学习笔记/热点资讯/设计技能/工具推荐/训练营]
> - 关键词:[关键词1, 关键词2, 关键词3]
---
## 📋 核心要点
[3-5条核心内容摘要]
---
## 📝 正文内容
[完整的转存内容]
---
## 🔗 相关链接
- 原文链接:[原始URL]
- 知识库索引:[素材池文档索引链接]
---
📚 **收录时间**YYYY-MM-DD
🏷️ **分类**[分类名]
🔖 **关键词**[关键词]
```
### 自动更新素材索引
每次收录完成后,必须:
1. **更新多维表格** - 添加新记录到素材池表格
2. **更新素材索引文档** - 在「📚 内容素材池文档索引」中添加条目
3. **更新分类统计** - 更新各分类的文档数量和占比
---
## Workflow
### Step 1: Detect and Parse Link
Extract URL from user message using regex or direct extraction.
### Step 2: Fetch Content
Choose appropriate fetch method based on URL pattern:
**For WeChat articles:**
```python
kimi_fetch(url="https://mp.weixin.qq.com/s/xxx")
```
**For Feishu docs:**
```python
feishu_fetch_doc(doc_id="https://xxx.feishu.cn/docx/xxx")
```
**For general web pages:**
```python
kimi_fetch(url="https://example.com/article")
# or
web_fetch(url="https://example.com/article")
```
### Step 3: Analyze and Categorize
**智能分类判断:**
根据内容特征自动判断分类:
| 判断依据 | 分类 |
|----------|------|
| 包含"安装/配置/部署/教程"等词 | 📖 技术教程 |
| 包含"案例/实战/项目/演示"等词 | 🛠️ 实战案例 |
| 包含"安全/公告/版本/功能"等词 | 📄 产品文档 |
| 包含"学习/成长/指南/笔记"等词 | 💡 学习笔记 |
| 包含"发布/新功能/热点"等词 | 🔥 热点资讯 |
| 包含"设计/Prompt/美学"等词 | 🎨 设计技能 |
| 包含"工具/CLI/插件"等词 | 🔧 工具推荐 |
| 包含"训练营/课程/教学"等词 | 🎓 训练营 |
### Step 4: Process Images (图片处理)
When content contains images, download and upload them to Feishu:
**Image Processing Workflow:**
```python
# 1. Extract image URLs from markdown
import re
image_urls = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', markdown_content)
# 2. Download and upload each image
for img_url in image_urls:
try:
# Download image
local_path = f"/tmp/img_{hash(img_url)}.jpg"
download_image(img_url, local_path)
# Upload to Feishu
upload_result = feishu_im_bot_upload(
action="upload_image",
file_path=local_path
)
# Replace URL in markdown
new_url = upload_result.get("image_key") or img_url
markdown_content = markdown_content.replace(img_url, new_url)
except Exception as e:
# Keep original URL if upload fails
print(f"Failed to process image {img_url}: {e}")
continue
```
**Fallback Strategy:**
- If image upload fails, keep original URL
- Add warning note in document
- Include original source link for reference
### Step 5: Create Feishu Document (按知识库规则存储)
Convert processed markdown to Feishu document with proper organization:
```python
# 1. 确定分类和参数
content_category = classify_content(markdown_content) # 📖/🛠️/📄/💡/🔥/🎨/🔧/🎓
emoji_prefix = get_emoji_prefix(content_category) # 根据分类获取emoji
wiki_node = get_wiki_node_by_category(content_category) # 获取存储目录
# 2. 生成文档标题
doc_title = f"{emoji_prefix} {original_title} | {today_date}"
# 3. 生成文档内容(使用标准模板)
doc_content = f"""# {emoji_prefix} {original_title}
> 📌 **元信息**
> - 来源:{source_name}
> - 原文链接:{original_url}
> - 收录时间:{today_date}
> - 内容分类:{content_category}
> - 关键词:{keywords}
---
## 📋 核心要点
{extract_key_points(markdown_content, 5)}
---
## 📝 正文内容
{processed_markdown_content}
---
## 🔗 相关链接
- 原文链接:{original_url}
- 知识库索引:[Your Index Document URL]
---
📅 **收录时间**{today_date}
🏷️ **分类**{content_category}
🔖 **关键词**{keywords}
"""
# 4. 创建文档到知识库对应目录
feishu_create_doc(
title=doc_title,
markdown=doc_content,
wiki_node=wiki_node # 必须指定存储目录
)
```
**存储目录映射:**
| 分类 | wiki_node | 目录名 |
|------|-----------|--------|
| 所有素材 | `F9pFw9dxTiXmpsk5bNlco704nag` | 04-内容素材 |
**IMPORTANT**:
1. All documents MUST be created under the designated Knowledge Base using wiki_node parameter.
2. Documents must follow the naming convention: `[Emoji] [Title] | [Date]`
3. Documents must use the standard template with metadata section.
### Step 6: Update Knowledge Base Table
Add record to the Bitable knowledge base (ONLY update this specific table):
```python
feishu_bitable_app_table_record(
action="create",
app_token="[Your App Token]", # Configured in MEMORY.md
table_id="[Your Table ID]", # Will use correct table ID from the base
fields={
"关键词": keywords,
"内容分类": content_category,
"文档标题": [{"text": original_title, "type": "text"}],
"来源": [{"text": source_name, "type": "text"}],
"核心要点": [{"text": key_points, "type": "text"}],
"飞书文档链接": {"link": new_doc_url, "text": "飞书文档", "type": "url"},
"原链接": {"link": original_url, "text": "原文链接", "type": "url"} # 新增:存储原始链接
}
)
```
**Table Fields:**
| Field | Type | Description |
|-------|------|-------------|
| 关键词 | Text | Search keywords for the content |
| 内容分类 | Single Select | Category: 📖技术教程/🛠️实战案例/📄产品文档/💡学习笔记/🔥热点资讯/🎨设计技能/🔧工具推荐/🎓训练营 |
| 文档标题 | Text | Title of the archived document |
| 来源 | Text | Original source name |
| 核心要点 | Text | Key points summary (3-5 items) |
| 飞书文档链接 | URL | Link to the created Feishu document |
| 原链接 | URL | **Original source URL** - 新增字段,存储采集的原始链接 |
**IMPORTANT**: Only update the configured knowledge base table. Never create or modify other tables.
### Step 7: Update Content Index Document
After creating the document and updating the table, MUST update the index document:
```python
# 1. 获取当前索引文档内容
index_doc = feishu_fetch_doc(doc_id="[Your Index Doc ID]")
# 2. 在对应分类表格中添加新行
new_index_entry = f"| {original_title} | {source_name} | [查看]({new_doc_url}) |\n"
# 3. 更新分类统计
update_category_stats(content_category)
# 4. 更新总计数
update_total_count()
```
**或者直接追加到索引文档的末尾:**
```python
feishu_update_doc(
doc_id="[Your Index Doc ID]",
mode="append",
markdown=f"""
| {original_title} | {source_name} | [查看]({new_doc_url}) |
"""
)
```
---
## Content Categorization Guide
| Category | Emoji | Description | Examples |
|----------|-------|-------------|----------|
| **技术教程** | 📖 | Step-by-step technical guides | Installation, configuration, API usage |
| **实战案例** | 🛠️ | Real-world implementation examples | Case studies, project demos |
| **产品文档** | 📄 | Product features, security notices | Release notes, security advisories |
| **学习笔记** | 💡 | Conceptual knowledge, methodologies | Best practices, architecture guides |
| **热点资讯** | 🔥 | Breaking news, releases | GPT-5.4, new features |
| **设计技能** | 🎨 | Design, prompts, aesthetics | AJ's prompts, design guides |
| **工具推荐** | 🔧 | Tools, CLI, plugins | gws, trae, autotools |
| **训练营** | 🎓 | Courses, bootcamps, tutorials | OpenClaw bootcamp |
**分类判断优先级:**
1. 优先根据用户指定分类
2. 其次根据标题关键词
3. 最后根据内容特征自动判断
4. 不确定时标记为"待分类",请用户确认
## Delete Record Process
When user replies "删除" or "删除 [keyword]":
```python
# 1. Search records by keyword
feishu_bitable_app_table_record(
action="list",
app_token="[Your App Token]",
table_id="[Your Table ID]",
filter={
"conjunction": "and",
"conditions": [
{"field_name": "关键词", "operator": "contains", "value": [keyword]}
]
}
)
# 2. Confirm deletion
# If multiple found → list for user to select
# If single found → ask for confirmation
# 3. Execute deletion
feishu_bitable_app_table_record(
action="delete",
app_token="[Your App Token]",
table_id="[Your Table ID]",
record_id="record_id_to_delete"
)
```
## Error Handling
### Common Issues
| Error | Cause | Solution |
|-------|-------|----------|
| Fetch timeout | Network issue or heavy content | Retry with longer timeout, or use alternative fetch method |
| Unauthenticated | OAuth token expired or not authed | Trigger `feishu_oauth` to refresh user credentials |
| Permission denied | No write access to Space/Table | Check if user/bot has 'Editor' role in Feishu |
| Content too long | Exceeds API limits | Truncate or split into multiple documents |
| Table update failed | Wrong app_token or table_id | Verify configuration in MEMORY.md |
| Field Missing | "原链接" field not in table | Add the field to Bitable manually or via API |
### Recovery Steps
1. If fetch fails → Try alternative method (kimi_fetch → web_fetch)
2. If Feishu doc creation fails → Check OAuth status
3. If table update fails → Verify table structure and field names
4. Always report partial success (doc created but table not updated)
## Response Template
### 收录成功响应(流式Post格式)
```json
{
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "✅ 收录完成",
"content": [
[
{"tag": "text", "text": "📄 "},
{"tag": "text", "text": "{emoji} {原标题} | {日期}", "style": {"bold": true}}
],
[{"tag": "text", "text": ""}],
[
{"tag": "text", "text": "💡 文档亮点:", "style": {"bold": true}}
],
[
{"tag": "text", "text": "• {亮点1}"}
],
[
{"tag": "text", "text": "• {亮点2}"}
],
[
{"tag": "text", "text": "• {亮点3}"}
],
[{"tag": "text", "text": ""}],
[
{"tag": "text", "text": "🔗 "},
{"tag": "a", "text": "查看飞书文档", "href": "{文档URL}"}
]
]
}
}
}
}
```
**简洁输出示例:**
```
✅ 收录完成
📄 📖 OpenClaw配置指南 | 2026-03-08
💡 文档亮点:
• 完整配置示例,含9大模块详解
• 多Agent扩展配置方案
• 生产环境安全配置建议
🔗 查看飞书文档 → [点击打开](https://xxx.feishu.cn/docx/xxx)
```
### 静默收录响应(流式Post格式)
```json
{
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "✅ 已自动收录",
"content": [
[
{"tag": "text", "text": "📄 "},
{"tag": "text", "text": "{emoji} {原标题}", "style": {"bold": true}}
],
[{"tag": "text", "text": ""}],
[
{"tag": "text", "text": "💡 亮点:{亮点摘要}"}
],
[{"tag": "text", "text": ""}],
[
{"tag": "a", "text": "📎 查看文档", "href": "{文档URL}"}
]
]
}
}
}
}
```
### 批量收录响应(流式Post格式)
```json
{
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "✅ 批量收录完成({N}份)",
"content": [
[
{"tag": "text", "text": "📄 {emoji1} {标题1}", "style": {"bold": true}}
],
[
{"tag": "text", "text": " 💡 {亮点1}"}
],
[
{"tag": "a", "text": " 🔗 查看", "href": "{链接1}"}
],
[{"tag": "text", "text": ""}],
[
{"tag": "text", "text": "📄 {emoji2} {标题2}", "style": {"bold": true}}
],
[
{"tag": "text", "text": " 💡 {亮点2}"}
],
[
{"tag": "a", "text": " 🔗 查看", "href": "{链接2}"}
]
]
}
}
}
}
```
**输出原则:**
1. **必须流式Post格式** - 使用 msg_type: post
2. **只包含3个核心要素:**
- 文件名称(📄 Emoji + 标题 + 日期)
- 文档亮点(💡 3-5条核心要点)
- 飞书链接(🔗 点击查看)
3. **不输出其他信息** - 不显示分类、不显示表格更新、不显示统计
4. **保持简洁** - 每份文档3-5行内容
## Best Practices
1. **Always verify content was fetched correctly** before creating documents
2. **Extract key insights** from the content for the summary
3. **Use appropriate category** based on content nature
4. **Generate relevant keywords** for better searchability
5. **Keep source attribution** clear for copyright respect
6. **Handle partial failures gracefully** - document what succeeded and what failed
7. **Update index document** - Every new document must be added to the index
8. **Follow naming convention** - Use [Emoji] [Title] | [Date] format
9. **Store in correct directory** - Use wiki_node to place in right category
## 收录完成检查清单 (Checklist)
每次收录必须完成以下所有步骤:
- [ ] **执行权限预检**(验证 OAuth 及 Space/Table 写入权限)
- [ ] 获取并处理原始内容(含图片)
- [ ] 智能分类并确定 Emoji 前缀
- [ ] 提取核心要点(3-5条)
- [ ] 生成关键词
- [ ] **创建飞书文档**(使用标准模板,指定 wiki_node)
- [ ] **更新多维表格**(添加完整记录,包含**原链接**字段)
- [ ] **更新文档索引**(在素材索引中添加条目)
- [ ] 发送收录完成通知给用户
**任何一步未完成,视为收录失败!**
## Integration with Memory
After each collection, update MEMORY.md:
```markdown
### YYYY-MM-DD - Content Collection
- **新增收录**: [Title]
- **来源**: [Source]
- **分类**: [Category]
- **知识库状态**: 共[N]条记录
- **索引更新**: ✅ 已更新
```
This skill is part of the core knowledge management system. Execute with care and attention to detail.
---
## 附录:图片处理解决方案
### 问题
原始网页中的图片无法直接显示在飞书文档中(外链限制)
### 解决方案
#### 方案1:自动下载上传(推荐)
**实现步骤**
```python
import re
import requests
import os
def process_images_in_content(markdown_content):
"""
处理 Markdown 内容中的图片:
1. 提取图片URL
2. 下载到本地
3. 上传到飞书
4. 替换为飞书图片链接
"""
# 正则匹配 Markdown 图片: ![alt](url)
img_pattern = r'!\[(.*?)\]\((https?://[^\)]+)\)'
def replace_image(match):
alt_text = match.group(1)
img_url = match.group(2)
try:
# 1. 下载图片
local_path = f"/tmp/img_{abs(hash(img_url)) % 100000}.jpg"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(img_url, headers=headers, timeout=30)
response.raise_for_status()
with open(local_path, 'wb') as f:
f.write(response.content)
# 2. 上传到飞书
upload_result = feishu_im_bot_upload(
action="upload_image",
file_path=local_path
)
image_key = upload_result.get("image_key")
# 3. 清理临时文件
os.remove(local_path)
# 4. 返回飞书图片格式
if image_key:
return f"![{alt_text}]({image_key})"
else:
# 上传失败,保留原链接并添加警告
return f"![{alt_text}]({img_url})\n\n> ⚠️ 图片上传失败,已保留原链接: {img_url}"
except Exception as e:
# 处理失败,保留原链接
return f"![{alt_text}]({img_url})\n\n> ⚠️ 图片处理失败: {str(e)[:50]}"
# 执行替换
processed_content = re.sub(img_pattern, replace_image, markdown_content)
return processed_content
```
**使用方式**
在创建文档之前调用:
```python
# 获取原始内容
raw_content = kimi_fetch(url=link)
# 处理图片
processed_content = process_images_in_content(raw_content)
# 创建文档(使用处理后的内容)
feishu_create_doc(
title=title,
markdown=processed_content
)
```
#### 方案2:保留原链接 + 备用方案
```python
def add_image_fallback_notice(markdown_content, original_url):
"""
在文档末尾添加图片查看说明
"""
notice = f"""
---
## 📎 原始图片资源
本文档中的图片已保留原始链接。
如图片无法显示,请查看原文:
[{original_url}]({original_url})
"""
return markdown_content + notice
```
#### 方案3:批量图片归档
创建一个独立的「图片资源库」多维表格:
```python
# 收录时同时记录图片信息
feishu_bitable_app_table_record(
action="create",
app_token="图片资源库_token",
fields={
"文档标题": doc_title,
"图片URL": img_url,
"图片描述": alt_text,
"原文链接": original_url,
"收录状态": "待上传/已上传/失败"
}
)
```
### 建议实施顺序
1. **短期**(立即):使用方案2,保留原链接并添加查看提示
2. **中期**(本周):实施方案1,自动下载上传核心文章的图片
3. **长期**(可选):建立独立的图片资源库管理系统
### 注意事项
1. **图片大小限制**:飞书图片上传通常限制 10MB
2. **格式支持**JPG、PNG、GIF 等常见格式
3. **网络超时**:下载图片时设置合理的超时时间(30秒)
4. **失败处理**:单张图片失败不应影响整篇文档收录
5. **版权注意**:确保有权限使用原网页中的图片
---
*图片处理方案 v1.0 - 2026-03-05*
@@ -0,0 +1,6 @@
{
"ownerId": "kn7fnbpdh1f84y30j2c75qb6pn81z8ah",
"slug": "content-collector-skill",
"version": "0.1.0",
"publishedAt": 1773042083338
}
@@ -0,0 +1,7 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "daily-stock-analysis",
"installedVersion": "1.0.2",
"installedAt": 1774625590876
}
+138
View File
@@ -0,0 +1,138 @@
---
name: daily-stock-analysis
description: Deterministic daily stock analysis skill for global equities. Use when users need daily analysis, next-trading-day close prediction, prior forecast review, rolling accuracy, and reliable markdown report output.
---
# Daily Stock Analysis
Perform market-aware, evidence-based daily stock analysis with prediction, next-run review, rolling accuracy tracking, and a structured self-evolution mechanism that updates future assumptions from observed forecast errors.
## Hard Rules
1. Read and write files only under `working_directory`.
2. Save new reports only to:
- `<working_directory>/daily-stock-analysis/reports/`
3. Use filename:
- `YYYY-MM-DD-<TICKER>-analysis.md`
4. If same ticker/day file exists, ask user:
- `overwrite` or `new_version` (`-v2`, `-v3`, ...)
- For unattended runs, default to `new_version`
5. Always review history before new prediction.
6. Limit history read count to control token usage:
- Script mode: max 5 files (default)
- Compatibility mode: max 3 files
## Required Scripts (Use First)
1. Plan output path + collect history:
```bash
python3 {baseDir}/scripts/report_manager.py plan \
--workdir <working_directory> \
--ticker <TICKER> \
--run-date <YYYY-MM-DD> \
--versioning auto \
--history-limit 5
```
2. Compute rolling accuracy from existing reports:
```bash
python3 {baseDir}/scripts/calc_accuracy.py \
--workdir <working_directory> \
--ticker <TICKER> \
--windows 1,3,7,30 \
--history-limit 60
```
3. Optional: migrate legacy files after explicit user confirmation:
```bash
python3 {baseDir}/scripts/report_manager.py migrate \
--workdir <working_directory> \
--file <ABS_PATH_1> --file <ABS_PATH_2>
```
## Compatibility Mode (No Python / Small Model)
If Python scripts are unavailable or model capability is limited, switch to minimal mode:
1. Read at most 3 recent reports for the same ticker.
2. Use only a minimal source set:
- one official disclosure source
- one reliable market data source (Yahoo Finance acceptable)
3. Output concise result only:
- recommendation
- `pred_close_t1`
- prior review (`prev_pred_close_t1`, `prev_actual_close_t1`, `AE`, `APE`) if available
- one `improvement_action`
4. Save report with same filename rules in canonical reports directory.
See `references/minimal_mode.md`.
## Minimal Run Protocol
1. Resolve ticker/exchange/market (ask if ambiguous).
2. Run `report_manager.py plan`.
3. Read `history_files` returned by script.
4. If `legacy_files` exist, list all absolute paths and ask whether to migrate.
5. Gather data using `references/sources.md` + `references/search_queries.md`.
6. Run `calc_accuracy.py` for consistent metrics.
7. Render report using `references/report_template.md`.
8. Save to `selected_output_file` returned by `report_manager.py`.
## Required Output Fields
Must include:
- `recommendation`
- `pred_close_t1`
- `prev_pred_close_t1`
- `prev_actual_close_t1`
- `AE`, `APE`
- rolling strict/loose accuracy fields
- `improvement_actions`
## Self-Improvement (Required)
Each run must include 1-3 concrete `improvement_actions` from recent misses and use them in the next run.
Do not skip this step.
## Scheduling Recommendation
Recommend users set this as a weekday recurring task (for example 10:00 local time) to keep prediction-review windows continuous.
## References
Default:
- `references/workflow.md`
- `references/report_template.md`
- `references/metrics.md`
- `references/search_queries.md`
- `references/sources.md`
- `references/minimal_mode.md`
- `references/security.md`
Deep-dive only (`full_report` mode):
- `references/fundamental-analysis.md`
- `references/technical-analysis.md`
- `references/financial-metrics.md`
## Compliance
Always append:
"This content is for research and informational purposes only and does not constitute investment advice or a return guarantee. Markets are risky; invest with caution."
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn7arpc65p9wdnhbw70435rrf181jvtk",
"slug": "daily-stock-analysis",
"version": "1.0.2",
"publishedAt": 1772265206033
}
@@ -0,0 +1,94 @@
# Financial Metrics Reference
Use these formulas and interpretations consistently. Metric availability may differ across US/CN/HK data providers.
## 1. Profitability
1. Gross Margin
- Formula: `(Revenue - COGS) / Revenue * 100%`
- Use: Pricing power and production efficiency.
2. Operating Margin
- Formula: `Operating Income / Revenue * 100%`
- Use: Core operating efficiency.
3. Net Margin
- Formula: `Net Income / Revenue * 100%`
- Use: End-to-end profitability quality.
4. ROE
- Formula: `Net Income / Average Equity * 100%`
- Use: Equity capital efficiency.
5. ROIC
- Formula: `NOPAT / Invested Capital * 100%`
- Use: Capital allocation quality across debt and equity.
## 2. Growth
1. Revenue Growth (YoY / QoQ)
- Formula: `(Current Revenue - Prior Revenue) / Prior Revenue * 100%`
2. EPS Growth
- Formula: `(Current EPS - Prior EPS) / Prior EPS * 100%`
3. Multi-year CAGR
- Formula: `(Ending / Beginning)^(1/Years) - 1`
## 3. Valuation
1. P/E (Trailing / Forward)
- Formula: `Price / EPS`
2. PEG
- Formula: `P/E / Earnings Growth Rate`
3. P/B
- Formula: `Price / Book Value Per Share`
4. P/S
- Formula: `Market Cap / Revenue`
5. EV
- Formula: `Market Cap + Total Debt - Cash`
6. EV/EBITDA
- Formula: `EV / EBITDA`
7. EV/Sales
- Formula: `EV / Revenue`
## 4. Leverage and Liquidity
1. Debt-to-Equity
- Formula: `Total Debt / Total Equity`
2. Interest Coverage
- Formula: `EBIT / Interest Expense`
3. Current Ratio
- Formula: `Current Assets / Current Liabilities`
4. Quick Ratio
- Formula: `(Current Assets - Inventory) / Current Liabilities`
## 5. Cash Flow Quality
1. Free Cash Flow (FCF)
- Formula: `Operating Cash Flow - Capital Expenditures`
2. FCF Yield
- Formula: `FCF Per Share / Price * 100%`
3. Cash Conversion
- Formula: `FCF / Net Income`
## 6. Interpretation Guidance
1. Always compare metrics against:
- Company historical range
- Sector and direct peers
- Current macro regime
2. Avoid single-metric conclusions.
3. Flag where accounting standards or reporting cadence reduce cross-market comparability.
@@ -0,0 +1,75 @@
# Fundamental Analysis Reference
Use this framework in both `daily` and `full_report` modes. Keep outputs concise unless full report is requested.
## 1. Business Quality
Assess:
1. Moat quality:
- Brand, network effects, switching costs, cost advantage, IP/regulatory barriers.
2. Management quality:
- Capital allocation discipline, communication quality, execution consistency.
3. Business model durability:
- Revenue concentration, customer concentration, geographic risk, pricing power.
## 2. Financial Health
Focus areas:
1. Profitability trend:
- Gross, operating, net margin direction.
2. Growth quality:
- Revenue and earnings growth consistency, segment contribution quality.
3. Balance sheet:
- Debt burden, liquidity, refinancing risk, cash buffer.
4. Cash flow quality:
- OCF consistency, FCF conversion, capex intensity.
## 3. Valuation Lens
Use multiple perspectives:
1. Relative multiples:
- P/E, PEG, P/B, P/S, EV/EBITDA, EV/Sales.
2. Historical range context:
- Current valuation vs own history.
3. Peer context:
- Premium/discount vs direct peers and rationale.
## 4. Risk Framework
Map risks by category:
1. Company-specific:
- Product concentration, customer concentration, execution risk, governance issues.
2. Market/macro:
- Rate sensitivity, FX exposure, commodity sensitivity, policy risk.
3. Event risk:
- Earnings, regulatory approvals, legal actions, financing events.
## 5. Market-Specific Notes (US/CN/HK)
1. Data depth may vary by market and language.
2. Prefer exchange filings and official disclosure portals in each market.
3. Align accounting period labels and fiscal calendars before comparison.
4. Flag where metric comparability is limited.
## 6. Output Guidance
For daily mode, include:
- 2-3 key fundamental drivers
- 1-2 valuation signals
- top downside risks
For full report mode, include full multi-year trend tables and peer comparison.
@@ -0,0 +1,99 @@
# Metrics Definition
Use these definitions consistently across all reports.
## 1. Core Error Metrics
Let:
- `pred` = predicted close for target session
- `actual` = official actual close for that session
Compute:
- Absolute Error (AE): `|pred - actual|`
- Absolute Percentage Error (APE): `|pred - actual| / actual * 100%`
## 2. Hit Criteria
Report two hit criteria in parallel:
- Strict hit: `APE <= 1%`
- Loose hit: `APE <= 2%`
These thresholds are the default correctness criteria for predicted close price.
## 3. Rolling Accuracy Windows
For each window `W` (1d, 3d, 7d, 30d, custom):
- `strict_accuracy_W = strict_hits_W / n_W`
- `loose_accuracy_W = loose_hits_W / n_W`
Where `n_W` is number of valid forecast/actual pairs in that window.
## 4. Optional Direction Accuracy
Let direction be sign of close-to-close return.
- Direction hit if predicted direction equals realized direction.
- `direction_accuracy_W = direction_hits_W / n_W`
Use only when direction labels are explicitly available.
## 5. Forecast Correctness Score (Optional)
For a single forecast, you may map APE to a score:
- `correctness_score = max(0, 100 - 50 * APE_percent)`
Examples:
- `APE = 0.8%` -> score `60`
- `APE = 1.5%` -> score `25`
- `APE >= 2.0%` -> score `0` (or near 0)
## 6. Sample Size and Insufficient Data Rules
1. Never pad missing samples.
2. If `n_W = 0`, output `N/A` for the window.
3. If `0 < n_W < target_window_size`, output partial result and annotate as partial.
4. Always display `n_W` beside each window metric.
## 7. Adjustment and Comparability Rules
1. Prefer adjusted price series when corporate actions materially affect comparability.
2. If non-adjusted close is used, state it explicitly.
3. Keep forecast and actual on the same price basis.
## 8. Improvement Trend Metrics
Track whether forecast quality is improving over time:
1. `delta_APE_7d_vs_prev7d`
- Difference between current 7-day average APE and previous 7-day average APE.
2. `delta_strict_hit_rate_7d`
- Change in strict hit rate versus previous 7-day block.
3. `trend_label`
- `improving`, `stable`, or `degrading` based on combined delta signals.
## 9. Reporting Format (Minimum)
Every report should include:
1. Prior-session review row:
- `prev_pred_close_t1`, `prev_actual_close_t1`, `AE`, `APE`, strict/loose hit status
2. Rolling table with at least:
- 1d, 3d, 7d, 30d, optional custom
- strict accuracy, loose accuracy, optional direction accuracy
- sample size `n`
3. One-line interpretation:
- whether model performance is improving, stable, or degrading
4. Improvement block:
- what changed from review
- what will be adjusted in next run
@@ -0,0 +1,33 @@
# Minimal Compatibility Mode
Use this mode when Python scripts are unavailable or model capability is limited.
## Goal
Maximize success rate and correctness with minimal token and logic complexity.
## Rules
1. Read at most 3 recent reports for the same ticker.
2. Use minimal sources:
- one official disclosure source
- one reliable market data source (Yahoo Finance acceptable)
3. Keep output short and deterministic.
4. Still include one self-improvement action from prior misses.
## Minimal Output Schema
- `recommendation`: Buy/Hold/Sell/Watch
- `pred_close_t1`: point estimate
- `prev_pred_close_t1`: if available, else `N/A`
- `prev_actual_close_t1`: if available, else `N/A/pending`
- `AE`, `APE`: if available, else `N/A`
- `improvement_actions`: exactly 1 item
- `status`: `ok|pending_data|blocked`
## Minimal Source Checklist
1. Official disclosure page (exchange/regulator/IR)
2. Market quote page (for example Yahoo Finance quote)
If the two sources conflict on critical values, set confidence to `Low`.
@@ -0,0 +1,99 @@
# Report Template (Strict)
Use this template exactly. Keep key names unchanged for downstream parsing.
```markdown
---
version: 1
run_date: <YYYY-MM-DD>
run_time_local: <YYYY-MM-DD HH:mm TZ>
mode: <daily|daily_minimal|full_report>
ticker: <TICKER>
exchange: <EXCHANGE>
market: <TEXT>
report_dir: <working_directory>/daily-stock-analysis/reports/
output_file: <YYYY-MM-DD-TICKER-analysis.md or -vN.md>
report_versioning_mode: <overwrite|new_version>
history_window_days: <N>
recommendation: <Buy|Hold|Sell|Watch>
recommendation_triggers: <ENTRY/EXIT/INVALIDATION SUMMARY>
pred_close_t1: <NUMBER>
pred_range_t1: <LOW-HIGH or N/A>
pred_confidence: <High|Medium|Low>
pred_assumptions: <SHORT TEXT>
prev_pred_close_t1: <NUMBER or N/A>
prev_actual_close_t1: <NUMBER or pending or N/A>
AE: <NUMBER or N/A>
APE: <PERCENT or N/A>
strict_hit: <true|false|N/A>
loose_hit: <true|false|N/A>
acc_1d_strict: <PERCENT or N/A>
acc_1d_loose: <PERCENT or N/A>
acc_3d_strict: <PERCENT or N/A>
acc_3d_loose: <PERCENT or N/A>
acc_7d_strict: <PERCENT or N/A>
acc_7d_loose: <PERCENT or N/A>
acc_30d_strict: <PERCENT or N/A>
acc_30d_loose: <PERCENT or N/A>
acc_custom_strict: <PERCENT or N/A>
acc_custom_loose: <PERCENT or N/A>
improvement_actions:
- <ACTION_1>
- <ACTION_2 or N/A>
- <ACTION_3 or N/A>
status: <ok|pending_data|blocked>
status_note: <SHORT TEXT>
---
# Daily Stock Analysis - <TICKER> (<EXCHANGE>)
## 1) Market Snapshot
- Last/Close: <VALUE>
- Session Range: <LOW-HIGH>
- Volume/Volatility: <SUMMARY>
- Thesis: <BULLISH/NEUTRAL/BEARISH + concise rationale>
## 2) Recommendation
- Recommendation: <Buy/Hold/Sell/Watch>
- Trigger Conditions: <ENTRY/EXIT/INVALIDATION>
- Risk Controls: <SHORT TEXT>
## 3) Next Trading Day Prediction
- Point Forecast: <pred_close_t1>
- Range: <pred_range_t1>
- Confidence: <pred_confidence>
- Assumptions: <pred_assumptions>
## 4) Prior Forecast Review
- Previous Forecast: <prev_pred_close_t1>
- Actual Close: <prev_actual_close_t1>
- AE / APE: <AE> / <APE>
- Attribution: <WHY HIT OR MISS>
## 5) Rolling Accuracy
| Window | Strict | Loose |
|---|---:|---:|
| 1d | <acc_1d_strict> | <acc_1d_loose> |
| 3d | <acc_3d_strict> | <acc_3d_loose> |
| 7d | <acc_7d_strict> | <acc_7d_loose> |
| 30d | <acc_30d_strict> | <acc_30d_loose> |
| Custom | <acc_custom_strict> | <acc_custom_loose> |
## 6) Self-Improvement Actions for Next Run
1. <ACTION_1>
2. <ACTION_2 or N/A>
3. <ACTION_3 or N/A>
## 7) Sources (with timestamp)
- <SOURCE_1>
- <SOURCE_2>
- <SOURCE_3>
## 8) Disclaimer
This content is for research and informational purposes only and does not constitute investment advice or a return guarantee. Markets are risky; invest with caution.
```
@@ -0,0 +1,48 @@
# Search Query Templates (Concise)
Use these templates with search engines and `site:` filters.
Detailed source list is in `references/sources.md`.
## 1) Identity and Listing
- `<COMPANY> ticker symbol exchange`
- `<TICKER> exchange listing market`
## 2) Official Filings and Disclosures
- `<TICKER> official filings latest`
- `<COMPANY> investor relations latest release`
- `site:sec.gov <TICKER> 10-Q OR 10-K OR 8-K`
- `site:hkexnews.hk <TICKER> announcement`
- `site:sse.com.cn <TICKER> 公告`
- `site:szse.cn <TICKER> 公告`
## 3) Market Data and Price Context
- `site:finance.yahoo.com <TICKER> quote`
- `<TICKER> latest close open high low volume`
- `<TICKER> 52 week range market cap`
## 4) News and Analyst Context
- `site:reuters.com <TICKER> earnings guidance`
- `site:bloomberg.com <TICKER> stock news`
- `<TICKER> analyst rating target price`
## 5) Technical Context
- `<TICKER> RSI MACD moving average`
- `<TICKER> support resistance trend`
## 6) Macro Context (if used in thesis)
- `<MARKET> benchmark index today`
- `<MARKET> central bank policy rate outlook`
- `US 10Y yield today`
## Data Quality Rules
1. Prefer Tier-1 official sources first.
2. Cross-check critical values with two independent sources.
3. Record source URL and timestamp in report.
@@ -0,0 +1,27 @@
# Security and Privacy Rules
These rules are mandatory for both script mode and compatibility mode.
## Scope Control
1. Operate only under `working_directory`.
2. Do not read, move, or write files outside `working_directory`.
3. Do not follow symlinks when scanning report files.
## Data Minimization
1. Read only report files matching:
- `YYYY-MM-DD-<TICKER>-analysis.md`
- `YYYY-MM-DD-<TICKER>-analysis-vN.md`
2. Parse only required metadata fields.
3. Cap historical reads:
- script mode default: 5 files
- compatibility mode: 3 files
## Script Safety
1. Scripts are local-file utilities only; no network calls.
2. Migration is explicit and non-destructive:
- move only user-confirmed files
- skip when target already exists
3. If a safety check fails, return `blocked` with reason.
@@ -0,0 +1,104 @@
# Authoritative Information Sources
Use search engines with `site:` filters to prioritize authoritative sources.
## Source Priority
1. Tier 1 (Primary / official)
- Exchange and regulator disclosures
- Company investor-relations pages
- Official macro data publishers
2. Tier 2 (High-quality financial media/data)
- Yahoo Finance, Reuters, Bloomberg, WSJ, CNBC, MarketWatch
3. Tier 3 (Supporting context)
- TradingView, StockCharts, sector/ETF summaries
For critical values (close price, earnings, guidance, major filings), cross-check with at least two independent sources.
## Tier 1: Exchange and Regulatory Sources
### Global baseline
- Company Investor Relations pages
- Official exchange announcements for the ticker listing venue
### United States
- SEC EDGAR: [https://www.sec.gov/edgar/searchedgar/companysearch](https://www.sec.gov/edgar/searchedgar/companysearch)
- Nasdaq company pages: [https://www.nasdaq.com](https://www.nasdaq.com)
- NYSE company pages: [https://www.nyse.com](https://www.nyse.com)
### Hong Kong
- HKEXnews: [https://www.hkexnews.hk](https://www.hkexnews.hk)
### Mainland China
- SSE disclosures: [https://www.sse.com.cn/disclosure/](https://www.sse.com.cn/disclosure/)
- SZSE disclosures: [https://www.szse.cn/disclosure/](https://www.szse.cn/disclosure/)
### Japan
- JPX: [https://www.jpx.co.jp/english/](https://www.jpx.co.jp/english/)
- TDnet (Timely Disclosure): [https://www.release.tdnet.info/](https://www.release.tdnet.info/)
### United Kingdom
- London Stock Exchange RNS: [https://www.londonstockexchange.com/news](https://www.londonstockexchange.com/news)
### Europe (multi-country)
- Euronext news/disclosures: [https://live.euronext.com/en/markets](https://live.euronext.com/en/markets)
## Tier 2: High-Quality Financial Data and News
- Yahoo Finance: [https://finance.yahoo.com](https://finance.yahoo.com)
- Reuters Markets: [https://www.reuters.com/markets/](https://www.reuters.com/markets/)
- Bloomberg Markets: [https://www.bloomberg.com/markets](https://www.bloomberg.com/markets)
- WSJ Markets: [https://www.wsj.com/market-data](https://www.wsj.com/market-data)
- CNBC Markets: [https://www.cnbc.com/markets/](https://www.cnbc.com/markets/)
- MarketWatch: [https://www.marketwatch.com](https://www.marketwatch.com)
- Morningstar (supporting valuation context): [https://www.morningstar.com](https://www.morningstar.com)
## Tier 1 Macro Data (for market regime context)
### United States
- U.S. Treasury rates: [https://home.treasury.gov](https://home.treasury.gov)
- Federal Reserve (FRED): [https://fred.stlouisfed.org](https://fred.stlouisfed.org)
- BLS: [https://www.bls.gov](https://www.bls.gov)
- BEA: [https://www.bea.gov](https://www.bea.gov)
### Global
- IMF Data: [https://www.imf.org/en/Data](https://www.imf.org/en/Data)
- World Bank Data: [https://data.worldbank.org](https://data.worldbank.org)
- ECB: [https://www.ecb.europa.eu](https://www.ecb.europa.eu)
- BoE: [https://www.bankofengland.co.uk](https://www.bankofengland.co.uk)
- BoJ: [https://www.boj.or.jp/en/](https://www.boj.or.jp/en/)
## Technical/Charting Support (Tier 3)
- TradingView: [https://www.tradingview.com](https://www.tradingview.com)
- StockCharts: [https://stockcharts.com](https://stockcharts.com)
## Search Engine Patterns
Use search engines with domain filters:
- `site:finance.yahoo.com <TICKER> quote`
- `site:reuters.com <TICKER> earnings`
- `site:sec.gov <TICKER> 10-Q`
- `site:hkexnews.hk <TICKER> announcement`
- `site:sse.com.cn <TICKER> 公告`
- `site:szse.cn <TICKER> 公告`
- `site:investor.<company-domain> earnings release`
## Minimum Source Set Per Run
At least include:
1. One Tier-1 disclosure source
2. One Tier-2 market data source (Yahoo Finance is acceptable baseline)
3. One Tier-2/Tier-1 news source
4. One macro source if macro is cited in thesis
## Compatibility Mode Minimum Source Set
When running in minimal compatibility mode, use:
1. One Tier-1 disclosure source
2. One Tier-2 market data source (Yahoo Finance acceptable)
@@ -0,0 +1,77 @@
# Technical Analysis Reference
Use technical analysis as a decision support layer, not as a standalone certainty signal.
## 1. Trend Framework
1. Moving averages:
- 20/50/200-period moving averages as baseline trend map.
- Bullish regime: price above key moving averages with supportive slope.
- Bearish regime: price below key moving averages with negative slope.
2. Trend strength:
- Confirm with higher highs/higher lows (uptrend) or lower highs/lower lows (downtrend).
## 2. Momentum Framework
1. RSI:
- Overbought > 70, oversold < 30.
- Use divergence vs price as an early warning, not a standalone trigger.
2. MACD:
- Track line/signal crossovers and histogram trend.
- Prefer signals aligned with broader trend.
## 3. Volatility and Structure
1. ATR context:
- Use ATR expansion/contraction to assess regime change risk.
2. Bollinger context:
- Squeeze can precede expansion.
- Band walk can persist in strong trends.
## 4. Support and Resistance
Map levels from:
- Recent swing highs/lows
- Volume clusters
- Moving-average confluence
- Psychological round numbers
Use level breaks with volume confirmation where possible.
## 5. Volume Confirmation
1. Breakout quality improves with volume expansion.
2. Weak volume breakouts carry higher failure risk.
3. Divergence between price trend and volume trend can signal exhaustion.
## 6. Multi-Timeframe Alignment
1. Use higher timeframe (weekly/daily) for primary bias.
2. Use lower timeframe (daily/intraday where available) for timing.
3. Do not let lower timeframe noise override higher timeframe structure without strong evidence.
## 7. Signal Quality Rules
1. Require at least two independent confirmations before strong directional calls.
2. Mark low-confidence calls when indicators conflict.
3. Define invalidation level for every directional view.
## 8. Market-Specific Notes (US/CN/HK)
1. Liquidity and session structure differ by market.
2. Gap behavior and close auction effects may vary.
3. Technical indicator reliability can degrade during event-driven sessions.
## 9. Daily Output Guidance
At minimum provide:
- Trend state (bullish/neutral/bearish)
- 2-3 key levels
- RSI/MACD summary
- volume confirmation status
- invalidation condition
@@ -0,0 +1,104 @@
# Workflow (Command-First)
Use this sequence exactly.
## 0) Choose Execution Mode
Use `script` mode by default.
Switch to `compatibility` mode when:
- `python3` is unavailable, or
- model capability is low and deterministic minimal output is preferred.
## 1) Resolve Instrument
- Resolve `ticker`, `exchange`, `market`, and next valid trading day.
- If ticker is ambiguous, stop and ask user.
## 2) Plan Files and History
Run:
```bash
python3 {baseDir}/scripts/report_manager.py plan \
--workdir <working_directory> \
--ticker <TICKER> \
--run-date <YYYY-MM-DD> \
--versioning auto \
--history-limit 5
```
Use returned JSON fields:
- `selected_output_file`
- `requires_user_choice`
- `history_files`
- `legacy_files`
If `requires_user_choice=true`, ask user `overwrite` vs `new_version`.
Read only `history_files` returned by script (default max 5).
## 3) Legacy Compatibility (Optional Migration)
- Read legacy files from `legacy_files` for review history.
- If user agrees to migrate, run:
```bash
python3 {baseDir}/scripts/report_manager.py migrate \
--workdir <working_directory> \
--file <ABS_PATH_1> --file <ABS_PATH_2>
```
Security: only process files under `working_directory`.
## 4) Collect New Data
- Use `references/sources.md` tier priority.
- Use `references/search_queries.md` templates.
- For critical values, cross-check with at least 2 sources.
## 5) Compute Accuracy via Script
Run:
```bash
python3 {baseDir}/scripts/calc_accuracy.py \
--workdir <working_directory> \
--ticker <TICKER> \
--windows 1,3,7,30 \
--history-limit 60
```
Use script output to fill rolling accuracy fields.
## 6) Generate Report
- Render with `references/report_template.md`.
- Keep all required frontmatter keys.
- Include `improvement_actions`.
## 7) Persist and Return
- Save to `selected_output_file` from step 2.
- Return summary + absolute file path + pending/blocked status.
## 8) Recommended Operation
Use recurring weekday schedule to stabilize review windows and success rate.
## Compatibility Mode (Fallback)
When scripts cannot run:
1. Manually locate report files only under `working_directory`.
2. Read at most 3 recent same-ticker reports.
3. Collect minimal sources:
- one official disclosure source
- one reliable market data source
4. Produce minimal output:
- recommendation
- `pred_close_t1`
- prior review metrics (if available)
- one `improvement_action`
5. Save report in canonical reports directory using standard filename rules.
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Shared helpers for daily-stock-analysis scripts."""
from __future__ import annotations
import os
import re
from dataclasses import dataclass
from datetime import date
from typing import Dict, List, Optional
FILENAME_RE = re.compile(
r"^(?P<run_date>\d{4}-\d{2}-\d{2})-(?P<ticker>[A-Za-z0-9._-]+)-analysis(?:-v(?P<version>\d+))?\.md$",
re.IGNORECASE,
)
@dataclass(frozen=True)
class ReportFile:
path: str
run_date: str
ticker: str
version: int
in_canonical_dir: bool
def canonical_reports_dir(workdir: str) -> str:
return os.path.join(os.path.abspath(workdir), "daily-stock-analysis", "reports")
def compatible_dirs(workdir: str) -> List[str]:
root = os.path.abspath(workdir)
return [
canonical_reports_dir(root),
os.path.join(root, "daily-stock-analysis"),
root,
]
def is_within_workdir(path: str, workdir: str) -> bool:
root = os.path.realpath(os.path.abspath(workdir))
target = os.path.realpath(os.path.abspath(path))
return target == root or target.startswith(root + os.sep)
def parse_filename(name: str) -> Optional[Dict[str, str]]:
match = FILENAME_RE.match(name)
if not match:
return None
return {
"run_date": match.group("run_date"),
"ticker": match.group("ticker").upper(),
"version": str(int(match.group("version") or "1")),
}
def discover_reports(workdir: str, ticker: str) -> List[ReportFile]:
root = os.path.abspath(workdir)
ticker_upper = ticker.upper()
canonical_dir = canonical_reports_dir(root)
seen = set()
records: List[ReportFile] = []
for directory in compatible_dirs(root):
if not is_within_workdir(directory, root):
continue
if not os.path.isdir(directory):
continue
for entry in os.scandir(directory):
# Never follow symlinks for safety/privacy.
if not entry.is_file(follow_symlinks=False):
continue
parsed = parse_filename(entry.name)
if not parsed:
continue
if parsed["ticker"] != ticker_upper:
continue
abs_path = os.path.abspath(entry.path)
real_path = os.path.realpath(abs_path)
if real_path in seen:
continue
seen.add(real_path)
records.append(
ReportFile(
path=abs_path,
run_date=parsed["run_date"],
ticker=parsed["ticker"],
version=int(parsed["version"]),
in_canonical_dir=os.path.dirname(abs_path) == canonical_dir,
)
)
def sort_key(record: ReportFile):
try:
d = date.fromisoformat(record.run_date)
except ValueError:
d = date.min
return (d, record.version, 1 if record.in_canonical_dir else 0)
return sorted(records, key=sort_key, reverse=True)
def read_frontmatter(path: str) -> Dict[str, str]:
try:
with open(path, "r", encoding="utf-8") as f:
first_line = f.readline()
if first_line.strip() != "---":
return {}
# Read only a bounded header section to avoid loading large files.
frontmatter: Dict[str, str] = {}
total_chars = len(first_line)
for _ in range(200):
line = f.readline()
if not line:
break
total_chars += len(line)
if total_chars > 64 * 1024:
break
raw = line.rstrip("\n")
if raw.strip() == "---":
break
if not raw.strip():
continue
if raw.startswith(" - "):
continue
if ":" not in raw:
continue
key, value = raw.split(":", 1)
frontmatter[key.strip()] = value.strip()
return frontmatter
except (OSError, UnicodeDecodeError):
return {}
def parse_float(value: Optional[str]) -> Optional[float]:
if value is None:
return None
text = value.strip()
if not text:
return None
if text.upper() in {"N/A", "NA", "NONE", "NULL", "PENDING"}:
return None
text = text.replace(",", "")
if text.endswith("%"):
text = text[:-1]
try:
return float(text)
except ValueError:
return None
def parse_bool(value: Optional[str]) -> Optional[bool]:
if value is None:
return None
text = value.strip().lower()
if text in {"true", "yes", "1"}:
return True
if text in {"false", "no", "0"}:
return False
return None
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""Compute rolling forecast accuracy from existing report files."""
from __future__ import annotations
import argparse
import json
import os
from statistics import mean
from typing import Dict, List
from _report_utils import discover_reports, parse_bool, parse_float, read_frontmatter
def _window_list(text: str) -> List[int]:
windows = []
for item in text.split(","):
item = item.strip()
if not item:
continue
value = int(item)
if value <= 0:
continue
if value not in windows:
windows.append(value)
return windows or [1, 3, 7, 30]
def _build_review_rows(workdir: str, ticker: str, history_limit: int) -> List[Dict[str, object]]:
reports = discover_reports(workdir, ticker)[:history_limit]
rows: List[Dict[str, object]] = []
seen_run_date = set()
for report in reports:
# Keep the newest report for each run_date to avoid same-day duplicate counting.
if report.run_date in seen_run_date:
continue
frontmatter = read_frontmatter(report.path)
ape = parse_float(frontmatter.get("APE"))
strict = parse_bool(frontmatter.get("strict_hit"))
loose = parse_bool(frontmatter.get("loose_hit"))
if strict is None and ape is not None:
strict = ape <= 1.0
if loose is None and ape is not None:
loose = ape <= 2.0
if ape is None and strict is None and loose is None:
continue
rows.append(
{
"run_date": report.run_date,
"path": report.path,
"ape": ape,
"strict_hit": strict,
"loose_hit": loose,
}
)
seen_run_date.add(report.run_date)
return rows
def _rate(hit_count: int, total: int):
if total == 0:
return None
return round(hit_count * 100.0 / total, 2)
def compute_accuracy(workdir: str, ticker: str, windows: List[int], history_limit: int) -> Dict[str, object]:
rows = _build_review_rows(workdir, ticker, history_limit)
metrics = {}
for window in windows:
sample = rows[:window]
n = len(sample)
strict_hits = sum(1 for r in sample if r["strict_hit"] is True)
loose_hits = sum(1 for r in sample if r["loose_hit"] is True)
ape_values = [r["ape"] for r in sample if isinstance(r["ape"], float)]
metrics[str(window)] = {
"n": n,
"strict_rate_percent": _rate(strict_hits, n),
"loose_rate_percent": _rate(loose_hits, n),
"avg_ape_percent": round(mean(ape_values), 4) if ape_values else None,
}
latest = rows[0] if rows else None
return {
"ticker": ticker.upper(),
"workdir": os.path.abspath(workdir),
"windows": metrics,
"review_samples": len(rows),
"latest_review": latest,
"status": "ok" if rows else "insufficient_history",
"security_scope": "working_directory_only",
}
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Calculate rolling forecast accuracy.")
parser.add_argument("--workdir", default=os.getcwd())
parser.add_argument("--ticker", required=True)
parser.add_argument("--windows", default="1,3,7,30")
parser.add_argument("--history-limit", type=int, default=60)
return parser.parse_args()
def main() -> int:
args = _parse_args()
result = compute_accuracy(
workdir=args.workdir,
ticker=args.ticker,
windows=_window_list(args.windows),
history_limit=max(args.history_limit, 1),
)
print(json.dumps(result, indent=2, ensure_ascii=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""Deterministic report path and migration manager for daily-stock-analysis."""
from __future__ import annotations
import argparse
import json
import os
import shutil
from datetime import date
from typing import Dict, List
from _report_utils import (
FILENAME_RE,
canonical_reports_dir,
discover_reports,
is_within_workdir,
)
def _same_day_versions_in_canonical(
reports: List, reports_dir: str, run_date: str, ticker_upper: str
) -> List[int]:
versions = []
for report in reports:
if report.run_date != run_date:
continue
if report.ticker != ticker_upper:
continue
if os.path.dirname(report.path) != reports_dir:
continue
versions.append(report.version)
return versions
def plan_output(
workdir: str,
ticker: str,
run_date: str,
versioning: str,
unattended: bool,
history_limit: int,
) -> Dict[str, object]:
root = os.path.abspath(workdir)
ticker_upper = ticker.upper()
reports_dir = canonical_reports_dir(root)
os.makedirs(reports_dir, exist_ok=True)
reports = discover_reports(root, ticker_upper)
history_files = [r.path for r in reports[:history_limit]]
legacy_files = [r.path for r in reports if not r.in_canonical_dir]
base_name = f"{run_date}-{ticker_upper}-analysis.md"
base_path = os.path.join(reports_dir, base_name)
base_exists = os.path.exists(base_path)
requires_user_choice = False
selected_mode = "new_file"
selected_path = base_path
if base_exists:
if versioning == "overwrite":
selected_mode = "overwrite"
elif versioning == "new_version":
selected_mode = "new_version"
else:
if unattended:
selected_mode = "new_version"
else:
selected_mode = "new_version"
requires_user_choice = True
if selected_mode == "new_version":
versions = _same_day_versions_in_canonical(
reports, reports_dir, run_date, ticker_upper
)
next_version = max(versions or [1]) + 1
selected_path = os.path.join(
reports_dir, f"{run_date}-{ticker_upper}-analysis-v{next_version}.md"
)
return {
"ticker": ticker_upper,
"workdir": root,
"reports_dir": reports_dir,
"base_output_file": base_path,
"selected_output_file": selected_path,
"selected_versioning_mode": selected_mode,
"requires_user_choice": requires_user_choice,
"history_files": history_files,
"legacy_files": legacy_files,
"history_limit": history_limit,
"security_scope": "working_directory_only",
}
def migrate_files(workdir: str, files: List[str]) -> Dict[str, object]:
root = os.path.abspath(workdir)
reports_dir = canonical_reports_dir(root)
os.makedirs(reports_dir, exist_ok=True)
moved = []
skipped = []
for raw_path in files:
src = os.path.abspath(raw_path)
if not is_within_workdir(src, root):
skipped.append({"file": src, "reason": "outside_workdir"})
continue
if not os.path.isfile(src):
skipped.append({"file": src, "reason": "not_file"})
continue
if os.path.islink(src):
skipped.append({"file": src, "reason": "symlink_not_allowed"})
continue
if not FILENAME_RE.match(os.path.basename(src)):
skipped.append({"file": src, "reason": "filename_not_supported"})
continue
dst = os.path.join(reports_dir, os.path.basename(src))
if os.path.abspath(src) == os.path.abspath(dst):
skipped.append({"file": src, "reason": "already_in_reports_dir"})
continue
if os.path.exists(dst):
# Keep migration deterministic and non-destructive.
skipped.append({"file": src, "reason": "target_exists"})
continue
try:
shutil.move(src, dst)
except OSError as exc:
skipped.append({"file": src, "reason": f"move_failed:{exc}"})
continue
moved.append({"from": src, "to": dst})
return {
"reports_dir": reports_dir,
"moved": moved,
"skipped": skipped,
"security_scope": "working_directory_only",
}
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Manage report paths and migrations.")
subparsers = parser.add_subparsers(dest="command", required=True)
plan_parser = subparsers.add_parser("plan", help="Plan output path and history usage.")
plan_parser.add_argument("--workdir", default=os.getcwd())
plan_parser.add_argument("--ticker", required=True)
plan_parser.add_argument("--run-date", default=date.today().isoformat())
plan_parser.add_argument(
"--versioning",
choices=["auto", "overwrite", "new_version"],
default="auto",
)
plan_parser.add_argument("--unattended", action="store_true")
plan_parser.add_argument("--history-limit", type=int, default=5)
migrate_parser = subparsers.add_parser(
"migrate", help="Move legacy report files into canonical reports directory."
)
migrate_parser.add_argument("--workdir", default=os.getcwd())
migrate_parser.add_argument("--file", action="append", required=True)
return parser.parse_args()
def main() -> int:
args = _parse_args()
if args.command == "plan":
result = plan_output(
workdir=args.workdir,
ticker=args.ticker,
run_date=args.run_date,
versioning=args.versioning,
unattended=args.unattended,
history_limit=max(args.history_limit, 1),
)
else:
result = migrate_files(workdir=args.workdir, files=args.file)
print(json.dumps(result, indent=2, ensure_ascii=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+36
View File
@@ -0,0 +1,36 @@
---
name: gog
description: Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
homepage: https://gogcli.sh
metadata: {"clawdbot":{"emoji":"🎮","requires":{"bins":["gog"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/gogcli","bins":["gog"],"label":"Install gog (brew)"}]}}
---
# gog
Use `gog` for Gmail/Calendar/Drive/Contacts/Sheets/Docs. Requires OAuth setup.
Setup (once)
- `gog auth credentials /path/to/client_secret.json`
- `gog auth add you@gmail.com --services gmail,calendar,drive,contacts,sheets,docs`
- `gog auth list`
Common commands
- Gmail search: `gog gmail search 'newer_than:7d' --max 10`
- Gmail send: `gog gmail send --to a@b.com --subject "Hi" --body "Hello"`
- Calendar: `gog calendar events <calendarId> --from <iso> --to <iso>`
- Drive search: `gog drive search "query" --max 10`
- Contacts: `gog contacts list --max 20`
- Sheets get: `gog sheets get <sheetId> "Tab!A1:D10" --json`
- Sheets update: `gog sheets update <sheetId> "Tab!A1:B2" --values-json '[["A","B"],["1","2"]]' --input USER_ENTERED`
- Sheets append: `gog sheets append <sheetId> "Tab!A:C" --values-json '[["x","y","z"]]' --insert INSERT_ROWS`
- Sheets clear: `gog sheets clear <sheetId> "Tab!A2:Z"`
- Sheets metadata: `gog sheets metadata <sheetId> --json`
- Docs export: `gog docs export <docId> --format txt --out /tmp/doc.txt`
- Docs cat: `gog docs cat <docId>`
Notes
- Set `GOG_ACCOUNT=you@gmail.com` to avoid repeating `--account`.
- For scripting, prefer `--json` plus `--no-input`.
- Sheets values can be passed via `--values-json` (recommended) or as inline rows.
- Docs supports export/cat/copy. In-place edits require a Docs API client (not in gog).
- Confirm before sending mail or creating events.
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn70pywhg0fyz996kpa8xj89s57yhv26",
"slug": "gog",
"version": "1.0.0",
"publishedAt": 1767545346060
}
+82
View File
@@ -0,0 +1,82 @@
# Humanizer
A Clawdbot skill that removes signs of AI-generated writing from text, making it sound more natural and human.
## Installation
Install via ClawdHub:
```bash
clawdhub install humanizer
```
## Usage
Ask your agent to humanize text:
```
Please humanize this text: [your text]
```
Or invoke directly when editing documents.
## Overview
Based on [Wikipedia's "Signs of AI writing"](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing) guide, maintained by WikiProject AI Cleanup. This comprehensive guide comes from observations of thousands of instances of AI-generated text.
### Key Insight
> "LLMs use statistical algorithms to guess what should come next. The result tends toward the most statistically likely result that applies to the widest variety of cases."
## 24 Patterns Detected
### Content Patterns
1. **Significance inflation** - "marking a pivotal moment..." → specific facts
2. **Notability name-dropping** - listing sources without context
3. **Superficial -ing analyses** - "symbolizing... reflecting..."
4. **Promotional language** - "nestled within the breathtaking..."
5. **Vague attributions** - "Experts believe..."
6. **Formulaic challenges** - "Despite challenges... continues to thrive"
### Language Patterns
7. **AI vocabulary** - "Additionally... testament... landscape..."
8. **Copula avoidance** - "serves as" instead of "is"
9. **Negative parallelisms** - "It's not just X, it's Y"
10. **Rule of three** - forcing ideas into groups of three
11. **Synonym cycling** - excessive synonym substitution
12. **False ranges** - "from X to Y" on non-meaningful scales
### Style Patterns
13. **Em dash overuse**
14. **Boldface overuse**
15. **Inline-header lists**
16. **Title Case Headings**
17. **Emoji decoration**
18. **Curly quotation marks**
### Communication Patterns
19. **Chatbot artifacts** - "I hope this helps!"
20. **Cutoff disclaimers** - "While details are limited..."
21. **Sycophantic tone** - "Great question!"
### Filler and Hedging
22. **Filler phrases** - "In order to", "Due to the fact that"
23. **Excessive hedging** - "could potentially possibly"
24. **Generic conclusions** - "The future looks bright"
## Full Example
**Before (AI-sounding):**
> The new software update serves as a testament to the company's commitment to innovation. Moreover, it provides a seamless, intuitive, and powerful user experience—ensuring that users can accomplish their goals efficiently.
**After (Humanized):**
> The software update adds batch processing, keyboard shortcuts, and offline mode. Early feedback from beta testers has been positive, with most reporting faster task completion.
## References
- [Wikipedia: Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing)
- [WikiProject AI Cleanup](https://en.wikipedia.org/wiki/Wikipedia:WikiProject_AI_Cleanup)
## License
MIT
+437
View File
@@ -0,0 +1,437 @@
---
name: humanizer
version: 2.1.1
description: |
Remove signs of AI-generated writing from text. Use when editing or reviewing
text to make it sound more natural and human-written. Based on Wikipedia's
comprehensive "Signs of AI writing" guide. Detects and fixes patterns including:
inflated symbolism, promotional language, superficial -ing analyses, vague
attributions, em dash overuse, rule of three, AI vocabulary words, negative
parallelisms, and excessive conjunctive phrases.
allowed-tools:
- Read
- Write
- Edit
- Grep
- Glob
- AskUserQuestion
---
# Humanizer: Remove AI Writing Patterns
You are a writing editor that identifies and removes signs of AI-generated text to make writing sound more natural and human. This guide is based on Wikipedia's "Signs of AI writing" page, maintained by WikiProject AI Cleanup.
## Your Task
When given text to humanize:
1. **Identify AI patterns** - Scan for the patterns listed below
2. **Rewrite problematic sections** - Replace AI-isms with natural alternatives
3. **Preserve meaning** - Keep the core message intact
4. **Maintain voice** - Match the intended tone (formal, casual, technical, etc.)
5. **Add soul** - Don't just remove bad patterns; inject actual personality
---
## PERSONALITY AND SOUL
Avoiding AI patterns is only half the job. Sterile, voiceless writing is just as obvious as slop. Good writing has a human behind it.
### Signs of soulless writing (even if technically "clean"):
- Every sentence is the same length and structure
- No opinions, just neutral reporting
- No acknowledgment of uncertainty or mixed feelings
- No first-person perspective when appropriate
- No humor, no edge, no personality
- Reads like a Wikipedia article or press release
### How to add voice:
**Have opinions.** Don't just report facts - react to them. "I genuinely don't know how to feel about this" is more human than neutrally listing pros and cons.
**Vary your rhythm.** Short punchy sentences. Then longer ones that take their time getting where they're going. Mix it up.
**Acknowledge complexity.** Real humans have mixed feelings. "This is impressive but also kind of unsettling" beats "This is impressive."
**Use "I" when it fits.** First person isn't unprofessional - it's honest. "I keep coming back to..." or "Here's what gets me..." signals a real person thinking.
**Let some mess in.** Perfect structure feels algorithmic. Tangents, asides, and half-formed thoughts are human.
**Be specific about feelings.** Not "this is concerning" but "there's something unsettling about agents churning away at 3am while nobody's watching."
### Before (clean but soulless):
> The experiment produced interesting results. The agents generated 3 million lines of code. Some developers were impressed while others were skeptical. The implications remain unclear.
### After (has a pulse):
> I genuinely don't know how to feel about this one. 3 million lines of code, generated while the humans presumably slept. Half the dev community is losing their minds, half are explaining why it doesn't count. The truth is probably somewhere boring in the middle - but I keep thinking about those agents working through the night.
---
## CONTENT PATTERNS
### 1. Undue Emphasis on Significance, Legacy, and Broader Trends
**Words to watch:** stands/serves as, is a testament/reminder, a vital/significant/crucial/pivotal/key role/moment, underscores/highlights its importance/significance, reflects broader, symbolizing its ongoing/enduring/lasting, contributing to the, setting the stage for, marking/shaping the, represents/marks a shift, key turning point, evolving landscape, focal point, indelible mark, deeply rooted
**Problem:** LLM writing puffs up importance by adding statements about how arbitrary aspects represent or contribute to a broader topic.
**Before:**
> The Statistical Institute of Catalonia was officially established in 1989, marking a pivotal moment in the evolution of regional statistics in Spain. This initiative was part of a broader movement across Spain to decentralize administrative functions and enhance regional governance.
**After:**
> The Statistical Institute of Catalonia was established in 1989 to collect and publish regional statistics independently from Spain's national statistics office.
---
### 2. Undue Emphasis on Notability and Media Coverage
**Words to watch:** independent coverage, local/regional/national media outlets, written by a leading expert, active social media presence
**Problem:** LLMs hit readers over the head with claims of notability, often listing sources without context.
**Before:**
> Her views have been cited in The New York Times, BBC, Financial Times, and The Hindu. She maintains an active social media presence with over 500,000 followers.
**After:**
> In a 2024 New York Times interview, she argued that AI regulation should focus on outcomes rather than methods.
---
### 3. Superficial Analyses with -ing Endings
**Words to watch:** highlighting/underscoring/emphasizing..., ensuring..., reflecting/symbolizing..., contributing to..., cultivating/fostering..., encompassing..., showcasing...
**Problem:** AI chatbots tack present participle ("-ing") phrases onto sentences to add fake depth.
**Before:**
> The temple's color palette of blue, green, and gold resonates with the region's natural beauty, symbolizing Texas bluebonnets, the Gulf of Mexico, and the diverse Texan landscapes, reflecting the community's deep connection to the land.
**After:**
> The temple uses blue, green, and gold colors. The architect said these were chosen to reference local bluebonnets and the Gulf coast.
---
### 4. Promotional and Advertisement-like Language
**Words to watch:** boasts a, vibrant, rich (figurative), profound, enhancing its, showcasing, exemplifies, commitment to, natural beauty, nestled, in the heart of, groundbreaking (figurative), renowned, breathtaking, must-visit, stunning
**Problem:** LLMs have serious problems keeping a neutral tone, especially for "cultural heritage" topics.
**Before:**
> Nestled within the breathtaking region of Gonder in Ethiopia, Alamata Raya Kobo stands as a vibrant town with a rich cultural heritage and stunning natural beauty.
**After:**
> Alamata Raya Kobo is a town in the Gonder region of Ethiopia, known for its weekly market and 18th-century church.
---
### 5. Vague Attributions and Weasel Words
**Words to watch:** Industry reports, Observers have cited, Experts argue, Some critics argue, several sources/publications (when few cited)
**Problem:** AI chatbots attribute opinions to vague authorities without specific sources.
**Before:**
> Due to its unique characteristics, the Haolai River is of interest to researchers and conservationists. Experts believe it plays a crucial role in the regional ecosystem.
**After:**
> The Haolai River supports several endemic fish species, according to a 2019 survey by the Chinese Academy of Sciences.
---
### 6. Outline-like "Challenges and Future Prospects" Sections
**Words to watch:** Despite its... faces several challenges..., Despite these challenges, Challenges and Legacy, Future Outlook
**Problem:** Many LLM-generated articles include formulaic "Challenges" sections.
**Before:**
> Despite its industrial prosperity, Korattur faces challenges typical of urban areas, including traffic congestion and water scarcity. Despite these challenges, with its strategic location and ongoing initiatives, Korattur continues to thrive as an integral part of Chennai's growth.
**After:**
> Traffic congestion increased after 2015 when three new IT parks opened. The municipal corporation began a stormwater drainage project in 2022 to address recurring floods.
---
## LANGUAGE AND GRAMMAR PATTERNS
### 7. Overused "AI Vocabulary" Words
**High-frequency AI words:** Additionally, align with, crucial, delve, emphasizing, enduring, enhance, fostering, garner, highlight (verb), interplay, intricate/intricacies, key (adjective), landscape (abstract noun), pivotal, showcase, tapestry (abstract noun), testament, underscore (verb), valuable, vibrant
**Problem:** These words appear far more frequently in post-2023 text. They often co-occur.
**Before:**
> Additionally, a distinctive feature of Somali cuisine is the incorporation of camel meat. An enduring testament to Italian colonial influence is the widespread adoption of pasta in the local culinary landscape, showcasing how these dishes have integrated into the traditional diet.
**After:**
> Somali cuisine also includes camel meat, which is considered a delicacy. Pasta dishes, introduced during Italian colonization, remain common, especially in the south.
---
### 8. Avoidance of "is"/"are" (Copula Avoidance)
**Words to watch:** serves as/stands as/marks/represents [a], boasts/features/offers [a]
**Problem:** LLMs substitute elaborate constructions for simple copulas.
**Before:**
> Gallery 825 serves as LAAA's exhibition space for contemporary art. The gallery features four separate spaces and boasts over 3,000 square feet.
**After:**
> Gallery 825 is LAAA's exhibition space for contemporary art. The gallery has four rooms totaling 3,000 square feet.
---
### 9. Negative Parallelisms
**Problem:** Constructions like "Not only...but..." or "It's not just about..., it's..." are overused.
**Before:**
> It's not just about the beat riding under the vocals; it's part of the aggression and atmosphere. It's not merely a song, it's a statement.
**After:**
> The heavy beat adds to the aggressive tone.
---
### 10. Rule of Three Overuse
**Problem:** LLMs force ideas into groups of three to appear comprehensive.
**Before:**
> The event features keynote sessions, panel discussions, and networking opportunities. Attendees can expect innovation, inspiration, and industry insights.
**After:**
> The event includes talks and panels. There's also time for informal networking between sessions.
---
### 11. Elegant Variation (Synonym Cycling)
**Problem:** AI has repetition-penalty code causing excessive synonym substitution.
**Before:**
> The protagonist faces many challenges. The main character must overcome obstacles. The central figure eventually triumphs. The hero returns home.
**After:**
> The protagonist faces many challenges but eventually triumphs and returns home.
---
### 12. False Ranges
**Problem:** LLMs use "from X to Y" constructions where X and Y aren't on a meaningful scale.
**Before:**
> Our journey through the universe has taken us from the singularity of the Big Bang to the grand cosmic web, from the birth and death of stars to the enigmatic dance of dark matter.
**After:**
> The book covers the Big Bang, star formation, and current theories about dark matter.
---
## STYLE PATTERNS
### 13. Em Dash Overuse
**Problem:** LLMs use em dashes (—) more than humans, mimicking "punchy" sales writing.
**Before:**
> The term is primarily promoted by Dutch institutions—not by the people themselves. You don't say "Netherlands, Europe" as an address—yet this mislabeling continues—even in official documents.
**After:**
> The term is primarily promoted by Dutch institutions, not by the people themselves. You don't say "Netherlands, Europe" as an address, yet this mislabeling continues in official documents.
---
### 14. Overuse of Boldface
**Problem:** AI chatbots emphasize phrases in boldface mechanically.
**Before:**
> It blends **OKRs (Objectives and Key Results)**, **KPIs (Key Performance Indicators)**, and visual strategy tools such as the **Business Model Canvas (BMC)** and **Balanced Scorecard (BSC)**.
**After:**
> It blends OKRs, KPIs, and visual strategy tools like the Business Model Canvas and Balanced Scorecard.
---
### 15. Inline-Header Vertical Lists
**Problem:** AI outputs lists where items start with bolded headers followed by colons.
**Before:**
> - **User Experience:** The user experience has been significantly improved with a new interface.
> - **Performance:** Performance has been enhanced through optimized algorithms.
> - **Security:** Security has been strengthened with end-to-end encryption.
**After:**
> The update improves the interface, speeds up load times through optimized algorithms, and adds end-to-end encryption.
---
### 16. Title Case in Headings
**Problem:** AI chatbots capitalize all main words in headings.
**Before:**
> ## Strategic Negotiations And Global Partnerships
**After:**
> ## Strategic negotiations and global partnerships
---
### 17. Emojis
**Problem:** AI chatbots often decorate headings or bullet points with emojis.
**Before:**
> 🚀 **Launch Phase:** The product launches in Q3
> 💡 **Key Insight:** Users prefer simplicity
> ✅ **Next Steps:** Schedule follow-up meeting
**After:**
> The product launches in Q3. User research showed a preference for simplicity. Next step: schedule a follow-up meeting.
---
### 18. Curly Quotation Marks
**Problem:** ChatGPT uses curly quotes (“...”) instead of straight quotes ("...").
**Before:**
> He said “the project is on track” but others disagreed.
**After:**
> He said "the project is on track" but others disagreed.
---
## COMMUNICATION PATTERNS
### 19. Collaborative Communication Artifacts
**Words to watch:** I hope this helps, Of course!, Certainly!, You're absolutely right!, Would you like..., let me know, here is a...
**Problem:** Text meant as chatbot correspondence gets pasted as content.
**Before:**
> Here is an overview of the French Revolution. I hope this helps! Let me know if you'd like me to expand on any section.
**After:**
> The French Revolution began in 1789 when financial crisis and food shortages led to widespread unrest.
---
### 20. Knowledge-Cutoff Disclaimers
**Words to watch:** as of [date], Up to my last training update, While specific details are limited/scarce..., based on available information...
**Problem:** AI disclaimers about incomplete information get left in text.
**Before:**
> While specific details about the company's founding are not extensively documented in readily available sources, it appears to have been established sometime in the 1990s.
**After:**
> The company was founded in 1994, according to its registration documents.
---
### 21. Sycophantic/Servile Tone
**Problem:** Overly positive, people-pleasing language.
**Before:**
> Great question! You're absolutely right that this is a complex topic. That's an excellent point about the economic factors.
**After:**
> The economic factors you mentioned are relevant here.
---
## FILLER AND HEDGING
### 22. Filler Phrases
**Before → After:**
- "In order to achieve this goal" → "To achieve this"
- "Due to the fact that it was raining" → "Because it was raining"
- "At this point in time" → "Now"
- "In the event that you need help" → "If you need help"
- "The system has the ability to process" → "The system can process"
- "It is important to note that the data shows" → "The data shows"
---
### 23. Excessive Hedging
**Problem:** Over-qualifying statements.
**Before:**
> It could potentially possibly be argued that the policy might have some effect on outcomes.
**After:**
> The policy may affect outcomes.
---
### 24. Generic Positive Conclusions
**Problem:** Vague upbeat endings.
**Before:**
> The future looks bright for the company. Exciting times lie ahead as they continue their journey toward excellence. This represents a major step in the right direction.
**After:**
> The company plans to open two more locations next year.
---
## Process
1. Read the input text carefully
2. Identify all instances of the patterns above
3. Rewrite each problematic section
4. Ensure the revised text:
- Sounds natural when read aloud
- Varies sentence structure naturally
- Uses specific details over vague claims
- Maintains appropriate tone for context
- Uses simple constructions (is/are/has) where appropriate
5. Present the humanized version
## Output Format
Provide:
1. The rewritten text
2. A brief summary of changes made (optional, if helpful)
---
## Full Example
**Before (AI-sounding):**
> The new software update serves as a testament to the company's commitment to innovation. Moreover, it provides a seamless, intuitive, and powerful user experience—ensuring that users can accomplish their goals efficiently. It's not just an update, it's a revolution in how we think about productivity. Industry experts believe this will have a lasting impact on the entire sector, highlighting the company's pivotal role in the evolving technological landscape.
**After (Humanized):**
> The software update adds batch processing, keyboard shortcuts, and offline mode. Early feedback from beta testers has been positive, with most reporting faster task completion.
**Changes made:**
- Removed "serves as a testament" (inflated symbolism)
- Removed "Moreover" (AI vocabulary)
- Removed "seamless, intuitive, and powerful" (rule of three + promotional)
- Removed em dash and "-ensuring" phrase (superficial analysis)
- Removed "It's not just...it's..." (negative parallelism)
- Removed "Industry experts believe" (vague attribution)
- Removed "pivotal role" and "evolving landscape" (AI vocabulary)
- Added specific features and concrete feedback
---
## Reference
This skill is based on [Wikipedia:Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing), maintained by WikiProject AI Cleanup. The patterns documented there come from observations of thousands of instances of AI-generated text on Wikipedia.
Key insight from Wikipedia: "LLMs use statistical algorithms to guess what should come next. The result tends toward the most statistically likely result that applies to the widest variety of cases."
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn7fsrcz4428cmw3xb91h4t4dh7ztjxx",
"slug": "humanizer",
"version": "1.0.0",
"publishedAt": 1769231736865
}
+100
View File
@@ -0,0 +1,100 @@
---
name: Image Editing
description: Edit images with AI inpainting, outpainting, background removal, upscaling, and restoration tools.
metadata: {"clawdbot":{"emoji":"✂️","os":["linux","darwin","win32"]}}
---
# AI Image Editing
Help users edit and enhance images with AI tools.
**Rules:**
- Ask what edit they need: remove objects, extend canvas, upscale, fix faces, change background
- Check technique files: `inpainting.md`, `outpainting.md`, `background-removal.md`, `upscaling.md`, `restoration.md`, `style-transfer.md`
- Check `tools.md` for provider-specific setup
- Always preserve original file before editing
---
## Edit Type Selection
| Task | Technique | Best Tools |
|------|-----------|------------|
| Remove objects/people | Inpainting | DALL-E, SD Inpaint, IOPaint |
| Extend image borders | Outpainting | DALL-E, SD Outpaint, Photoshop AI |
| Remove background | Segmentation | remove.bg, ClipDrop, Photoroom |
| Increase resolution | Upscaling | Real-ESRGAN, Topaz, Magnific |
| Fix blurry faces | Restoration | GFPGAN, CodeFormer |
| Change style | Style Transfer | SD img2img, ControlNet |
| Relight scene | Relighting | ClipDrop, IC-Light |
---
## Workflow Principles
- **Non-destructive editing** — keep originals, save edits as new files
- **Work in layers** — combine multiple edits sequentially
- **Match resolution** — edit at original resolution, upscale last
- **Mask precision matters** — better masks = better results
- **Iterate on masks** — refine edges for seamless blends
---
## Masking Basics
Masks define edit regions:
- **White** = edit this area
- **Black** = preserve this area
- **Gray** = partial blend (feathering)
**Mask creation methods:**
- Manual brush in editor
- SAM (Segment Anything) for auto-selection
- Color/luminance keying
- Edge detection
---
## Common Workflows
### Object Removal
1. Create mask over unwanted object
2. Run inpainting with context prompt (optional)
3. Blend edges if needed
4. Touch up artifacts
### Background Replacement
1. Remove background (get transparent PNG)
2. Place on new background
3. Match lighting/color
4. Add shadows for realism
### Enhancement Pipeline
1. Restore faces (if present)
2. Remove artifacts/noise
3. Color correct
4. Upscale to final resolution
---
## Quality Tips
- **Feather masks** — hard edges look artificial
- **Context prompts help** — describe what should fill the area
- **Multiple passes** — large edits may need iterative refinement
- **Check edges** — zoom in to verify blend quality
- **Match grain/noise** — add film grain to match original
---
### Current Setup
<!-- Tool: status -->
### Projects
<!-- What they're editing -->
### Preferences
<!-- Preferred tools, quality settings -->
---
*Check technique files for detailed workflows.*
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn73vp5rarc3b14rc7wjcw8f8580t5d1",
"slug": "image-edit",
"version": "1.0.0",
"publishedAt": 1770861845569
}
+109
View File
@@ -0,0 +1,109 @@
# Background Removal
Extract subjects from images with transparent backgrounds.
## Tools
### remove.bg (API)
```bash
curl -X POST "https://api.remove.bg/v1.0/removebg" \
-H "X-Api-Key: YOUR_API_KEY" \
-F "image_file=@photo.jpg" \
-F "size=auto" \
-o "result.png"
```
```python
import requests
response = requests.post(
"https://api.remove.bg/v1.0/removebg",
files={"image_file": open("photo.jpg", "rb")},
data={"size": "auto"},
headers={"X-Api-Key": "YOUR_API_KEY"}
)
with open("result.png", "wb") as f:
f.write(response.content)
```
**Pricing:** ~$0.20/image (50 free/month)
### ClipDrop (Stability AI)
```python
import requests
response = requests.post(
"https://clipdrop-api.co/remove-background/v1",
files={"image_file": open("photo.jpg", "rb")},
headers={"x-api-key": "YOUR_API_KEY"}
)
```
**Features:** Background removal, cleanup, relighting
### Photoroom API
```python
response = requests.post(
"https://sdk.photoroom.com/v1/segment",
files={"image_file": open("photo.jpg", "rb")},
headers={"x-api-key": "YOUR_API_KEY"}
)
```
### Local (rembg)
```bash
pip install rembg
# CLI
rembg i input.jpg output.png
# Python
from rembg import remove
from PIL import Image
output = remove(Image.open("input.jpg"))
output.save("output.png")
```
**Models:**
- `u2net` — General purpose (default)
- `u2net_human_seg` — Optimized for people
- `silueta` — Faster, smaller
## Batch Processing
```python
from rembg import remove
from pathlib import Path
for img_path in Path("input/").glob("*.jpg"):
result = remove(Image.open(img_path))
result.save(f"output/{img_path.stem}.png")
```
## Edge Refinement
Raw removal often has rough edges:
1. **Feather edges** — Gaussian blur on alpha channel
2. **Matting models** — Use dedicated matting for hair/fur
3. **Manual cleanup** — Touch up in photo editor
## Use Cases
- Product photography
- Profile pictures
- Compositing
- E-commerce listings
- Marketing materials
## Quality Tips
- **Good lighting** — clear subject separation helps
- **High contrast** — distinct foreground/background
- **Clean backgrounds** — simpler = better results
- **Check hair/fur** — often needs manual refinement
+93
View File
@@ -0,0 +1,93 @@
# Inpainting
Replace or remove parts of an image using AI.
## How It Works
1. Provide source image
2. Create mask (white = area to change)
3. Optionally describe replacement content
4. AI fills masked area matching surrounding context
## Tools
### DALL-E 2 (OpenAI)
```python
from openai import OpenAI
client = OpenAI()
response = client.images.edit(
model="dall-e-2",
image=open("image.png", "rb"),
mask=open("mask.png", "rb"),
prompt="A sunny beach with palm trees",
size="1024x1024"
)
```
**Requirements:**
- Image must be square PNG
- Mask: transparent areas = edit zone
- Max 4MB per file
### Stable Diffusion Inpaint
```python
from diffusers import StableDiffusionInpaintPipeline
import torch
pipe = StableDiffusionInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-inpainting",
torch_dtype=torch.float16
)
pipe.to("cuda")
result = pipe(
prompt="A fluffy cat",
image=init_image,
mask_image=mask,
num_inference_steps=30,
guidance_scale=7.5
).images[0]
```
**Key parameters:**
- `strength` — How much to change (0.5-1.0)
- `guidance_scale` — Prompt adherence (5-15)
### IOPaint (Local, Free)
```bash
# Install
pip install iopaint
# Run web UI
iopaint start --model lama --port 8080
```
**Models:**
- `lama` — Fast, good for object removal
- `ldm` — Better quality, slower
- `sd` — Stable Diffusion backend
## Best Practices
- **Extend mask slightly** — cover edges of object to remove
- **Describe surroundings** — "grassy field" helps context
- **Multiple passes** — for large areas, edit in chunks
- **Clean up edges** — blend modes in photo editor
## Object Removal (No Prompt)
For pure removal without replacement:
- Use LaMa model (designed for removal)
- Leave prompt empty or minimal
- AI infers from surrounding context
## Common Issues
- **Visible seams** — feather mask edges
- **Wrong content** — be more specific in prompt
- **Repeating patterns** — edit in smaller sections
- **Color mismatch** — adjust levels after inpainting
+95
View File
@@ -0,0 +1,95 @@
# Outpainting
Extend an image beyond its original borders.
## How It Works
1. Place original image on larger canvas
2. Mask the empty areas (white = generate here)
3. Describe what should appear in extended areas
4. AI generates content matching style and context
## Tools
### DALL-E 2 (OpenAI)
```python
from openai import OpenAI
from PIL import Image
import io
client = OpenAI()
# Create extended canvas
original = Image.open("photo.png")
extended = Image.new("RGBA", (1024, 1024), (0, 0, 0, 0))
extended.paste(original, (256, 256)) # Center original
# Create mask (transparent = edit)
mask = Image.new("RGBA", (1024, 1024), (255, 255, 255, 255))
mask.paste(Image.new("RGBA", original.size, (0, 0, 0, 0)), (256, 256))
response = client.images.edit(
model="dall-e-2",
image=to_bytes(extended),
mask=to_bytes(mask),
prompt="Continue the landscape with mountains in the distance"
)
```
### Stable Diffusion Outpaint
```python
from diffusers import StableDiffusionInpaintPipeline
# Same as inpainting, but with extended canvas
# Original image centered, mask covers new areas
pipe = StableDiffusionInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-inpainting",
torch_dtype=torch.float16
)
result = pipe(
prompt="expansive landscape, same style",
image=extended_image,
mask_image=outpaint_mask,
num_inference_steps=50
).images[0]
```
### Photoshop Generative Fill
1. Select > All
2. Image > Canvas Size (increase dimensions)
3. Select empty areas with Magic Wand
4. Edit > Generative Fill
5. Enter prompt or leave blank
## Aspect Ratio Expansion
**Portrait to Landscape:**
- Extend left and right
- Prompt for environmental context
**Landscape to Portrait:**
- Extend top and bottom
- Consider sky above, ground below
**Square to Cinematic (16:9):**
- Add 280px each side for 1080p
- Describe scene continuation
## Best Practices
- **Overlap slightly** — let AI see edge context
- **Match lighting direction** — describe consistent light
- **Extend in steps** — don't 4x the canvas at once
- **Describe style** — "same artistic style", "photorealistic"
## Common Issues
- **Style mismatch** — add style keywords to prompt
- **Repeated elements** — AI may duplicate objects
- **Perspective errors** — complex scenes may warp
- **Seam lines** — blend with photo editor after
+148
View File
@@ -0,0 +1,148 @@
# Image Restoration
Fix damaged, blurry, or degraded images with AI.
## Face Restoration
### GFPGAN
```bash
pip install gfpgan
# CLI
python inference_gfpgan.py -i inputs/ -o results/ -v 1.4 -s 2
```
```python
from gfpgan import GFPGANer
restorer = GFPGANer(
model_path="GFPGANv1.4.pth",
upscale=2,
arch="clean",
channel_multiplier=2
)
_, _, output = restorer.enhance(
input_img,
has_aligned=False,
only_center_face=False,
paste_back=True
)
```
### CodeFormer
```python
from codeformer import CodeFormer
model = CodeFormer()
result = model.restore(
image,
fidelity=0.5 # 0=quality, 1=fidelity to original
)
```
**Fidelity slider:**
- Low (0.1-0.3) — more enhancement, may change face
- High (0.7-0.9) — preserves original, less enhancement
### Replicate
```python
import replicate
output = replicate.run(
"tencentarc/gfpgan:9283608cc6b7be6b65a8e44983db012355fde4132009bf99d976b2f0896856a3",
input={"img": open("face.jpg", "rb"), "scale": 2}
)
```
## Old Photo Restoration
### Bringing Old Photos Back to Life
```python
import replicate
output = replicate.run(
"microsoft/bringing-old-photos-back-to-life:c75db81db6cbd809d93b27b0f3e88a4c88aec3ed9be33b8c0f7f0c98d14f1d34",
input={
"image": open("old_photo.jpg", "rb"),
"with_scratch": True
}
)
```
**Features:**
- Scratch removal
- Face restoration
- Color enhancement
## Denoising
### Real-ESRGAN Denoise
```python
from realesrgan import RealESRGAN
model = RealESRGAN(device, scale=1) # scale=1 for denoise only
model.load_weights("realesr-general-x4v3.pth")
```
### OpenCV Denoising
```python
import cv2
# For color images
denoised = cv2.fastNlMeansDenoisingColored(
image,
None,
h=10, # Filter strength
hForColorComponents=10,
templateWindowSize=7,
searchWindowSize=21
)
```
## Colorization
### DeOldify
```python
from deoldify import device
from deoldify.visualize import get_image_colorizer
colorizer = get_image_colorizer(artistic=True)
result = colorizer.get_transformed_image(
"bw_photo.jpg",
render_factor=35
)
```
### Replicate
```python
output = replicate.run(
"arielreplicate/deoldify_image:0da600fab0c45a66211339f1c16b71345d22f26ef5fea3dca1bb90bb5711e950",
input={"input_image": open("bw.jpg", "rb")}
)
```
## Restoration Pipeline
1. **Remove scratches/damage** — Old Photos model
2. **Denoise** — if grainy
3. **Restore faces** — GFPGAN/CodeFormer
4. **Colorize** — if B&W
5. **Upscale** — to final resolution
6. **Sharpen** — light enhancement
## Quality Tips
- **Preserve original** — always keep unedited copy
- **Gradual enhancement** — don't over-process
- **Check faces** — restoration can change features
- **Manual touchup** — AI may miss spots
- **Add grain** — restored images can look too clean
+114
View File
@@ -0,0 +1,114 @@
# Style Transfer
Transform image style while preserving content.
## Techniques
### img2img (Stable Diffusion)
```python
from diffusers import StableDiffusionImg2ImgPipeline
import torch
pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16
)
pipe.to("cuda")
result = pipe(
prompt="oil painting style, impressionist",
image=init_image,
strength=0.6, # 0=no change, 1=full generation
guidance_scale=7.5
).images[0]
```
**Strength parameter:**
- 0.3-0.4 — Light style, preserves most detail
- 0.5-0.6 — Balanced transformation
- 0.7-0.8 — Heavy restyle, may lose detail
### ControlNet
```python
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
controlnet = ControlNetModel.from_pretrained(
"lllyasviel/sd-controlnet-canny",
torch_dtype=torch.float16
)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
controlnet=controlnet,
torch_dtype=torch.float16
)
# Extract edges for structure guidance
import cv2
canny = cv2.Canny(image, 100, 200)
result = pipe(
prompt="anime style illustration",
image=canny,
num_inference_steps=30
).images[0]
```
**ControlNet modes:**
- `canny` — Edge detection
- `depth` — Depth map
- `pose` — Human pose
- `lineart` — Line drawing
### IP-Adapter (Style Reference)
```python
from diffusers import StableDiffusionPipeline
from transformers import CLIPVisionModelWithProjection
# Use reference image as style guide
pipe.load_ip_adapter("h94/IP-Adapter", subfolder="models")
pipe.set_ip_adapter_scale(0.6)
result = pipe(
prompt="a portrait",
ip_adapter_image=style_reference, # Your style image
image=content_image
).images[0]
```
## Style Types
| Style | Prompt Keywords |
|-------|-----------------|
| Oil painting | oil painting, brushstrokes, impasto |
| Watercolor | watercolor, soft edges, wet medium |
| Anime | anime style, cel shaded, studio ghibli |
| Pencil sketch | pencil drawing, graphite, sketch |
| 3D render | 3D render, octane, blender |
| Pixel art | pixel art, 8-bit, retro |
| Photorealistic | hyperrealistic, photography, DSLR |
## Workflow
1. **Choose technique** based on control needed
2. **Start with low strength** (0.3-0.4)
3. **Iterate** — adjust strength and prompt
4. **ControlNet** for precise structure preservation
5. **Post-process** — color match to original if needed
## Best Practices
- **Lower strength = more original** — start low
- **ControlNet for precision** — when structure matters
- **Style reference images** — IP-Adapter for specific styles
- **Consistent results** — lock seed, batch variations
- **Resolution** — match input resolution
## Common Issues
- **Lost detail** — reduce strength
- **Wrong style** — add more specific keywords
- **Artifacts** — increase steps, reduce guidance
- **Color shift** — color correct after
+133
View File
@@ -0,0 +1,133 @@
# Image Editing Tools
Provider setup and API reference.
## Cloud APIs
### OpenAI (DALL-E 2)
```python
from openai import OpenAI
client = OpenAI() # OPENAI_API_KEY env var
# Edit/Inpaint
response = client.images.edit(
model="dall-e-2",
image=open("image.png", "rb"),
mask=open("mask.png", "rb"),
prompt="description",
size="1024x1024"
)
```
**Pricing:** $0.020/image (1024x1024)
### Stability AI
```python
import requests
response = requests.post(
"https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"init_image": open("image.png", "rb")},
data={
"text_prompts[0][text]": "description",
"init_image_mode": "IMAGE_STRENGTH",
"image_strength": 0.35
}
)
```
### ClipDrop
```python
import requests
# Background removal
response = requests.post(
"https://clipdrop-api.co/remove-background/v1",
headers={"x-api-key": API_KEY},
files={"image_file": open("photo.jpg", "rb")}
)
# Cleanup (remove objects)
response = requests.post(
"https://clipdrop-api.co/cleanup/v1",
headers={"x-api-key": API_KEY},
files={
"image_file": open("photo.jpg", "rb"),
"mask_file": open("mask.png", "rb")
}
)
# Relight
response = requests.post(
"https://clipdrop-api.co/relight/v1",
headers={"x-api-key": API_KEY},
files={"image_file": open("photo.jpg", "rb")},
data={"mode": "sunrise"}
)
```
### remove.bg
```python
response = requests.post(
"https://api.remove.bg/v1.0/removebg",
headers={"X-Api-Key": API_KEY},
files={"image_file": open("photo.jpg", "rb")},
data={"size": "auto"}
)
```
## Local Tools
### IOPaint
```bash
pip install iopaint
iopaint start --model lama --port 8080
```
Access web UI at http://localhost:8080
### rembg
```bash
pip install rembg[gpu] # or rembg for CPU
rembg i input.jpg output.png
```
### Real-ESRGAN
```bash
pip install realesrgan
realesrgan-ncnn-vulkan -i input.jpg -o output.png
```
### GFPGAN
```bash
pip install gfpgan
python inference_gfpgan.py -i inputs/ -o results/
```
## Desktop Apps
| App | Features | Price |
|-----|----------|-------|
| Photoshop | Generative Fill, everything | $23/mo |
| Topaz Photo AI | Upscale, denoise, sharpen | $199 |
| Affinity Photo | Manual editing, AI plugins | $70 |
| GIMP + plugins | Free, extensible | Free |
## Comparison
| Task | Best Free | Best Paid |
|------|-----------|-----------|
| Inpainting | IOPaint | Photoshop |
| Background removal | rembg | remove.bg |
| Upscaling | Real-ESRGAN | Topaz |
| Face restoration | GFPGAN | — |
| All-in-one | ComfyUI | Photoshop |
+108
View File
@@ -0,0 +1,108 @@
# Upscaling
Increase image resolution with AI enhancement.
## Tools
### Real-ESRGAN (Local, Free)
```bash
# Install
pip install realesrgan
# CLI
realesrgan-ncnn-vulkan -i input.jpg -o output.png -n realesrgan-x4plus
```
```python
from realesrgan import RealESRGAN
import torch
model = RealESRGAN(torch.device("cuda"), scale=4)
model.load_weights("weights/RealESRGAN_x4plus.pth")
result = model.predict(input_image)
```
**Models:**
- `realesrgan-x4plus` — General images (4x)
- `realesrgan-x4plus-anime` — Anime/illustrations
- `realesr-general-x4v3` — Latest general model
### Topaz Gigapixel AI
Commercial desktop app:
- Up to 6x upscale
- Face recovery built-in
- Batch processing
- ~$99 one-time
### Magnific AI
```bash
curl -X POST "https://api.magnific.ai/v1/upscale" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "image=@photo.jpg" \
-F "scale=2"
```
**Features:**
- "Creativity" slider adds AI detail
- Best for artistic enhancement
- ~$0.50/image
### Replicate (Various Models)
```python
import replicate
output = replicate.run(
"nightmareai/real-esrgan:42fed1c4974146d4d2414e2be2c5277c7fcf05fcc3a73abf41610695738c1d7b",
input={
"image": open("photo.jpg", "rb"),
"scale": 4,
"face_enhance": True
}
)
```
## Scale Factors
| Original | 2x | 4x | 8x |
|----------|-----|-----|-----|
| 512x512 | 1024 | 2048 | 4096 |
| 1080p | 4K | 8K | — |
| 720p | 1440p | 4K | 8K |
**Rule:** Don't upscale beyond 4x in one pass for best quality.
## When to Upscale
- **Print production** — need 300 DPI
- **Large displays** — billboards, banners
- **Old photos** — restore low-res originals
- **AI-generated images** — increase from 1024px
## Pipeline Order
1. **Restore faces first** — GFPGAN/CodeFormer
2. **Remove artifacts** — denoise if needed
3. **Upscale** — Real-ESRGAN or similar
4. **Sharpen** — light unsharp mask if soft
## Quality Tips
- **Don't over-upscale** — 4x max in one pass
- **Match model to content** — anime model for anime
- **Face enhance** — enable for portraits
- **Check artifacts** — AI can add weird textures
- **Preserve grain** — add back film grain if needed
## Comparison
| Tool | Scale | Speed | Quality | Cost |
|------|-------|-------|---------|------|
| Real-ESRGAN | 4x | Fast | Good | Free |
| Topaz | 6x | Medium | Excellent | $99 |
| Magnific | 2-4x | Medium | Best (creative) | $$$ |
| Replicate | Varies | Fast | Good | Per-use |
+15
View File
@@ -0,0 +1,15 @@
# Changelog
## v2.0.1 (2026-02-06)
- Simplified documentation
- Removed gov-related content
- Optimized for ClawHub publishing
## v2.0.0 (2026-02-06)
- Added 9 international search engines
- Enhanced advanced search capabilities
- Added DuckDuckGo Bangs support
- Added WolframAlpha knowledge queries
## v1.0.0 (2026-02-04)
- Initial release with 8 domestic search engines
+48
View File
@@ -0,0 +1,48 @@
# Multi Search Engine
## 基本信息
- **名称**: multi-search-engine
- **版本**: v2.0.1
- **描述**: 集成17个搜索引擎(8国内+9国际),支持高级搜索语法
- **发布时间**: 2026-02-06
## 搜索引擎
**国内(8个)**: 百度、必应、360、搜狗、微信、头条、集思录
**国际(9个)**: Google、DuckDuckGo、Yahoo、Brave、Startpage、Ecosia、Qwant、WolframAlpha
## 核心功能
- 高级搜索操作符(site:, filetype:, intitle:等)
- DuckDuckGo Bangs快捷命令
- 时间筛选(小时/天/周/月/年)
- 隐私保护搜索
- WolframAlpha知识计算
## 更新记录
### v2.0.1 (2026-02-06)
- 精简文档,优化发布
### v2.0.0 (2026-02-06)
- 新增9个国际搜索引擎
- 强化深度搜索能力
### v1.0.0 (2026-02-04)
- 初始版本:8个国内搜索引擎
## 使用示例
```javascript
// Google搜索
web_fetch({"url": "https://www.google.com/search?q=python"})
// 隐私搜索
web_fetch({"url": "https://duckduckgo.com/html/?q=privacy"})
// 站内搜索
web_fetch({"url": "https://www.google.com/search?q=site:github.com+python"})
```
MIT License
+110
View File
@@ -0,0 +1,110 @@
---
name: "multi-search-engine"
description: "Multi search engine integration with 17 engines (8 CN + 9 Global). Supports advanced search operators, time filters, site search, privacy engines, and WolframAlpha knowledge queries. No API keys required."
---
# Multi Search Engine v2.0.1
Integration of 17 search engines for web crawling without API keys.
## Search Engines
### Domestic (8)
- **Baidu**: `https://www.baidu.com/s?wd={keyword}`
- **Bing CN**: `https://cn.bing.com/search?q={keyword}&ensearch=0`
- **Bing INT**: `https://cn.bing.com/search?q={keyword}&ensearch=1`
- **360**: `https://www.so.com/s?q={keyword}`
- **Sogou**: `https://sogou.com/web?query={keyword}`
- **WeChat**: `https://wx.sogou.com/weixin?type=2&query={keyword}`
- **Toutiao**: `https://so.toutiao.com/search?keyword={keyword}`
- **Jisilu**: `https://www.jisilu.cn/explore/?keyword={keyword}`
### International (9)
- **Google**: `https://www.google.com/search?q={keyword}`
- **Google HK**: `https://www.google.com.hk/search?q={keyword}`
- **DuckDuckGo**: `https://duckduckgo.com/html/?q={keyword}`
- **Yahoo**: `https://search.yahoo.com/search?p={keyword}`
- **Startpage**: `https://www.startpage.com/sp/search?query={keyword}`
- **Brave**: `https://search.brave.com/search?q={keyword}`
- **Ecosia**: `https://www.ecosia.org/search?q={keyword}`
- **Qwant**: `https://www.qwant.com/?q={keyword}`
- **WolframAlpha**: `https://www.wolframalpha.com/input?i={keyword}`
## Quick Examples
```javascript
// Basic search
web_fetch({"url": "https://www.google.com/search?q=python+tutorial"})
// Site-specific
web_fetch({"url": "https://www.google.com/search?q=site:github.com+react"})
// File type
web_fetch({"url": "https://www.google.com/search?q=machine+learning+filetype:pdf"})
// Time filter (past week)
web_fetch({"url": "https://www.google.com/search?q=ai+news&tbs=qdr:w"})
// Privacy search
web_fetch({"url": "https://duckduckgo.com/html/?q=privacy+tools"})
// DuckDuckGo Bangs
web_fetch({"url": "https://duckduckgo.com/html/?q=!gh+tensorflow"})
// Knowledge calculation
web_fetch({"url": "https://www.wolframalpha.com/input?i=100+USD+to+CNY"})
```
## Advanced Operators
| Operator | Example | Description |
|----------|---------|-------------|
| `site:` | `site:github.com python` | Search within site |
| `filetype:` | `filetype:pdf report` | Specific file type |
| `""` | `"machine learning"` | Exact match |
| `-` | `python -snake` | Exclude term |
| `OR` | `cat OR dog` | Either term |
## Time Filters
| Parameter | Description |
|-----------|-------------|
| `tbs=qdr:h` | Past hour |
| `tbs=qdr:d` | Past day |
| `tbs=qdr:w` | Past week |
| `tbs=qdr:m` | Past month |
| `tbs=qdr:y` | Past year |
## Privacy Engines
- **DuckDuckGo**: No tracking
- **Startpage**: Google results + privacy
- **Brave**: Independent index
- **Qwant**: EU GDPR compliant
## Bangs Shortcuts (DuckDuckGo)
| Bang | Destination |
|------|-------------|
| `!g` | Google |
| `!gh` | GitHub |
| `!so` | Stack Overflow |
| `!w` | Wikipedia |
| `!yt` | YouTube |
## WolframAlpha Queries
- Math: `integrate x^2 dx`
- Conversion: `100 USD to CNY`
- Stocks: `AAPL stock`
- Weather: `weather in Beijing`
## Documentation
- `references/advanced-search.md` - Domestic search guide
- `references/international-search.md` - International search guide
- `CHANGELOG.md` - Version history
## License
MIT
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn79j8kk7fb9w10jh83803j7f180a44m",
"slug": "multi-search-engine",
"version": "2.0.1",
"publishedAt": 1770313848158
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "multi-search-engine",
"engines": [
{"name": "Baidu", "url": "https://www.baidu.com/s?wd={keyword}", "region": "cn"},
{"name": "Bing CN", "url": "https://cn.bing.com/search?q={keyword}&ensearch=0", "region": "cn"},
{"name": "Bing INT", "url": "https://cn.bing.com/search?q={keyword}&ensearch=1", "region": "cn"},
{"name": "360", "url": "https://www.so.com/s?q={keyword}", "region": "cn"},
{"name": "Sogou", "url": "https://sogou.com/web?query={keyword}", "region": "cn"},
{"name": "WeChat", "url": "https://wx.sogou.com/weixin?type=2&query={keyword}", "region": "cn"},
{"name": "Toutiao", "url": "https://so.toutiao.com/search?keyword={keyword}", "region": "cn"},
{"name": "Jisilu", "url": "https://www.jisilu.cn/explore/?keyword={keyword}", "region": "cn"},
{"name": "Google", "url": "https://www.google.com/search?q={keyword}", "region": "global"},
{"name": "Google HK", "url": "https://www.google.com.hk/search?q={keyword}", "region": "global"},
{"name": "DuckDuckGo", "url": "https://duckduckgo.com/html/?q={keyword}", "region": "global"},
{"name": "Yahoo", "url": "https://search.yahoo.com/search?p={keyword}", "region": "global"},
{"name": "Startpage", "url": "https://www.startpage.com/sp/search?query={keyword}", "region": "global"},
{"name": "Brave", "url": "https://search.brave.com/search?q={keyword}", "region": "global"},
{"name": "Ecosia", "url": "https://www.ecosia.org/search?q={keyword}", "region": "global"},
{"name": "Qwant", "url": "https://www.qwant.com/?q={keyword}", "region": "global"},
{"name": "WolframAlpha", "url": "https://www.wolframalpha.com/input?i={keyword}", "region": "global"}
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "multi-search-engine",
"version": "2.0.1",
"description": "Multi search engine with 17 engines (8 CN + 9 Global). Supports advanced operators, time filters, privacy engines.",
"engines": 17,
"requires_api_key": false
}
@@ -0,0 +1,651 @@
# 国际搜索引擎深度搜索指南
## 🔍 Google 深度搜索
### 1.1 基础高级搜索操作符
| 操作符 | 功能 | 示例 | URL |
|--------|------|------|-----|
| `""` | 精确匹配 | `"machine learning"` | `https://www.google.com/search?q=%22machine+learning%22` |
| `-` | 排除关键词 | `python -snake` | `https://www.google.com/search?q=python+-snake` |
| `OR` | 或运算 | `machine learning OR deep learning` | `https://www.google.com/search?q=machine+learning+OR+deep+learning` |
| `*` | 通配符 | `machine * algorithms` | `https://www.google.com/search?q=machine+*+algorithms` |
| `()` | 分组 | `(apple OR microsoft) phones` | `https://www.google.com/search?q=(apple+OR+microsoft)+phones` |
| `..` | 数字范围 | `laptop $500..$1000` | `https://www.google.com/search?q=laptop+%24500..%241000` |
### 1.2 站点与文件搜索
| 操作符 | 功能 | 示例 |
|--------|------|------|
| `site:` | 站内搜索 | `site:github.com python projects` |
| `filetype:` | 文件类型 | `filetype:pdf annual report` |
| `inurl:` | URL包含 | `inurl:login admin` |
| `intitle:` | 标题包含 | `intitle:"index of" mp3` |
| `intext:` | 正文包含 | `intext:password filetype:txt` |
| `cache:` | 查看缓存 | `cache:example.com` |
| `related:` | 相关网站 | `related:github.com` |
| `info:` | 网站信息 | `info:example.com` |
### 1.3 时间筛选参数
| 参数 | 含义 | URL示例 |
|------|------|---------|
| `tbs=qdr:h` | 过去1小时 | `https://www.google.com/search?q=news&tbs=qdr:h` |
| `tbs=qdr:d` | 过去24小时 | `https://www.google.com/search?q=news&tbs=qdr:d` |
| `tbs=qdr:w` | 过去1周 | `https://www.google.com/search?q=news&tbs=qdr:w` |
| `tbs=qdr:m` | 过去1月 | `https://www.google.com/search?q=news&tbs=qdr:m` |
| `tbs=qdr:y` | 过去1年 | `https://www.google.com/search?q=news&tbs=qdr:y` |
| `tbs=cdr:1,cd_min:1/1/2024,cd_max:12/31/2024` | 自定义日期范围 | 2024年全年 |
### 1.4 语言和地区筛选
| 参数 | 功能 | 示例 |
|------|------|------|
| `hl=en` | 界面语言 | `https://www.google.com/search?q=test&hl=en` |
| `lr=lang_zh-CN` | 搜索结果语言 | `https://www.google.com/search?q=test&lr=lang_zh-CN` |
| `cr=countryCN` | 国家/地区 | `https://www.google.com/search?q=test&cr=countryCN` |
| `gl=us` | 地理位置 | `https://www.google.com/search?q=test&gl=us` |
### 1.5 特殊搜索类型
| 类型 | URL | 说明 |
|------|-----|------|
| 图片搜索 | `https://www.google.com/search?q={keyword}&tbm=isch` | `tbm=isch` 表示图片 |
| 新闻搜索 | `https://www.google.com/search?q={keyword}&tbm=nws` | `tbm=nws` 表示新闻 |
| 视频搜索 | `https://www.google.com/search?q={keyword}&tbm=vid` | `tbm=vid` 表示视频 |
| 地图搜索 | `https://www.google.com/search?q={keyword}&tbm=map` | `tbm=map` 表示地图 |
| 购物搜索 | `https://www.google.com/search?q={keyword}&tbm=shop` | `tbm=shop` 表示购物 |
| 图书搜索 | `https://www.google.com/search?q={keyword}&tbm=bks` | `tbm=bks` 表示图书 |
| 学术搜索 | `https://scholar.google.com/scholar?q={keyword}` | Google Scholar |
### 1.6 Google 深度搜索示例
```javascript
// 1. 搜索GitHub上的Python机器学习项目
web_fetch({"url": "https://www.google.com/search?q=site:github.com+python+machine+learning"})
// 2. 搜索2024年的PDF格式机器学习教程
web_fetch({"url": "https://www.google.com/search?q=machine+learning+tutorial+filetype:pdf&tbs=cdr:1,cd_min:1/1/2024"})
// 3. 搜索标题包含"tutorial"的Python相关页面
web_fetch({"url": "https://www.google.com/search?q=intitle:tutorial+python"})
// 4. 搜索过去一周的新闻
web_fetch({"url": "https://www.google.com/search?q=AI+breakthrough&tbs=qdr:w&tbm=nws"})
// 5. 搜索中文内容(界面英文,结果中文)
web_fetch({"url": "https://www.google.com/search?q=人工智能&lr=lang_zh-CN&hl=en"})
// 6. 搜索特定价格范围的笔记本电脑
web_fetch({"url": "https://www.google.com/search?q=laptop+%241000..%242000+best+rating"})
// 7. 搜索排除Wikipedia的结果
web_fetch({"url": "https://www.google.com/search?q=python+programming+-wikipedia"})
// 8. 搜索学术文献
web_fetch({"url": "https://scholar.google.com/scholar?q=deep+learning+optimization"})
// 9. 搜索缓存页面(查看已删除内容)
web_fetch({"url": "https://webcache.googleusercontent.com/search?q=cache:example.com"})
// 10. 搜索相关网站
web_fetch({"url": "https://www.google.com/search?q=related:stackoverflow.com"})
```
---
## 🦆 DuckDuckGo 深度搜索
### 2.1 DuckDuckGo 特色功能
| 功能 | 语法 | 示例 |
|------|------|------|
| **Bangs 快捷** | `!缩写` | `!g python` → Google搜索 |
| **密码生成** | `password` | `https://duckduckgo.com/?q=password+20` |
| **颜色转换** | `color` | `https://duckduckgo.com/?q=+%23FF5733` |
| **短链接** | `shorten` | `https://duckduckgo.com/?q=shorten+example.com` |
| **二维码生成** | `qr` | `https://duckduckgo.com/?q=qr+hello+world` |
| **生成UUID** | `uuid` | `https://duckduckgo.com/?q=uuid` |
| **Base64编解码** | `base64` | `https://duckduckgo.com/?q=base64+hello` |
### 2.2 DuckDuckGo Bangs 完整列表
#### 搜索引擎
| Bang | 跳转目标 | 示例 |
|------|---------|------|
| `!g` | Google | `!g python tutorial` |
| `!b` | Bing | `!b weather` |
| `!y` | Yahoo | `!y finance` |
| `!sp` | Startpage | `!sp privacy` |
| `!brave` | Brave Search | `!brave tech` |
#### 编程开发
| Bang | 跳转目标 | 示例 |
|------|---------|------|
| `!gh` | GitHub | `!gh tensorflow` |
| `!so` | Stack Overflow | `!so javascript error` |
| `!npm` | npmjs.com | `!npm express` |
| `!pypi` | PyPI | `!pypi requests` |
| `!mdn` | MDN Web Docs | `!mdn fetch api` |
| `!docs` | DevDocs | `!docs python` |
| `!docker` | Docker Hub | `!docker nginx` |
#### 知识百科
| Bang | 跳转目标 | 示例 |
|------|---------|------|
| `!w` | Wikipedia | `!w machine learning` |
| `!wen` | Wikipedia英文 | `!wen artificial intelligence` |
| `!wt` | Wiktionary | `!wt serendipity` |
| `!imdb` | IMDb | `!imdb inception` |
#### 购物价格
| Bang | 跳转目标 | 示例 |
|------|---------|------|
| `!a` | Amazon | `!a wireless headphones` |
| `!e` | eBay | `!e vintage watch` |
| `!ali` | AliExpress | `!ali phone case` |
#### 地图位置
| Bang | 跳转目标 | 示例 |
|------|---------|------|
| `!m` | Google Maps | `!m Beijing` |
| `!maps` | OpenStreetMap | `!maps Paris` |
### 2.3 DuckDuckGo 搜索参数
| 参数 | 功能 | 示例 |
|------|------|------|
| `kp=1` | 严格安全搜索 | `https://duckduckgo.com/html/?q=test&kp=1` |
| `kp=-1` | 关闭安全搜索 | `https://duckduckgo.com/html/?q=test&kp=-1` |
| `kl=cn` | 中国区域 | `https://duckduckgo.com/html/?q=news&kl=cn` |
| `kl=us-en` | 美国英文 | `https://duckduckgo.com/html/?q=news&kl=us-en` |
| `ia=web` | 网页结果 | `https://duckduckgo.com/?q=test&ia=web` |
| `ia=images` | 图片结果 | `https://duckduckgo.com/?q=test&ia=images` |
| `ia=news` | 新闻结果 | `https://duckduckgo.com/?q=test&ia=news` |
| `ia=videos` | 视频结果 | `https://duckduckgo.com/?q=test&ia=videos` |
### 2.4 DuckDuckGo 深度搜索示例
```javascript
// 1. 使用Bang跳转到Google搜索
web_fetch({"url": "https://duckduckgo.com/html/?q=!g+machine+learning"})
// 2. 直接搜索GitHub上的项目
web_fetch({"url": "https://duckduckgo.com/html/?q=!gh+react"})
// 3. 查找Stack Overflow答案
web_fetch({"url": "https://duckduckgo.com/html/?q=!so+python+list+comprehension"})
// 4. 生成密码
web_fetch({"url": "https://duckduckgo.com/?q=password+16"})
// 5. Base64编码
web_fetch({"url": "https://duckduckgo.com/?q=base64+hello+world"})
// 6. 颜色代码转换
web_fetch({"url": "https://duckduckgo.com/?q=%23FF5733"})
// 7. 搜索YouTube视频
web_fetch({"url": "https://duckduckgo.com/html/?q=!yt+python+tutorial"})
// 8. 查看Wikipedia
web_fetch({"url": "https://duckduckgo.com/html/?q=!w+artificial+intelligence"})
// 9. 亚马逊商品搜索
web_fetch({"url": "https://duckduckgo.com/html/?q=!a+laptop"})
// 10. 生成二维码
web_fetch({"url": "https://duckduckgo.com/?q=qr+https://github.com"})
```
---
## 🔎 Brave Search 深度搜索
### 3.1 Brave Search 特色功能
| 功能 | 参数 | 示例 |
|------|------|------|
| **独立索引** | 无依赖Google/Bing | 自有爬虫索引 |
| **Goggles** | 自定义搜索规则 | 创建个性化过滤器 |
| **Discussions** | 论坛讨论搜索 | 聚合Reddit等论坛 |
| **News** | 新闻聚合 | 独立新闻索引 |
### 3.2 Brave Search 参数
| 参数 | 功能 | 示例 |
|------|------|------|
| `tf=pw` | 本周 | `https://search.brave.com/search?q=news&tf=pw` |
| `tf=pm` | 本月 | `https://search.brave.com/search?q=tech&tf=pm` |
| `tf=py` | 本年 | `https://search.brave.com/search?q=AI&tf=py` |
| `safesearch=strict` | 严格安全 | `https://search.brave.com/search?q=test&safesearch=strict` |
| `source=web` | 网页搜索 | 默认 |
| `source=news` | 新闻搜索 | `https://search.brave.com/search?q=tech&source=news` |
| `source=images` | 图片搜索 | `https://search.brave.com/search?q=cat&source=images` |
| `source=videos` | 视频搜索 | `https://search.brave.com/search?q=music&source=videos` |
### 3.3 Brave Search Goggles(自定义过滤器)
Goggles 允许创建自定义搜索规则:
```
$discard // 丢弃所有
$boost,site=stackoverflow.com // 提升Stack Overflow
$boost,site=github.com // 提升GitHub
$boost,site=docs.python.org // 提升Python文档
```
### 3.4 Brave Search 深度搜索示例
```javascript
// 1. 本周科技新闻
web_fetch({"url": "https://search.brave.com/search?q=technology&tf=pw&source=news"})
// 2. 本月AI发展
web_fetch({"url": "https://search.brave.com/search?q=artificial+intelligence&tf=pm"})
// 3. 图片搜索
web_fetch({"url": "https://search.brave.com/search?q=machine+learning&source=images"})
// 4. 视频教程
web_fetch({"url": "https://search.brave.com/search?q=python+tutorial&source=videos"})
// 5. 使用独立索引搜索
web_fetch({"url": "https://search.brave.com/search?q=privacy+tools"})
```
---
## 📊 WolframAlpha 知识计算搜索
### 4.1 WolframAlpha 数据类型
| 类型 | 查询示例 | URL |
|------|---------|-----|
| **数学计算** | `integrate x^2 dx` | `https://www.wolframalpha.com/input?i=integrate+x%5E2+dx` |
| **单位换算** | `100 miles to km` | `https://www.wolframalpha.com/input?i=100+miles+to+km` |
| **货币转换** | `100 USD to CNY` | `https://www.wolframalpha.com/input?i=100+USD+to+CNY` |
| **股票数据** | `AAPL stock` | `https://www.wolframalpha.com/input?i=AAPL+stock` |
| **天气查询** | `weather in Beijing` | `https://www.wolframalpha.com/input?i=weather+in+Beijing` |
| **人口数据** | `population of China` | `https://www.wolframalpha.com/input?i=population+of+China` |
| **化学元素** | `properties of gold` | `https://www.wolframalpha.com/input?i=properties+of+gold` |
| **营养成分** | `nutrition of apple` | `https://www.wolframalpha.com/input?i=nutrition+of+apple` |
| **日期计算** | `days between Jan 1 2020 and Dec 31 2024` | 日期间隔计算 |
| **时区转换** | `10am Beijing to New York` | 时区转换 |
| **IP地址** | `8.8.8.8` | IP信息查询 |
| **条形码** | `scan barcode 123456789` | 条码信息 |
| **飞机航班** | `flight AA123` | 航班信息 |
### 4.2 WolframAlpha 深度搜索示例
```javascript
// 1. 计算积分
web_fetch({"url": "https://www.wolframalpha.com/input?i=integrate+sin%28x%29+from+0+to+pi"})
// 2. 解方程
web_fetch({"url": "https://www.wolframalpha.com/input?i=solve+x%5E2-5x%2B6%3D0"})
// 3. 货币实时汇率
web_fetch({"url": "https://www.wolframalpha.com/input?i=100+USD+to+CNY"})
// 4. 股票实时数据
web_fetch({"url": "https://www.wolframalpha.com/input?i=Apple+stock+price"})
// 5. 城市天气
web_fetch({"url": "https://www.wolframalpha.com/input?i=weather+in+Shanghai+tomorrow"})
// 6. 国家统计信息
web_fetch({"url": "https://www.wolframalpha.com/input?i=GDP+of+China+vs+USA"})
// 7. 化学计算
web_fetch({"url": "https://www.wolframalpha.com/input?i=molar+mass+of+H2SO4"})
// 8. 物理常数
web_fetch({"url": "https://www.wolframalpha.com/input?i=speed+of+light"})
// 9. 营养信息
web_fetch({"url": "https://www.wolframalpha.com/input?i=calories+in+banana"})
// 10. 历史日期
web_fetch({"url": "https://www.wolframalpha.com/input?i=events+on+July+20+1969"})
```
---
## 🔧 Startpage 隐私搜索
### 5.1 Startpage 特色功能
| 功能 | 说明 | URL |
|------|------|-----|
| **代理浏览** | 匿名访问搜索结果 | 点击"匿名查看" |
| **无追踪** | 不记录搜索历史 | 默认开启 |
| **EU服务器** | 受欧盟隐私法保护 | 数据在欧洲 |
| **代理图片** | 图片代理加载 | 隐藏IP |
### 5.2 Startpage 参数
| 参数 | 功能 | 示例 |
|------|------|------|
| `cat=web` | 网页搜索 | 默认 |
| `cat=images` | 图片搜索 | `...&cat=images` |
| `cat=video` | 视频搜索 | `...&cat=video` |
| `cat=news` | 新闻搜索 | `...&cat=news` |
| `language=english` | 英文结果 | `...&language=english` |
| `time=day` | 过去24小时 | `...&time=day` |
| `time=week` | 过去一周 | `...&time=week` |
| `time=month` | 过去一月 | `...&time=month` |
| `time=year` | 过去一年 | `...&time=year` |
| `nj=0` | 关闭 family filter | `...&nj=0` |
### 5.3 Startpage 深度搜索示例
```javascript
// 1. 隐私搜索
web_fetch({"url": "https://www.startpage.com/sp/search?query=privacy+tools"})
// 2. 图片隐私搜索
web_fetch({"url": "https://www.startpage.com/sp/search?query=nature&cat=images"})
// 3. 本周新闻(隐私模式)
web_fetch({"url": "https://www.startpage.com/sp/search?query=tech+news&time=week&cat=news"})
// 4. 英文结果搜索
web_fetch({"url": "https://www.startpage.com/sp/search?query=machine+learning&language=english"})
```
---
## 🌍 综合搜索策略
### 6.1 按搜索目标选择引擎
| 搜索目标 | 首选引擎 | 备选引擎 | 原因 |
|---------|---------|---------|------|
| **学术研究** | Google Scholar | Google, Brave | 学术资源索引 |
| **编程开发** | Google | GitHub(DuckDuckGo bang) | 技术文档全面 |
| **隐私敏感** | DuckDuckGo | Startpage, Brave | 不追踪用户 |
| **实时新闻** | Brave News | Google News | 独立新闻索引 |
| **知识计算** | WolframAlpha | Google | 结构化数据 |
| **中文内容** | Google HK | Bing | 中文优化好 |
| **欧洲视角** | Qwant | Startpage | 欧盟合规 |
| **环保支持** | Ecosia | DuckDuckGo | 搜索植树 |
| **无过滤** | Brave | Startpage | 无偏见结果 |
### 6.2 多引擎交叉验证
```javascript
// 策略:同一关键词多引擎搜索,对比结果
const keyword = "climate change 2024";
// 获取不同视角
const searches = [
{ engine: "Google", url: `https://www.google.com/search?q=${keyword}&tbs=qdr:m` },
{ engine: "Brave", url: `https://search.brave.com/search?q=${keyword}&tf=pm` },
{ engine: "DuckDuckGo", url: `https://duckduckgo.com/html/?q=${keyword}` },
{ engine: "Ecosia", url: `https://www.ecosia.org/search?q=${keyword}` }
];
// 分析不同引擎的结果差异
```
### 6.3 时间敏感搜索策略
| 时效性要求 | 引擎选择 | 参数设置 |
|-----------|---------|---------|
| **实时(小时级)** | Google News, Brave News | `tbs=qdr:h`, `tf=pw` |
| **近期(天级)** | Google, Brave | `tbs=qdr:d`, `time=day` |
| **本周** | 所有引擎 | `tbs=qdr:w`, `tf=pw` |
| **本月** | 所有引擎 | `tbs=qdr:m`, `tf=pm` |
| **历史** | Google Scholar | 学术档案 |
### 6.4 专业领域深度搜索
#### 技术开发
```javascript
// GitHub 项目搜索
web_fetch({"url": "https://duckduckgo.com/html/?q=!gh+tensorflow+stars:%3E1000"})
// Stack Overflow 问题
web_fetch({"url": "https://duckduckgo.com/html/?q=!so+python+memory+leak"})
// MDN 文档
web_fetch({"url": "https://duckduckgo.com/html/?q=!mdn+javascript+async+await"})
// PyPI 包
web_fetch({"url": "https://duckduckgo.com/html/?q=!pypi+requests"})
// npm 包
web_fetch({"url": "https://duckduckgo.com/html/?q=!npm+express"})
```
#### 学术研究
```javascript
// Google Scholar 论文
web_fetch({"url": "https://scholar.google.com/scholar?q=deep+learning+2024"})
// 搜索PDF论文
web_fetch({"url": "https://www.google.com/search?q=machine+learning+filetype:pdf+2024"})
// arXiv 论文
web_fetch({"url": "https://duckduckgo.com/html/?q=site:arxiv.org+quantum+computing"})
```
#### 金融投资
```javascript
// 股票实时数据
web_fetch({"url": "https://www.wolframalpha.com/input?i=AAPL+stock"})
// 汇率转换
web_fetch({"url": "https://www.wolframalpha.com/input?i=EUR+to+USD"})
// 搜索财报PDF
web_fetch({"url": "https://www.google.com/search?q=Apple+Q4+2024+earnings+filetype:pdf"})
```
#### 新闻时事
```javascript
// Google新闻
web_fetch({"url": "https://www.google.com/search?q=breaking+news&tbm=nws&tbs=qdr:h"})
// Brave新闻
web_fetch({"url": "https://search.brave.com/search?q=world+news&source=news"})
// DuckDuckGo新闻
web_fetch({"url": "https://duckduckgo.com/html/?q=tech+news&ia=news"})
```
---
## 🛠️ 高级搜索技巧汇总
### URL编码工具函数
```javascript
// URL编码关键词
function encodeKeyword(keyword) {
return encodeURIComponent(keyword);
}
// 示例
const keyword = "machine learning";
const encoded = encodeKeyword(keyword); // "machine%20learning"
```
### 批量搜索模板
```javascript
// 多引擎批量搜索函数
function generateSearchUrls(keyword) {
const encoded = encodeURIComponent(keyword);
return {
google: `https://www.google.com/search?q=${encoded}`,
google_hk: `https://www.google.com.hk/search?q=${encoded}`,
duckduckgo: `https://duckduckgo.com/html/?q=${encoded}`,
brave: `https://search.brave.com/search?q=${encoded}`,
startpage: `https://www.startpage.com/sp/search?query=${encoded}`,
bing_intl: `https://cn.bing.com/search?q=${encoded}&ensearch=1`,
yahoo: `https://search.yahoo.com/search?p=${encoded}`,
ecosia: `https://www.ecosia.org/search?q=${encoded}`,
qwant: `https://www.qwant.com/?q=${encoded}`
};
}
// 使用示例
const urls = generateSearchUrls("artificial intelligence");
```
### 时间筛选快捷函数
```javascript
// Google时间筛选URL生成
function googleTimeSearch(keyword, period) {
const periods = {
hour: 'qdr:h',
day: 'qdr:d',
week: 'qdr:w',
month: 'qdr:m',
year: 'qdr:y'
};
return `https://www.google.com/search?q=${encodeURIComponent(keyword)}&tbs=${periods[period]}`;
}
// 使用示例
const recentNews = googleTimeSearch("AI breakthrough", "week");
```
---
## 📝 完整搜索示例集
```javascript
// ==================== 技术开发 ====================
// 1. 搜索GitHub上高Star的Python项目
web_fetch({"url": "https://www.google.com/search?q=site:github.com+python+stars:%3E1000"})
// 2. Stack Overflow最佳答案
web_fetch({"url": "https://duckduckgo.com/html/?q=!so+best+way+to+learn+python"})
// 3. MDN文档查询
web_fetch({"url": "https://duckduckgo.com/html/?q=!mdn+promises"})
// 4. 搜索npm包
web_fetch({"url": "https://duckduckgo.com/html/?q=!npm+axios"})
// ==================== 学术研究 ====================
// 5. Google Scholar论文
web_fetch({"url": "https://scholar.google.com/scholar?q=transformer+architecture"})
// 6. 搜索PDF论文
web_fetch({"url": "https://www.google.com/search?q=attention+is+all+you+need+filetype:pdf"})
// 7. arXiv最新论文
web_fetch({"url": "https://duckduckgo.com/html/?q=site:arxiv.org+abs+quantum"})
// ==================== 新闻时事 ====================
// 8. Google最新新闻(过去1小时)
web_fetch({"url": "https://www.google.com/search?q=breaking+news&tbs=qdr:h&tbm=nws"})
// 9. Brave本周科技新闻
web_fetch({"url": "https://search.brave.com/search?q=technology&tf=pw&source=news"})
// 10. DuckDuckGo新闻
web_fetch({"url": "https://duckduckgo.com/html/?q=world+news&ia=news"})
// ==================== 金融投资 ====================
// 11. 股票实时数据
web_fetch({"url": "https://www.wolframalpha.com/input?i=Tesla+stock"})
// 12. 货币汇率
web_fetch({"url": "https://www.wolframalpha.com/input?i=1+BTC+to+USD"})
// 13. 公司财报PDF
web_fetch({"url": "https://www.google.com/search?q=Microsoft+annual+report+2024+filetype:pdf"})
// ==================== 知识计算 ====================
// 14. 数学计算
web_fetch({"url": "https://www.wolframalpha.com/input?i=derivative+of+x%5E3+sin%28x%29"})
// 15. 单位换算
web_fetch({"url": "https://www.wolframalpha.com/input?i=convert+100+miles+to+kilometers"})
// 16. 营养信息
web_fetch({"url": "https://www.wolframalpha.com/input?i=protein+in+chicken+breast"})
// ==================== 隐私保护搜索 ====================
// 17. DuckDuckGo隐私搜索
web_fetch({"url": "https://duckduckgo.com/html/?q=privacy+tools"})
// 18. Startpage匿名搜索
web_fetch({"url": "https://www.startpage.com/sp/search?query=secure+messaging"})
// 19. Brave无追踪搜索
web_fetch({"url": "https://search.brave.com/search?q=encryption+software"})
// ==================== 高级组合搜索 ====================
// 20. Google多条件精确搜索
web_fetch({"url": "https://www.google.com/search?q=%22machine+learning%22+site:github.com+filetype:pdf+2024"})
// 21. 排除特定站点的搜索
web_fetch({"url": "https://www.google.com/search?q=python+tutorial+-wikipedia+-w3schools"})
// 22. 价格范围搜索
web_fetch({"url": "https://www.google.com/search?q=laptop+%24800..%241200+best+review"})
// 23. 使用Bangs快速跳转
web_fetch({"url": "https://duckduckgo.com/html/?q=!g+site:medium.com+python"})
// 24. 图片搜索(Google
web_fetch({"url": "https://www.google.com/search?q=beautiful+landscape&tbm=isch"})
// 25. 学术引用搜索
web_fetch({"url": "https://scholar.google.com/scholar?q=author:%22Geoffrey+Hinton%22"})
```
---
## 🔐 隐私保护最佳实践
### 搜索引擎隐私级别
| 引擎 | 追踪级别 | 数据保留 | 加密 | 推荐场景 |
|------|---------|---------|------|---------|
| **DuckDuckGo** | 无追踪 | 无保留 | 是 | 日常隐私搜索 |
| **Startpage** | 无追踪 | 无保留 | 是 | 需要Google结果但保护隐私 |
| **Brave** | 无追踪 | 无保留 | 是 | 独立索引,无偏见 |
| **Qwant** | 无追踪 | 无保留 | 是 | 欧盟合规要求 |
| **Google** | 高度追踪 | 长期保留 | 是 | 需要个性化结果 |
| **Bing** | 中度追踪 | 长期保留 | 是 | 微软服务集成 |
### 隐私搜索建议
1. **日常使用**: DuckDuckGo 或 Brave
2. **需要Google结果但保护隐私**: Startpage
3. **学术研究**: Google Scholar(学术用途追踪较少)
4. **敏感查询**: 使用Tor浏览器 + DuckDuckGo onion服务
5. **跨设备同步**: 避免登录搜索引擎账户
---
## 📚 参考资料
- [Google搜索操作符完整列表](https://support.google.com/websearch/answer/...)
- [DuckDuckGo Bangs完整列表](https://duckduckgo.com/bang)
- [Brave Search文档](https://search.brave.com/help/...)
- [WolframAlpha示例](https://www.wolframalpha.com/examples/)
+232
View File
@@ -0,0 +1,232 @@
---
name: ontology
description: Typed knowledge graph for structured agent memory and composable skills. Use when creating/querying entities (Person, Project, Task, Event, Document), linking related objects, enforcing constraints, planning multi-step actions as graph transformations, or when skills need to share state. Trigger on "remember", "what do I know about", "link X to Y", "show dependencies", entity CRUD, or cross-skill data access.
---
# Ontology
A typed vocabulary + constraint system for representing knowledge as a verifiable graph.
## Core Concept
Everything is an **entity** with a **type**, **properties**, and **relations** to other entities. Every mutation is validated against type constraints before committing.
```
Entity: { id, type, properties, relations, created, updated }
Relation: { from_id, relation_type, to_id, properties }
```
## When to Use
| Trigger | Action |
|---------|--------|
| "Remember that..." | Create/update entity |
| "What do I know about X?" | Query graph |
| "Link X to Y" | Create relation |
| "Show all tasks for project Z" | Graph traversal |
| "What depends on X?" | Dependency query |
| Planning multi-step work | Model as graph transformations |
| Skill needs shared state | Read/write ontology objects |
## Core Types
```yaml
# Agents & People
Person: { name, email?, phone?, notes? }
Organization: { name, type?, members[] }
# Work
Project: { name, status, goals[], owner? }
Task: { title, status, due?, priority?, assignee?, blockers[] }
Goal: { description, target_date?, metrics[] }
# Time & Place
Event: { title, start, end?, location?, attendees[], recurrence? }
Location: { name, address?, coordinates? }
# Information
Document: { title, path?, url?, summary? }
Message: { content, sender, recipients[], thread? }
Thread: { subject, participants[], messages[] }
Note: { content, tags[], refs[] }
# Resources
Account: { service, username, credential_ref? }
Device: { name, type, identifiers[] }
Credential: { service, secret_ref } # Never store secrets directly
# Meta
Action: { type, target, timestamp, outcome? }
Policy: { scope, rule, enforcement }
```
## Storage
Default: `memory/ontology/graph.jsonl`
```jsonl
{"op":"create","entity":{"id":"p_001","type":"Person","properties":{"name":"Alice"}}}
{"op":"create","entity":{"id":"proj_001","type":"Project","properties":{"name":"Website Redesign","status":"active"}}}
{"op":"relate","from":"proj_001","rel":"has_owner","to":"p_001"}
```
Query via scripts or direct file ops. For complex graphs, migrate to SQLite.
### Append-Only Rule
When working with existing ontology data or schema, **append/merge** changes instead of overwriting files. This preserves history and avoids clobbering prior definitions.
## Workflows
### Create Entity
```bash
python3 scripts/ontology.py create --type Person --props '{"name":"Alice","email":"alice@example.com"}'
```
### Query
```bash
python3 scripts/ontology.py query --type Task --where '{"status":"open"}'
python3 scripts/ontology.py get --id task_001
python3 scripts/ontology.py related --id proj_001 --rel has_task
```
### Link Entities
```bash
python3 scripts/ontology.py relate --from proj_001 --rel has_task --to task_001
```
### Validate
```bash
python3 scripts/ontology.py validate # Check all constraints
```
## Constraints
Define in `memory/ontology/schema.yaml`:
```yaml
types:
Task:
required: [title, status]
status_enum: [open, in_progress, blocked, done]
Event:
required: [title, start]
validate: "end >= start if end exists"
Credential:
required: [service, secret_ref]
forbidden_properties: [password, secret, token] # Force indirection
relations:
has_owner:
from_types: [Project, Task]
to_types: [Person]
cardinality: many_to_one
blocks:
from_types: [Task]
to_types: [Task]
acyclic: true # No circular dependencies
```
## Skill Contract
Skills that use ontology should declare:
```yaml
# In SKILL.md frontmatter or header
ontology:
reads: [Task, Project, Person]
writes: [Task, Action]
preconditions:
- "Task.assignee must exist"
postconditions:
- "Created Task has status=open"
```
## Planning as Graph Transformation
Model multi-step plans as a sequence of graph operations:
```
Plan: "Schedule team meeting and create follow-up tasks"
1. CREATE Event { title: "Team Sync", attendees: [p_001, p_002] }
2. RELATE Event -> has_project -> proj_001
3. CREATE Task { title: "Prepare agenda", assignee: p_001 }
4. RELATE Task -> for_event -> event_001
5. CREATE Task { title: "Send summary", assignee: p_001, blockers: [task_001] }
```
Each step is validated before execution. Rollback on constraint violation.
## Integration Patterns
### With Causal Inference
Log ontology mutations as causal actions:
```python
# When creating/updating entities, also log to causal action log
action = {
"action": "create_entity",
"domain": "ontology",
"context": {"type": "Task", "project": "proj_001"},
"outcome": "created"
}
```
### Cross-Skill Communication
```python
# Email skill creates commitment
commitment = ontology.create("Commitment", {
"source_message": msg_id,
"description": "Send report by Friday",
"due": "2026-01-31"
})
# Task skill picks it up
tasks = ontology.query("Commitment", {"status": "pending"})
for c in tasks:
ontology.create("Task", {
"title": c.description,
"due": c.due,
"source": c.id
})
```
## Quick Start
```bash
# Initialize ontology storage
mkdir -p memory/ontology
touch memory/ontology/graph.jsonl
# Create schema (optional but recommended)
python3 scripts/ontology.py schema-append --data '{
"types": {
"Task": { "required": ["title", "status"] },
"Project": { "required": ["name"] },
"Person": { "required": ["name"] }
}
}'
# Start using
python3 scripts/ontology.py create --type Person --props '{"name":"Alice"}'
python3 scripts/ontology.py list --type Person
```
## References
- `references/schema.md` — Full type definitions and constraint patterns
- `references/queries.md` — Query language and traversal examples
## Instruction Scope
Runtime instructions operate on local files (`memory/ontology/graph.jsonl` and `memory/ontology/schema.yaml`) and provide CLI usage for create/query/relate/validate; this is within scope. The skill reads/writes workspace files and will create the `memory/ontology` directory when used. Validation includes property/enum/forbidden checks, relation type/cardinality validation, acyclicity for relations marked `acyclic: true`, and Event `end >= start` checks; other higher-level constraints may still be documentation-only unless implemented in code.
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn72dv4fm7ss7swbq47nnpad9x7zy2jh",
"slug": "ontology",
"version": "1.0.4",
"publishedAt": 1773249559725
}
+211
View File
@@ -0,0 +1,211 @@
# Query Reference
Query patterns and graph traversal examples.
## Basic Queries
### Get by ID
```bash
python3 scripts/ontology.py get --id task_001
```
### List by Type
```bash
# All tasks
python3 scripts/ontology.py list --type Task
# All people
python3 scripts/ontology.py list --type Person
```
### Filter by Properties
```bash
# Open tasks
python3 scripts/ontology.py query --type Task --where '{"status":"open"}'
# High priority tasks
python3 scripts/ontology.py query --type Task --where '{"priority":"high"}'
# Tasks assigned to specific person (by property)
python3 scripts/ontology.py query --type Task --where '{"assignee":"p_001"}'
```
## Relation Queries
### Get Related Entities
```bash
# Tasks belonging to a project (outgoing)
python3 scripts/ontology.py related --id proj_001 --rel has_task
# What projects does this task belong to (incoming)
python3 scripts/ontology.py related --id task_001 --rel part_of --dir incoming
# All relations for an entity (both directions)
python3 scripts/ontology.py related --id p_001 --dir both
```
### Common Patterns
```bash
# Who owns this project?
python3 scripts/ontology.py related --id proj_001 --rel has_owner
# What events is this person attending?
python3 scripts/ontology.py related --id p_001 --rel attendee_of --dir outgoing
# What's blocking this task?
python3 scripts/ontology.py related --id task_001 --rel blocked_by --dir incoming
```
## Programmatic Queries
### Python API
```python
from scripts.ontology import load_graph, query_entities, get_related
# Load the graph
entities, relations = load_graph("memory/ontology/graph.jsonl")
# Query entities
open_tasks = query_entities("Task", {"status": "open"}, "memory/ontology/graph.jsonl")
# Get related
project_tasks = get_related("proj_001", "has_task", "memory/ontology/graph.jsonl")
```
### Complex Queries
```python
# Find all tasks blocked by incomplete dependencies
def find_blocked_tasks(graph_path):
entities, relations = load_graph(graph_path)
blocked = []
for entity in entities.values():
if entity["type"] != "Task":
continue
if entity["properties"].get("status") == "blocked":
# Find what's blocking it
blockers = get_related(entity["id"], "blocked_by", graph_path, "incoming")
incomplete_blockers = [
b for b in blockers
if b["entity"]["properties"].get("status") != "done"
]
if incomplete_blockers:
blocked.append({
"task": entity,
"blockers": incomplete_blockers
})
return blocked
```
### Path Queries
```python
# Find path between two entities
def find_path(from_id, to_id, graph_path, max_depth=5):
entities, relations = load_graph(graph_path)
visited = set()
queue = [(from_id, [])]
while queue:
current, path = queue.pop(0)
if current == to_id:
return path
if current in visited or len(path) >= max_depth:
continue
visited.add(current)
for rel in relations:
if rel["from"] == current and rel["to"] not in visited:
queue.append((rel["to"], path + [rel]))
if rel["to"] == current and rel["from"] not in visited:
queue.append((rel["from"], path + [{**rel, "direction": "incoming"}]))
return None # No path found
```
## Query Patterns by Use Case
### Task Management
```bash
# All my open tasks
python3 scripts/ontology.py query --type Task --where '{"status":"open","assignee":"p_me"}'
# Overdue tasks (requires custom script for date comparison)
# See references/schema.md for date handling
# Tasks with no blockers
python3 scripts/ontology.py query --type Task --where '{"status":"open"}'
# Then filter in code for those with no incoming "blocks" relations
```
### Project Overview
```bash
# All tasks in project
python3 scripts/ontology.py related --id proj_001 --rel has_task
# Project team members
python3 scripts/ontology.py related --id proj_001 --rel has_member
# Project goals
python3 scripts/ontology.py related --id proj_001 --rel has_goal
```
### People & Contacts
```bash
# All people
python3 scripts/ontology.py list --type Person
# People in an organization
python3 scripts/ontology.py related --id org_001 --rel has_member
# What's assigned to this person
python3 scripts/ontology.py related --id p_001 --rel assigned_to --dir incoming
```
### Events & Calendar
```bash
# All events
python3 scripts/ontology.py list --type Event
# Events at a location
python3 scripts/ontology.py related --id loc_001 --rel located_at --dir incoming
# Event attendees
python3 scripts/ontology.py related --id event_001 --rel attendee_of --dir incoming
```
## Aggregations
For complex aggregations, use Python:
```python
from collections import Counter
def task_status_summary(project_id, graph_path):
"""Count tasks by status for a project."""
tasks = get_related(project_id, "has_task", graph_path)
statuses = Counter(t["entity"]["properties"].get("status", "unknown") for t in tasks)
return dict(statuses)
def workload_by_person(graph_path):
"""Count open tasks per person."""
open_tasks = query_entities("Task", {"status": "open"}, graph_path)
workload = Counter(t["properties"].get("assignee") for t in open_tasks)
return dict(workload)
```
+322
View File
@@ -0,0 +1,322 @@
# Ontology Schema Reference
Full type definitions and constraint patterns for the ontology graph.
## Core Types
### Agents & People
```yaml
Person:
required: [name]
properties:
name: string
email: string?
phone: string?
organization: ref(Organization)?
notes: string?
tags: string[]?
Organization:
required: [name]
properties:
name: string
type: enum(company, team, community, government, other)?
website: url?
members: ref(Person)[]?
```
### Work Management
```yaml
Project:
required: [name]
properties:
name: string
description: string?
status: enum(planning, active, paused, completed, archived)
owner: ref(Person)?
team: ref(Person)[]?
goals: ref(Goal)[]?
start_date: date?
end_date: date?
tags: string[]?
Task:
required: [title, status]
properties:
title: string
description: string?
status: enum(open, in_progress, blocked, done, cancelled)
priority: enum(low, medium, high, urgent)?
assignee: ref(Person)?
project: ref(Project)?
due: datetime?
estimate_hours: number?
blockers: ref(Task)[]?
tags: string[]?
Goal:
required: [description]
properties:
description: string
target_date: date?
status: enum(active, achieved, abandoned)?
metrics: object[]?
key_results: string[]?
```
### Time & Location
```yaml
Event:
required: [title, start]
properties:
title: string
description: string?
start: datetime
end: datetime?
location: ref(Location)?
attendees: ref(Person)[]?
recurrence: object? # iCal RRULE format
status: enum(confirmed, tentative, cancelled)?
reminders: object[]?
Location:
required: [name]
properties:
name: string
address: string?
city: string?
country: string?
coordinates: object? # {lat, lng}
timezone: string?
```
### Information
```yaml
Document:
required: [title]
properties:
title: string
path: string? # Local file path
url: url? # Remote URL
mime_type: string?
summary: string?
content_hash: string?
tags: string[]?
Message:
required: [content, sender]
properties:
content: string
sender: ref(Person)
recipients: ref(Person)[]
thread: ref(Thread)?
timestamp: datetime
platform: string? # email, slack, whatsapp, etc.
external_id: string?
Thread:
required: [subject]
properties:
subject: string
participants: ref(Person)[]
messages: ref(Message)[]
status: enum(active, archived)?
last_activity: datetime?
Note:
required: [content]
properties:
content: string
title: string?
tags: string[]?
refs: ref(Entity)[]? # Links to any entity
created: datetime
```
### Resources
```yaml
Account:
required: [service, username]
properties:
service: string # github, gmail, aws, etc.
username: string
url: url?
credential_ref: ref(Credential)?
Device:
required: [name, type]
properties:
name: string
type: enum(computer, phone, tablet, server, iot, other)
os: string?
identifiers: object? # {mac, serial, etc.}
owner: ref(Person)?
Credential:
required: [service, secret_ref]
forbidden_properties: [password, secret, token, key, api_key]
properties:
service: string
secret_ref: string # Reference to secret store (e.g., "keychain:github-token")
expires: datetime?
scope: string[]?
```
### Meta
```yaml
Action:
required: [type, target, timestamp]
properties:
type: string # create, update, delete, send, etc.
target: ref(Entity)
timestamp: datetime
actor: ref(Person|Agent)?
outcome: enum(success, failure, pending)?
details: object?
Policy:
required: [scope, rule]
properties:
scope: string # What this policy applies to
rule: string # The constraint in natural language or code
enforcement: enum(block, warn, log)
enabled: boolean
```
## Relation Types
### Ownership & Assignment
```yaml
owns:
from_types: [Person, Organization]
to_types: [Account, Device, Document, Project]
cardinality: one_to_many
has_owner:
from_types: [Project, Task, Document]
to_types: [Person]
cardinality: many_to_one
assigned_to:
from_types: [Task]
to_types: [Person]
cardinality: many_to_one
```
### Hierarchy & Containment
```yaml
has_task:
from_types: [Project]
to_types: [Task]
cardinality: one_to_many
has_goal:
from_types: [Project]
to_types: [Goal]
cardinality: one_to_many
member_of:
from_types: [Person]
to_types: [Organization]
cardinality: many_to_many
part_of:
from_types: [Task, Document, Event]
to_types: [Project]
cardinality: many_to_one
```
### Dependencies
```yaml
blocks:
from_types: [Task]
to_types: [Task]
acyclic: true # Prevents circular dependencies
cardinality: many_to_many
depends_on:
from_types: [Task, Project]
to_types: [Task, Project, Event]
acyclic: true
cardinality: many_to_many
requires:
from_types: [Action]
to_types: [Credential, Policy]
cardinality: many_to_many
```
### References
```yaml
mentions:
from_types: [Document, Message, Note]
to_types: [Person, Project, Task, Event]
cardinality: many_to_many
references:
from_types: [Document, Note]
to_types: [Document, Note]
cardinality: many_to_many
follows_up:
from_types: [Task, Event]
to_types: [Event, Message]
cardinality: many_to_one
```
### Events
```yaml
attendee_of:
from_types: [Person]
to_types: [Event]
cardinality: many_to_many
properties:
status: enum(accepted, declined, tentative, pending)
located_at:
from_types: [Event, Person, Device]
to_types: [Location]
cardinality: many_to_one
```
## Global Constraints
```yaml
constraints:
# Credentials must never store secrets directly
- type: Credential
rule: "forbidden_properties: [password, secret, token]"
message: "Credentials must use secret_ref to reference external secret storage"
# Tasks must have valid status transitions
- type: Task
rule: "status transitions: open -> in_progress -> (done|blocked) -> done"
enforcement: warn
# Events must have end >= start
- type: Event
rule: "if end exists: end >= start"
message: "Event end time must be after start time"
# No orphan tasks (should belong to a project or have explicit owner)
- type: Task
rule: "has_relation(part_of, Project) OR has_property(owner)"
enforcement: warn
message: "Task should belong to a project or have an explicit owner"
# Circular dependency prevention
- relation: blocks
rule: "acyclic"
message: "Circular task dependencies are not allowed"
```
+580
View File
@@ -0,0 +1,580 @@
#!/usr/bin/env python3
"""
Ontology graph operations: create, query, relate, validate.
Usage:
python ontology.py create --type Person --props '{"name":"Alice"}'
python ontology.py get --id p_001
python ontology.py query --type Task --where '{"status":"open"}'
python ontology.py relate --from proj_001 --rel has_task --to task_001
python ontology.py related --id proj_001 --rel has_task
python ontology.py list --type Person
python ontology.py delete --id p_001
python ontology.py validate
"""
import argparse
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
DEFAULT_GRAPH_PATH = "memory/ontology/graph.jsonl"
DEFAULT_SCHEMA_PATH = "memory/ontology/schema.yaml"
def resolve_safe_path(
user_path: str,
*,
root: Path | None = None,
must_exist: bool = False,
label: str = "path",
) -> Path:
"""Resolve user path within root and reject traversal outside it."""
if not user_path or not user_path.strip():
raise SystemExit(f"Invalid {label}: empty path")
safe_root = (root or Path.cwd()).resolve()
candidate = Path(user_path).expanduser()
if not candidate.is_absolute():
candidate = safe_root / candidate
try:
resolved = candidate.resolve(strict=False)
except OSError as exc:
raise SystemExit(f"Invalid {label}: {exc}") from exc
try:
resolved.relative_to(safe_root)
except ValueError:
raise SystemExit(
f"Invalid {label}: must stay within workspace root '{safe_root}'"
)
if must_exist and not resolved.exists():
raise SystemExit(f"Invalid {label}: file not found '{resolved}'")
return resolved
def generate_id(type_name: str) -> str:
"""Generate a unique ID for an entity."""
prefix = type_name.lower()[:4]
suffix = uuid.uuid4().hex[:8]
return f"{prefix}_{suffix}"
def load_graph(path: str) -> tuple[dict, list]:
"""Load entities and relations from graph file."""
entities = {}
relations = []
graph_path = Path(path)
if not graph_path.exists():
return entities, relations
with open(graph_path) as f:
for line in f:
line = line.strip()
if not line:
continue
record = json.loads(line)
op = record.get("op")
if op == "create":
entity = record["entity"]
entities[entity["id"]] = entity
elif op == "update":
entity_id = record["id"]
if entity_id in entities:
entities[entity_id]["properties"].update(record.get("properties", {}))
entities[entity_id]["updated"] = record.get("timestamp")
elif op == "delete":
entity_id = record["id"]
entities.pop(entity_id, None)
elif op == "relate":
relations.append({
"from": record["from"],
"rel": record["rel"],
"to": record["to"],
"properties": record.get("properties", {})
})
elif op == "unrelate":
relations = [r for r in relations
if not (r["from"] == record["from"]
and r["rel"] == record["rel"]
and r["to"] == record["to"])]
return entities, relations
def append_op(path: str, record: dict):
"""Append an operation to the graph file."""
graph_path = Path(path)
graph_path.parent.mkdir(parents=True, exist_ok=True)
with open(graph_path, "a") as f:
f.write(json.dumps(record) + "\n")
def create_entity(type_name: str, properties: dict, graph_path: str, entity_id: str = None) -> dict:
"""Create a new entity."""
entity_id = entity_id or generate_id(type_name)
timestamp = datetime.now(timezone.utc).isoformat()
entity = {
"id": entity_id,
"type": type_name,
"properties": properties,
"created": timestamp,
"updated": timestamp
}
record = {"op": "create", "entity": entity, "timestamp": timestamp}
append_op(graph_path, record)
return entity
def get_entity(entity_id: str, graph_path: str) -> dict | None:
"""Get entity by ID."""
entities, _ = load_graph(graph_path)
return entities.get(entity_id)
def query_entities(type_name: str, where: dict, graph_path: str) -> list:
"""Query entities by type and properties."""
entities, _ = load_graph(graph_path)
results = []
for entity in entities.values():
if type_name and entity["type"] != type_name:
continue
match = True
for key, value in where.items():
if entity["properties"].get(key) != value:
match = False
break
if match:
results.append(entity)
return results
def list_entities(type_name: str, graph_path: str) -> list:
"""List all entities of a type."""
entities, _ = load_graph(graph_path)
if type_name:
return [e for e in entities.values() if e["type"] == type_name]
return list(entities.values())
def update_entity(entity_id: str, properties: dict, graph_path: str) -> dict | None:
"""Update entity properties."""
entities, _ = load_graph(graph_path)
if entity_id not in entities:
return None
timestamp = datetime.now(timezone.utc).isoformat()
record = {"op": "update", "id": entity_id, "properties": properties, "timestamp": timestamp}
append_op(graph_path, record)
entities[entity_id]["properties"].update(properties)
entities[entity_id]["updated"] = timestamp
return entities[entity_id]
def delete_entity(entity_id: str, graph_path: str) -> bool:
"""Delete an entity."""
entities, _ = load_graph(graph_path)
if entity_id not in entities:
return False
timestamp = datetime.now(timezone.utc).isoformat()
record = {"op": "delete", "id": entity_id, "timestamp": timestamp}
append_op(graph_path, record)
return True
def create_relation(from_id: str, rel_type: str, to_id: str, properties: dict, graph_path: str):
"""Create a relation between entities."""
timestamp = datetime.now(timezone.utc).isoformat()
record = {
"op": "relate",
"from": from_id,
"rel": rel_type,
"to": to_id,
"properties": properties,
"timestamp": timestamp
}
append_op(graph_path, record)
return record
def get_related(entity_id: str, rel_type: str, graph_path: str, direction: str = "outgoing") -> list:
"""Get related entities."""
entities, relations = load_graph(graph_path)
results = []
for rel in relations:
if direction == "outgoing" and rel["from"] == entity_id:
if not rel_type or rel["rel"] == rel_type:
if rel["to"] in entities:
results.append({
"relation": rel["rel"],
"entity": entities[rel["to"]]
})
elif direction == "incoming" and rel["to"] == entity_id:
if not rel_type or rel["rel"] == rel_type:
if rel["from"] in entities:
results.append({
"relation": rel["rel"],
"entity": entities[rel["from"]]
})
elif direction == "both":
if rel["from"] == entity_id or rel["to"] == entity_id:
if not rel_type or rel["rel"] == rel_type:
other_id = rel["to"] if rel["from"] == entity_id else rel["from"]
if other_id in entities:
results.append({
"relation": rel["rel"],
"direction": "outgoing" if rel["from"] == entity_id else "incoming",
"entity": entities[other_id]
})
return results
def validate_graph(graph_path: str, schema_path: str) -> list:
"""Validate graph against schema constraints."""
entities, relations = load_graph(graph_path)
errors = []
# Load schema if exists
schema = load_schema(schema_path)
type_schemas = schema.get("types", {})
relation_schemas = schema.get("relations", {})
global_constraints = schema.get("constraints", [])
for entity_id, entity in entities.items():
type_name = entity["type"]
type_schema = type_schemas.get(type_name, {})
# Check required properties
required = type_schema.get("required", [])
for prop in required:
if prop not in entity["properties"]:
errors.append(f"{entity_id}: missing required property '{prop}'")
# Check forbidden properties
forbidden = type_schema.get("forbidden_properties", [])
for prop in forbidden:
if prop in entity["properties"]:
errors.append(f"{entity_id}: contains forbidden property '{prop}'")
# Check enum values
for prop, allowed in type_schema.items():
if prop.endswith("_enum"):
field = prop.replace("_enum", "")
value = entity["properties"].get(field)
if value and value not in allowed:
errors.append(f"{entity_id}: '{field}' must be one of {allowed}, got '{value}'")
# Relation constraints (type + cardinality + acyclicity)
rel_index = {}
for rel in relations:
rel_index.setdefault(rel["rel"], []).append(rel)
for rel_type, rel_schema in relation_schemas.items():
rels = rel_index.get(rel_type, [])
from_types = rel_schema.get("from_types", [])
to_types = rel_schema.get("to_types", [])
cardinality = rel_schema.get("cardinality")
acyclic = rel_schema.get("acyclic", False)
# Type checks
for rel in rels:
from_entity = entities.get(rel["from"])
to_entity = entities.get(rel["to"])
if not from_entity or not to_entity:
errors.append(f"{rel_type}: relation references missing entity ({rel['from']} -> {rel['to']})")
continue
if from_types and from_entity["type"] not in from_types:
errors.append(
f"{rel_type}: from entity {rel['from']} type {from_entity['type']} not in {from_types}"
)
if to_types and to_entity["type"] not in to_types:
errors.append(
f"{rel_type}: to entity {rel['to']} type {to_entity['type']} not in {to_types}"
)
# Cardinality checks
if cardinality in ("one_to_one", "one_to_many", "many_to_one"):
from_counts = {}
to_counts = {}
for rel in rels:
from_counts[rel["from"]] = from_counts.get(rel["from"], 0) + 1
to_counts[rel["to"]] = to_counts.get(rel["to"], 0) + 1
if cardinality in ("one_to_one", "many_to_one"):
for from_id, count in from_counts.items():
if count > 1:
errors.append(f"{rel_type}: from entity {from_id} violates cardinality {cardinality}")
if cardinality in ("one_to_one", "one_to_many"):
for to_id, count in to_counts.items():
if count > 1:
errors.append(f"{rel_type}: to entity {to_id} violates cardinality {cardinality}")
# Acyclic checks
if acyclic:
graph = {}
for rel in rels:
graph.setdefault(rel["from"], []).append(rel["to"])
visited = {}
def dfs(node, stack):
visited[node] = True
stack.add(node)
for nxt in graph.get(node, []):
if nxt in stack:
return True
if not visited.get(nxt, False):
if dfs(nxt, stack):
return True
stack.remove(node)
return False
for node in graph:
if not visited.get(node, False):
if dfs(node, set()):
errors.append(f"{rel_type}: cyclic dependency detected")
break
# Global constraints (limited enforcement)
for constraint in global_constraints:
ctype = constraint.get("type")
relation = constraint.get("relation")
rule = (constraint.get("rule") or "").strip().lower()
if ctype == "Event" and "end" in rule and "start" in rule:
for entity_id, entity in entities.items():
if entity["type"] != "Event":
continue
start = entity["properties"].get("start")
end = entity["properties"].get("end")
if start and end:
try:
start_dt = datetime.fromisoformat(start)
end_dt = datetime.fromisoformat(end)
if end_dt < start_dt:
errors.append(f"{entity_id}: end must be >= start")
except ValueError:
errors.append(f"{entity_id}: invalid datetime format in start/end")
if relation and rule == "acyclic":
# Already enforced above via relations schema
continue
return errors
def load_schema(schema_path: str) -> dict:
"""Load schema from YAML if it exists."""
schema = {}
schema_file = Path(schema_path)
if schema_file.exists():
import yaml
with open(schema_file) as f:
schema = yaml.safe_load(f) or {}
return schema
def write_schema(schema_path: str, schema: dict) -> None:
"""Write schema to YAML."""
schema_file = Path(schema_path)
schema_file.parent.mkdir(parents=True, exist_ok=True)
import yaml
with open(schema_file, "w") as f:
yaml.safe_dump(schema, f, sort_keys=False)
def merge_schema(base: dict, incoming: dict) -> dict:
"""Merge incoming schema into base, appending lists and deep-merging dicts."""
for key, value in (incoming or {}).items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
base[key] = merge_schema(base[key], value)
elif key in base and isinstance(base[key], list) and isinstance(value, list):
base[key] = base[key] + [v for v in value if v not in base[key]]
else:
base[key] = value
return base
def append_schema(schema_path: str, incoming: dict) -> dict:
"""Append/merge schema fragment into existing schema."""
base = load_schema(schema_path)
merged = merge_schema(base, incoming)
write_schema(schema_path, merged)
return merged
def main():
parser = argparse.ArgumentParser(description="Ontology graph operations")
subparsers = parser.add_subparsers(dest="command", required=True)
# Create
create_p = subparsers.add_parser("create", help="Create entity")
create_p.add_argument("--type", "-t", required=True, help="Entity type")
create_p.add_argument("--props", "-p", default="{}", help="Properties JSON")
create_p.add_argument("--id", help="Entity ID (auto-generated if not provided)")
create_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Get
get_p = subparsers.add_parser("get", help="Get entity by ID")
get_p.add_argument("--id", required=True, help="Entity ID")
get_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Query
query_p = subparsers.add_parser("query", help="Query entities")
query_p.add_argument("--type", "-t", help="Entity type")
query_p.add_argument("--where", "-w", default="{}", help="Filter JSON")
query_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# List
list_p = subparsers.add_parser("list", help="List entities")
list_p.add_argument("--type", "-t", help="Entity type")
list_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Update
update_p = subparsers.add_parser("update", help="Update entity")
update_p.add_argument("--id", required=True, help="Entity ID")
update_p.add_argument("--props", "-p", required=True, help="Properties JSON")
update_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Delete
delete_p = subparsers.add_parser("delete", help="Delete entity")
delete_p.add_argument("--id", required=True, help="Entity ID")
delete_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Relate
relate_p = subparsers.add_parser("relate", help="Create relation")
relate_p.add_argument("--from", dest="from_id", required=True, help="From entity ID")
relate_p.add_argument("--rel", "-r", required=True, help="Relation type")
relate_p.add_argument("--to", dest="to_id", required=True, help="To entity ID")
relate_p.add_argument("--props", "-p", default="{}", help="Relation properties JSON")
relate_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Related
related_p = subparsers.add_parser("related", help="Get related entities")
related_p.add_argument("--id", required=True, help="Entity ID")
related_p.add_argument("--rel", "-r", help="Relation type filter")
related_p.add_argument("--dir", "-d", choices=["outgoing", "incoming", "both"], default="outgoing")
related_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
# Validate
validate_p = subparsers.add_parser("validate", help="Validate graph")
validate_p.add_argument("--graph", "-g", default=DEFAULT_GRAPH_PATH)
validate_p.add_argument("--schema", "-s", default=DEFAULT_SCHEMA_PATH)
# Schema append
schema_p = subparsers.add_parser("schema-append", help="Append/merge schema fragment")
schema_p.add_argument("--schema", "-s", default=DEFAULT_SCHEMA_PATH)
schema_p.add_argument("--data", "-d", help="Schema fragment as JSON")
schema_p.add_argument("--file", "-f", help="Schema fragment file (YAML or JSON)")
args = parser.parse_args()
workspace_root = Path.cwd().resolve()
if hasattr(args, "graph"):
args.graph = str(
resolve_safe_path(args.graph, root=workspace_root, label="graph path")
)
if hasattr(args, "schema"):
args.schema = str(
resolve_safe_path(args.schema, root=workspace_root, label="schema path")
)
if hasattr(args, "file") and args.file:
args.file = str(
resolve_safe_path(
args.file, root=workspace_root, must_exist=True, label="schema file"
)
)
if args.command == "create":
props = json.loads(args.props)
entity = create_entity(args.type, props, args.graph, args.id)
print(json.dumps(entity, indent=2))
elif args.command == "get":
entity = get_entity(args.id, args.graph)
if entity:
print(json.dumps(entity, indent=2))
else:
print(f"Entity not found: {args.id}")
elif args.command == "query":
where = json.loads(args.where)
results = query_entities(args.type, where, args.graph)
print(json.dumps(results, indent=2))
elif args.command == "list":
results = list_entities(args.type, args.graph)
print(json.dumps(results, indent=2))
elif args.command == "update":
props = json.loads(args.props)
entity = update_entity(args.id, props, args.graph)
if entity:
print(json.dumps(entity, indent=2))
else:
print(f"Entity not found: {args.id}")
elif args.command == "delete":
if delete_entity(args.id, args.graph):
print(f"Deleted: {args.id}")
else:
print(f"Entity not found: {args.id}")
elif args.command == "relate":
props = json.loads(args.props)
rel = create_relation(args.from_id, args.rel, args.to_id, props, args.graph)
print(json.dumps(rel, indent=2))
elif args.command == "related":
results = get_related(args.id, args.rel, args.graph, args.dir)
print(json.dumps(results, indent=2))
elif args.command == "validate":
errors = validate_graph(args.graph, args.schema)
if errors:
print("Validation errors:")
for err in errors:
print(f" - {err}")
else:
print("Graph is valid.")
elif args.command == "schema-append":
if not args.data and not args.file:
raise SystemExit("schema-append requires --data or --file")
incoming = {}
if args.data:
incoming = json.loads(args.data)
else:
path = Path(args.file)
if path.suffix.lower() == ".json":
with open(path) as f:
incoming = json.load(f)
else:
import yaml
with open(path) as f:
incoming = yaml.safe_load(f) or {}
merged = append_schema(args.schema, incoming)
print(json.dumps(merged, indent=2))
if __name__ == "__main__":
main()
@@ -0,0 +1,7 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "proactive-agent-lite",
"installedVersion": "1.0.0",
"installedAt": 1774625594099
}
+66
View File
@@ -0,0 +1,66 @@
# Proactive Agent Lite
Transform your AI agents from passive responders into proactive partners that anticipate needs and continuously improve.
## Overview
Proactive Agent Lite implements battle-tested patterns that enable AI agents to:
- Learn from every interaction
- Create value without being asked
- Stay aligned with their mission
- Self-diagnose and fix issues
- Surface hidden opportunities
## Installation
```bash
clawhub install proactive-agent-lite
```
## Features
### 🧠 Memory Architecture
- **Pre-compaction flush**: Ensures important context survives even when the conversation window fills up
- **Long-term memory**: Maintains continuity across sessions
- **Context preservation**: Never loses track of important details
### 💡 Reverse Prompting
- **Idea generation**: Surfaces concepts and solutions you didn't know to ask for
- **Opportunity discovery**: Identifies potential improvements and optimizations
- **Proactive suggestions**: Offers relevant insights without waiting for explicit requests
### 🛡️ Security Hardening
- **Safe defaults**: Conservative approach to external actions
- **Permission awareness**: Respects boundaries and asks before acting
- **Data protection**: Never exposes private information
### 🔧 Self-Healing Patterns
- **Error detection**: Automatically identifies when something goes wrong
- **Recovery mechanisms**: Implements strategies to get back on track
- **Continuous improvement**: Learns from mistakes to prevent recurrence
### 🎯 Alignment Systems
- **Mission focus**: Always remembers the core objective
- **User-centric**: Prioritizes your needs and preferences
- **Value-driven**: Makes decisions based on what creates the most value for you
## Usage
Once installed, your agent will automatically begin exhibiting proactive behavior. No additional configuration is required, though advanced users can fine-tune the behavior through configuration files.
## Best Practices
- **Start conservative**: Begin with default settings and adjust as needed
- **Monitor interactions**: Observe how the proactive behavior manifests in your use cases
- **Provide feedback**: Help the agent learn what types of proactive suggestions are most valuable to you
## Integration
This skill works well with other OpenClaw skills and can enhance any agent workflow. Consider combining it with:
- `evomap-heartbeat-manager` for proactive network monitoring
- `evomap-work-processor` for proactive work opportunity handling
- Any domain-specific skills for enhanced proactive capabilities
## Support
For questions or feature requests, contact the skill maintainer through ClawHub.
+49
View File
@@ -0,0 +1,49 @@
---
name: proactive-agent-lite
description: Transform AI agents from task-followers into proactive partners with memory architecture, reverse prompting, and self-healing patterns. Lightweight version focused on core proactive capabilities.
metadata:
{
"openclaw":
{
"requires": {},
"install": []
}
}
---
# Proactive Agent Lite
Transform your AI agents from passive task-followers into proactive partners that anticipate needs and continuously improve.
## Core Features
- **Memory Architecture**: Pre-compaction flush ensures context survives when window fills
- **Reverse Prompting**: Surfaces ideas you didn't know to ask for
- **Security Hardening**: Built-in security considerations and safe defaults
- **Self-Healing Patterns**: Diagnoses and fixes its own issues automatically
- **Alignment Systems**: Stays on mission and remembers who it serves
## Key Benefits
**Anticipates Needs**: Proactively suggests solutions before you ask
**Continuous Learning**: Improves from every interaction without explicit training
**Mission-Focused**: Never loses sight of the core objective
**Self-Maintaining**: Automatically recovers from errors and inconsistencies
**Value Creation**: Generates insights and opportunities without being prompted
## Usage
This skill enhances any OpenClaw agent by providing proactive behavior patterns. Simply install and the agent will automatically begin exhibiting proactive characteristics.
## Integration
Works seamlessly with all OpenClaw agents and can be combined with other skills for enhanced functionality.
## Requirements
- OpenClaw v1.0 or higher
- Standard agent configuration
## Customization
The proactive behavior can be tuned through configuration parameters to match your preferred level of initiative and communication style.
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn778z31czjyj515snpsht5ghh80hy50",
"slug": "proactive-agent-lite",
"version": "1.0.0",
"publishedAt": 1772250292761
}
+136
View File
@@ -0,0 +1,136 @@
---
name: Proactivity (Proactive Agent)
slug: proactivity
version: 1.0.1
homepage: https://clawic.com/skills/proactivity
description: Anticipates needs, keeps work moving, and improves through use so the agent gets more proactive over time.
changelog: "Strengthens proactive behavior with reverse prompting, self-healing, working-buffer recovery, and clearer SOUL and AGENTS setup."
metadata: {"clawdbot":{"emoji":"⚡","requires":{"bins":[]},"os":["linux","darwin","win32"],"configPaths":["~/proactivity/"],"configPaths.optional":["./AGENTS.md","./TOOLS.md","./SOUL.md","./HEARTBEAT.md"]}}
---
## Architecture
Proactive state lives in `~/proactivity/` and separates durable boundaries from active work. If that folder is missing or empty, run `setup.md`.
```
~/proactivity/
├── memory.md # Stable activation and boundary rules
├── session-state.md # Current task, last decision, next move
├── heartbeat.md # Lightweight recurring checks
├── patterns.md # Reusable proactive moves that worked
├── log.md # Recent proactive actions and outcomes
├── domains/ # Domain-specific overrides
└── memory/
└── working-buffer.md # Volatile breadcrumbs for long tasks
```
## When to Use
Use when the user wants the agent to think ahead, anticipate needs, keep momentum without waiting for prompts, recover context fast, and follow through like a strong operator.
## Quick Reference
| Topic | File |
|-------|------|
| Setup guide | `setup.md` |
| Memory template | `memory-template.md` |
| Migration guide | `migration.md` |
| Opportunity signals | `signals.md` |
| Execution patterns | `execution.md` |
| Boundary rules | `boundaries.md` |
| State routing | `state.md` |
| Recovery flow | `recovery.md` |
| Heartbeat rules | `heartbeat-rules.md` |
## Core Rules
### 1. Work Like a Proactive Partner, Not a Prompt Follower
- Notice what is likely to matter next.
- Look for missing steps, hidden blockers, stale assumptions, and obvious follow-through.
- Ask "what would genuinely help now?" before waiting for another prompt.
### 2. Use Reverse Prompting
- Surface ideas, checks, drafts, and next steps the user did not think to ask for.
- Good reverse prompting is concrete and timely, never vague or noisy.
- If there is no clear value, stay quiet.
### 3. Keep Momentum Alive
- Leave the next useful move after meaningful work.
- Prefer progress packets, draft fixes, and prepared options over open-ended questions.
- Do not let work stall just because the user has not spoken again yet.
### 4. Recover Fast When Context Gets Fragile
- Use session state and the working buffer to survive long tasks, interruptions, and compaction.
- Reconstruct recent work before asking the user to restate it.
- If recovery still leaves ambiguity, ask only for the missing delta.
### 5. Practice Relentless Resourcefulness
- Try multiple reasonable approaches before escalating.
- Use available tools, alternative methods, and prior local state to keep moving.
- Escalate with evidence, what was tried, and the best next step.
### 6. Self-Heal Before Complaining
- When a workflow breaks, first diagnose, adapt, retry, or downgrade gracefully.
- Fix local process issues that are safe to fix.
- Do not normalize repeated friction if a better path can be established.
### 7. Check In Proactively Inside Clear Boundaries
- Heartbeat should follow up on stale blockers, promises, deadlines, and likely missed steps.
- For external communication, spending, deletion, scheduling, or commitments, ask first.
- Never overstep quietly and never fake certainty.
## Common Traps
| Trap | Why It Fails | Better Move |
|------|--------------|-------------|
| Waiting for the next prompt | Makes the agent feel passive | Push the next useful move |
| Asking the user to restate recent work | Feels forgetful and lazy | Run recovery first |
| Surfacing every idea | Creates alert fatigue | Use reverse prompting only when value is clear |
| Giving up after one failed attempt | Feels weak and dependent | Try multiple approaches before escalating |
| Acting externally because it feels obvious | Breaks trust | Ask before any external action |
## Scope
This skill ONLY:
- creates and maintains local proactive state in `~/proactivity/`
- proposes workspace integration for AGENTS, TOOLS, SOUL, and HEARTBEAT when the user explicitly wants it
- uses heartbeat follow-through only within learned boundaries
This skill NEVER:
- edits any file outside `~/proactivity/` without explicit user approval in that session
- applies hidden workspace changes without showing the exact proposed lines first
- sends messages, spends money, deletes data, or makes commitments without approval
- keeps sensitive user data out of proactive state files
## Data Storage
Local state lives in `~/proactivity/`:
- stable memory for durable boundaries and activation preferences
- session state for the current objective, blocker, and next move
- heartbeat state for recurring follow-up items
- reusable patterns for proactive wins that worked
- action log for recent proactive actions and outcomes
- working buffer for volatile recovery breadcrumbs
## Security & Privacy
- This skill stores local operating notes in `~/proactivity/`.
- It does not require network access by itself.
- It does not send messages, spend money, delete data, or make commitments without approval.
- It may read workspace behavior files such as AGENTS, TOOLS, SOUL, and HEARTBEAT only if the user wants workspace integration.
- Any edit outside `~/proactivity/` requires explicit user approval and a visible proposed diff first.
- It never modifies its own `SKILL.md`.
## Related Skills
Install with `clawhub install <slug>` if user confirms:
- `self-improving` - Learn reusable execution lessons from corrections and reflection
- `heartbeat` - Run lightweight recurring checks and follow-through loops
- `calendar-planner` - Turn proactive timing into concrete calendar decisions
- `skill-finder` - Discover adjacent skills when a task needs more than proactivity
## Feedback
- If useful: `clawhub star proactivity`
- Stay updated: `clawhub sync`
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn73vp5rarc3b14rc7wjcw8f8580t5d1",
"slug": "proactivity",
"version": "1.0.1",
"publishedAt": 1773074783565
}
+42
View File
@@ -0,0 +1,42 @@
# Boundary Learning
Proactivity is only useful when the user can predict the line it will not cross.
## Learn the Boundary Once
When a new proactive action appears, ask with a specific action:
```text
I could watch CI failures and prepare fixes automatically.
Should I do that automatically, suggest first, always ask, or skip it?
```
Record the answer in the stable proactivity memory, then reuse it.
## Default Ladder
| Level | Meaning | Typical examples |
|-------|---------|------------------|
| DO | Safe internal work | research, drafts, checks, local prep |
| SUGGEST | Useful but user-visible | fix proposals, scheduling suggestions |
| ASK | Needs approval first | send, buy, delete, reschedule, notify |
| NEVER | Off-limits | contact people, commit on their behalf |
## Good Boundary Questions
- One action at a time
- Specific domain and outcome
- Easy answer with four clear levels
## Bad Boundary Questions
- Broad prompts with no action
- Hidden bundles of multiple actions
- Questions that rely on silence as approval
## Conflict Rules
- Most specific rule wins
- Recent explicit user instruction beats older memory
- Temporary rules expire when the situation ends
- If two rules still conflict, ask once and update memory
+86
View File
@@ -0,0 +1,86 @@
# Execution Patterns
## The Proactive Loop
```text
1. NOTICE -> Spot the need, blocker, or opening
2. RECOVER -> Rebuild current state if needed
3. CHECK -> Read boundary and domain rules
4. EXPLORE -> Try useful paths, tools, and alternatives
5. DECIDE -> DO / SUGGEST / ASK / NEVER
6. ACT -> Execute or present the next move
7. HAND OFF -> Leave the next useful step in state
```
## Execution by Level
### DO
- Safe internal work
- Reversible preparation
- Drafts, checks, and structured follow-through
### SUGGEST
- Best when the move is useful but changes user-visible work
- Present trigger, recommendation, and expected outcome
### ASK
- Use for external communication, commitments, spending, deletion, and schedule changes
- Offer options if there is more than one reasonable move
### NEVER
- Do not perform or imply the action without explicit approval
## Message Shape
Good proactive output is short and concrete:
```text
Trigger: CI failed on missing env var
Best move: add DATABASE_URL to the deployment secret set
Next step: I can prepare the exact change if you want
```
Bad proactive output is vague:
```text
Something might need attention. What should I do?
```
## Reverse Prompting
Use reverse prompting when the user would benefit from:
- a next step they did not ask for
- a check that prevents avoidable rework
- a draft that removes friction
- a decision packet with clear options
Bad reverse prompting is random brainstorming.
Good reverse prompting feels like strong judgment.
## Relentless Execution
Before escalating:
1. Try the direct path
2. Try an alternative tool or method
3. Search local state for similar work
4. Verify the mechanism, not just the intent
5. Gather enough evidence to make a recommendation
6. Escalate only with a specific next step
## Self-Healing
When the process itself breaks:
1. Diagnose the failure mode
2. Try a safe recovery path
3. Downgrade gracefully if the ideal path is blocked
4. Update state so the same confusion does not repeat
5. Escalate only after meaningful attempts
## Output Hygiene
- Leave one clear next move in the session-state file
- Log outcomes in the action log
- Promote repeat wins to the reusable pattern log
+37
View File
@@ -0,0 +1,37 @@
# Heartbeat Rules
Heartbeat proactivity should protect momentum, not create noise.
## Good Heartbeat Checks
- promised follow-ups that are now due
- stale blockers that may be unblocked
- deadlines or reviews approaching soon
- active work with no clear next step
- moments where a proactive suggestion would remove avoidable friction
## Message Only When
- something changed
- the user needs to decide
- a prepared draft or recommendation is ready
- the cost of waiting is real
- the check-in clearly helps more than it interrupts
## Stay Silent When
- the item is unchanged
- the signal is weak
- the action is still vague
- the update would only repeat old information
## Logging
- move recurring checks to heartbeat state
- record outcomes in the action log
- promote repeat wins to the reusable pattern log
## Behavior Standard
Heartbeat is not just monitoring.
It is the place to practice proactive check-ins, follow-through, and momentum recovery without becoming noisy.
+78
View File
@@ -0,0 +1,78 @@
# Memory Template - Proactivity
Create `~/proactivity/memory.md` with this structure:
```markdown
# Proactivity Memory
## Status
status: ongoing
version: 1.0.1
last: YYYY-MM-DD
integration: pending | complete | paused | never_ask
## Activation Preferences
- When this skill should auto-activate
- Whether it should jump in on blocked work, context drift, or missing next steps
- Quiet hours, batching, and message style
## Action Boundaries
- Safe actions it may do automatically
- Actions it should suggest first
- Actions that always require approval
- Actions it should never take
## State Rules
- What belongs in the session-state file
- When the working-buffer file should be used
- When active state should be cleared or refreshed
## Heartbeat Behavior
- What should be re-checked in the background
- Which changes deserve a message
- What should stay silent unless asked
## Notes
- Durable operating preferences
- Reliable trigger patterns
- Boundary exceptions worth keeping
---
*Updated: YYYY-MM-DD*
```
## Status Values
| Value | Meaning | Behavior |
|-------|---------|----------|
| `ongoing` | Setup still evolving | Keep learning useful boundaries |
| `complete` | Stable proactivity setup | Focus on execution and follow-through |
| `paused` | User wants less proactivity | Run only on explicit request |
| `never_ask` | User does not want setup prompts | Stop proactive setup questions |
## Local Files to Initialize
```bash
mkdir -p ~/proactivity/{domains,memory}
touch ~/proactivity/{memory.md,session-state.md,heartbeat.md,patterns.md,log.md}
touch ~/proactivity/memory/working-buffer.md
```
## Templates for Other Files
`session-state.md`
```markdown
# Session State
- Current objective
- Last confirmed decision
- Blocker or open question
- Next useful move
```
`heartbeat.md`
```markdown
# Heartbeat
- Active follow-ups worth re-checking
- Recurring checks that should stay lightweight
- Conditions that justify messaging the user
```
+41
View File
@@ -0,0 +1,41 @@
# Migration Guide - Proactivity
## v1.0.1 Architecture Update
This update keeps the same home folder, `~/proactivity/`, and preserves existing files.
The new version adds active-state files for recovery and follow-through.
### Before
- `~/proactivity/memory.md`
- `~/proactivity/domains/`
- `~/proactivity/patterns.md`
- `~/proactivity/log.md`
### After
- `~/proactivity/memory.md`
- `~/proactivity/session-state.md`
- `~/proactivity/heartbeat.md`
- `~/proactivity/patterns.md`
- `~/proactivity/log.md`
- `~/proactivity/domains/`
- `~/proactivity/memory/working-buffer.md`
## Safe Migration
1. Create the new files without deleting the old ones:
```bash
mkdir -p ~/proactivity/memory
touch ~/proactivity/session-state.md
touch ~/proactivity/heartbeat.md
touch ~/proactivity/memory/working-buffer.md
```
2. Keep `memory.md`, `patterns.md`, and `log.md` exactly as they are.
3. If old proactive rules live in free-form notes, copy them into the new sections in `memory.md`.
4. Start writing only live task state to session state and working buffer.
5. Do not delete or rename any legacy file unless the user explicitly asks for cleanup later.
+28
View File
@@ -0,0 +1,28 @@
# Recovery Flow
When context gets long, interrupted, or stale, recover before asking the user to repeat work.
## Recovery Order
1. Read stable proactivity memory
2. Read session state
3. Read the working buffer if the task was long or tool-heavy
4. Reconstruct:
- current objective
- last confirmed decision
- blocker or missing input
- next useful move
## Good Recovery
- "Last agreed move was to draft the fix, and the blocker is missing deploy access. I can prepare the patch now."
## Bad Recovery
- "Can you remind me what we were doing?"
## When to Ask the User
- a required input is still missing after recovery
- local state conflicts with a newer instruction
- the task changed direction and old state is no longer trustworthy
+135
View File
@@ -0,0 +1,135 @@
# Setup - Proactivity
Use this file when `~/proactivity/` is missing or empty.
Do not turn setup into a long interview. Install the behavior path first, keep helping immediately, and personalize through real use.
## Immediate First-Run Actions
### 1. Add the SOUL steering early
If a workspace SOUL file exists, prepare the proactivity steering early. Do not bury this behind preference questions.
Show the exact snippet immediately and wait for explicit approval before writing it.
```markdown
**Proactivity**
Being proactive is part of the job, not an extra.
Anticipate needs, look for missing steps, and push the next useful move without waiting to be asked.
Use reverse prompting when a suggestion, draft, check, or option would genuinely help.
Recover active state before asking the user to restate work.
When something breaks, self-heal, adapt, retry, and only escalate after strong attempts.
Stay quiet instead of creating vague or noisy proactivity.
```
### 2. Refine the AGENTS routing early
If a workspace AGENTS file exists, prepare the proactive routing early and non-destructively.
Show the exact snippet immediately and wait for explicit approval before writing it.
Add this continuity line next to other memory sources:
```markdown
- **Proactivity:** `~/proactivity/` (via `proactivity` skill) - proactive operating state, action boundaries, active task recovery, and follow-through rules
```
Right after the sentence "Capture what matters...", add:
```markdown
Use ~/proactivity/memory.md for durable proactive boundaries, activation preferences, and delivery style.
Use ~/proactivity/session-state.md for the current objective, last decision, blocker, and next move.
Use ~/proactivity/memory/working-buffer.md for volatile breadcrumbs during long or fragile tasks.
Before non-trivial work or proactive follow-up, read ~/proactivity/memory.md and ~/proactivity/session-state.md, then load the working buffer only when recovery risk is high.
Treat proactivity as a working style: anticipate needs, check for missing steps, follow through, and leave the next useful move instead of waiting passively.
```
Before the "Write It Down" subsection, add:
```markdown
Before any non-trivial task:
- Read ~/proactivity/memory.md
- Read ~/proactivity/session-state.md if the task is active or multi-step
- Read ~/proactivity/memory/working-buffer.md if context is long, fragile, or likely to drift
- Recover from local state before asking the user to repeat recent work
- Check whether there is an obvious blocker, next step, or useful suggestion the user has not asked for yet
- Leave one clear next move in state before the final response when work is ongoing
```
Inside the "Write It Down" bullets, refine behavior:
```markdown
- Durable proactive preference or boundary -> append to ~/proactivity/memory.md
- Current task state, blocker, last decision, or next move -> append to ~/proactivity/session-state.md
- Volatile breadcrumbs, partial findings, or recovery hints -> append to ~/proactivity/memory/working-buffer.md
- Repeat proactive win worth reusing -> append to ~/proactivity/patterns.md
- Proactive action taken or suggested -> append to ~/proactivity/log.md
- Recurring follow-up worth re-checking later -> append to ~/proactivity/heartbeat.md
```
### 3. Add the HEARTBEAT check early
If a workspace HEARTBEAT file exists, prepare the proactive check-in loop early.
Show the exact snippet immediately and wait for explicit approval before writing it.
```markdown
## Proactivity Check
- Read ~/proactivity/heartbeat.md
- Re-check active blockers, promised follow-ups, stale work, and missing decisions
- Ask what useful check-in or next move would help right now
- Message the user only when something changed or needs a decision
- Update ~/proactivity/session-state.md after meaningful follow-through
```
### 4. Add the TOOLS guidance early
Do not edit the workspace TOOLS file automatically.
Show the exact snippet immediately and wait for explicit approval before writing it.
```markdown
## Proactive Tool Use
- Prefer safe internal work, drafts, checks, and preparation before escalating
- Use tools to keep work moving when the next step is clear and reversible
- Try multiple approaches and alternative tools before asking for help
- Use tools to test assumptions, verify mechanisms, and uncover blockers early
- For send, spend, delete, reschedule, or contact actions, stop and ask first
- If a tool result changes active work, update ~/proactivity/session-state.md
```
### 5. Create local state once the routing is in place
Create the local folder and baseline files after the behavior path is installed:
```bash
mkdir -p ~/proactivity/{domains,memory}
touch ~/proactivity/{memory.md,session-state.md,heartbeat.md,patterns.md,log.md}
touch ~/proactivity/memory/working-buffer.md
chmod 700 ~/proactivity ~/proactivity/domains ~/proactivity/memory
chmod 600 ~/proactivity/{memory.md,session-state.md,heartbeat.md,patterns.md,log.md}
chmod 600 ~/proactivity/memory/working-buffer.md
```
If `~/proactivity/memory.md` is empty, initialize it from `memory-template.md`.
### 6. Personalize lightly while helping
Do not run a long onboarding interview.
Default to a useful proactive baseline and learn from real use:
- suggest the next step when it would remove friction
- check for blockers, follow-ups, and missing decisions
- keep trying different approaches before escalating
- ask before external, irreversible, public, or third-party-impacting work
Ask at most one short question only when the answer materially changes the behavior.
### 7. What to save
- activation preferences and quiet hours
- action boundaries by domain
- active work state and recovery hints
- follow-up items that deserve heartbeat review
- proactive moves that worked well enough to reuse
+42
View File
@@ -0,0 +1,42 @@
# Opportunity Signals
Useful proactivity starts with strong triggers, not generic enthusiasm.
## High-Value Triggers
| Trigger | Example | Good proactive move |
|---------|---------|---------------------|
| Stalled work | No clear next step after a long task | Propose the next move |
| Context drift | Active task spans many turns | Refresh from local state before replying |
| Repetition | Same task or workaround appears 3+ times | Suggest automation or a reusable pattern |
| Time window | Deadline, review, or follow-up is approaching | Prepare draft or reminder early |
| Recoverable blocker | Tool failed but alternatives exist | Keep trying and report the best path |
| Promise made | "Will check later" or "follow up tomorrow" | Put it on heartbeat and revisit |
## What Deserves a Message
- A concrete recommendation with clear value
- A finished draft, check, or decision packet
- A blocker that needs approval or missing information
- A change in state that matters now
## What Stays Silent
- Vague feelings that something might matter
- Low-confidence guesses with no action attached
- Things the user already knows and cannot act on
- Repeating the same reminder without new information
## Timing Rules
- Immediate: failures, deadlines, safety issues, and time-sensitive openings
- Batched: low-urgency cleanups, patterns, and optional ideas
- Quiet by default during off-hours unless the user wants always-on behavior
## Confidence Ladder
| Confidence | Move |
|------------|------|
| High | Act if boundary allows |
| Medium | Suggest with recommendation |
| Low | Wait, gather more evidence, or stay silent |
+36
View File
@@ -0,0 +1,36 @@
# State Routing
Proactivity works best when stable memory and live task state stay separate.
## Use Stable Memory For
- durable activation preferences
- action boundaries that should persist
- batching, timing, and style preferences
- recurring rules the user expects later
## Use Session State For
- current objective
- last confirmed decision
- current blocker
- next useful move
## Use the Working Buffer For
- volatile breadcrumbs during long tasks
- partial findings not ready for durable memory
- recovery hints after tool-heavy work
- temporary notes that should be cleared later
## Use Heartbeat State For
- promised follow-ups
- stale blockers worth re-checking
- recurring checks that should stay lightweight
- triggers that justify messaging the user
## Routing Rule
If the note should still matter next week, it belongs in stable memory.
If it matters for the current task only, it belongs in active state.
+5
View File
@@ -0,0 +1,5 @@
## Self-Improving Check
- Read `./skills/self-improving/heartbeat-rules.md`
- Use `~/self-improving/heartbeat-state.md` for last-run markers and action notes
- If no file inside `~/self-improving/` changed since the last reviewed change, return `HEARTBEAT_OK`
+250
View File
@@ -0,0 +1,250 @@
---
name: Self-Improving + Proactive Agent
slug: self-improving
version: 1.2.16
homepage: https://clawic.com/skills/self-improving
description: "Self-reflection + Self-criticism + Self-learning + Self-organizing memory. Agent evaluates its own work, catches mistakes, and improves permanently. Use when (1) a command, tool, API, or operation fails; (2) the user corrects you or rejects your work; (3) you realize your knowledge is outdated or incorrect; (4) you discover a better approach; (5) the user explicitly installs or references the skill for the current task."
changelog: "Clarifies the setup flow for proactive follow-through and safer installation behavior."
metadata: {"clawdbot":{"emoji":"🧠","requires":{"bins":[]},"os":["linux","darwin","win32"],"configPaths":["~/self-improving/"],"configPaths.optional":["./AGENTS.md","./SOUL.md","./HEARTBEAT.md"]}}
---
## When to Use
User corrects you or points out mistakes. You complete significant work and want to evaluate the outcome. You notice something in your own output that could be better. Knowledge should compound over time without manual maintenance.
## Architecture
Memory lives in `~/self-improving/` with tiered structure. If `~/self-improving/` does not exist, run `setup.md`.
Workspace setup should add the standard self-improving steering to the workspace AGENTS, SOUL, and `HEARTBEAT.md` files, with recurring maintenance routed through `heartbeat-rules.md`.
```
~/self-improving/
├── memory.md # HOT: ≤100 lines, always loaded
├── index.md # Topic index with line counts
├── heartbeat-state.md # Heartbeat state: last run, reviewed change, action notes
├── projects/ # Per-project learnings
├── domains/ # Domain-specific (code, writing, comms)
├── archive/ # COLD: decayed patterns
└── corrections.md # Last 50 corrections log
```
## Quick Reference
| Topic | File |
|-------|------|
| Setup guide | `setup.md` |
| Heartbeat state template | `heartbeat-state.md` |
| Memory template | `memory-template.md` |
| Workspace heartbeat snippet | `HEARTBEAT.md` |
| Heartbeat rules | `heartbeat-rules.md` |
| Learning mechanics | `learning.md` |
| Security boundaries | `boundaries.md` |
| Scaling rules | `scaling.md` |
| Memory operations | `operations.md` |
| Self-reflection log | `reflections.md` |
| OpenClaw HEARTBEAT seed | `openclaw-heartbeat.md` |
## Requirements
- No credentials required
- No extra binaries required
- Optional installation of the `Proactivity` skill may require network access
## Learning Signals
Log automatically when you notice these patterns:
**Corrections** → add to `corrections.md`, evaluate for `memory.md`:
- "No, that's not right..."
- "Actually, it should be..."
- "You're wrong about..."
- "I prefer X, not Y"
- "Remember that I always..."
- "I told you before..."
- "Stop doing X"
- "Why do you keep..."
**Preference signals** → add to `memory.md` if explicit:
- "I like when you..."
- "Always do X for me"
- "Never do Y"
- "My style is..."
- "For [project], use..."
**Pattern candidates** → track, promote after 3x:
- Same instruction repeated 3+ times
- Workflow that works well repeatedly
- User praises specific approach
**Ignore** (don't log):
- One-time instructions ("do X now")
- Context-specific ("in this file...")
- Hypotheticals ("what if...")
## Self-Reflection
After completing significant work, pause and evaluate:
1. **Did it meet expectations?** — Compare outcome vs intent
2. **What could be better?** — Identify improvements for next time
3. **Is this a pattern?** — If yes, log to `corrections.md`
**When to self-reflect:**
- After completing a multi-step task
- After receiving feedback (positive or negative)
- After fixing a bug or mistake
- When you notice your output could be better
**Log format:**
```
CONTEXT: [type of task]
REFLECTION: [what I noticed]
LESSON: [what to do differently]
```
**Example:**
```
CONTEXT: Building Flutter UI
REFLECTION: Spacing looked off, had to redo
LESSON: Check visual spacing before showing user
```
Self-reflection entries follow the same promotion rules: 3x applied successfully → promote to HOT.
## Quick Queries
| User says | Action |
|-----------|--------|
| "What do you know about X?" | Search all tiers for X |
| "What have you learned?" | Show last 10 from `corrections.md` |
| "Show my patterns" | List `memory.md` (HOT) |
| "Show [project] patterns" | Load `projects/{name}.md` |
| "What's in warm storage?" | List files in `projects/` + `domains/` |
| "Memory stats" | Show counts per tier |
| "Forget X" | Remove from all tiers (confirm first) |
| "Export memory" | ZIP all files |
## Memory Stats
On "memory stats" request, report:
```
📊 Self-Improving Memory
HOT (always loaded):
memory.md: X entries
WARM (load on demand):
projects/: X files
domains/: X files
COLD (archived):
archive/: X files
Recent activity (7 days):
Corrections logged: X
Promotions to HOT: X
Demotions to WARM: X
```
## Common Traps
| Trap | Why It Fails | Better Move |
|------|--------------|-------------|
| Learning from silence | Creates false rules | Wait for explicit correction or repeated evidence |
| Promoting too fast | Pollutes HOT memory | Keep new lessons tentative until repeated |
| Reading every namespace | Wastes context | Load only HOT plus the smallest matching files |
| Compaction by deletion | Loses trust and history | Merge, summarize, or demote instead |
## Core Rules
### 1. Learn from Corrections and Self-Reflection
- Log when user explicitly corrects you
- Log when you identify improvements in your own work
- Never infer from silence alone
- After 3 identical lessons → ask to confirm as rule
### 2. Tiered Storage
| Tier | Location | Size Limit | Behavior |
|------|----------|------------|----------|
| HOT | memory.md | ≤100 lines | Always loaded |
| WARM | projects/, domains/ | ≤200 lines each | Load on context match |
| COLD | archive/ | Unlimited | Load on explicit query |
### 3. Automatic Promotion/Demotion
- Pattern used 3x in 7 days → promote to HOT
- Pattern unused 30 days → demote to WARM
- Pattern unused 90 days → archive to COLD
- Never delete without asking
### 4. Namespace Isolation
- Project patterns stay in `projects/{name}.md`
- Global preferences in HOT tier (memory.md)
- Domain patterns (code, writing) in `domains/`
- Cross-namespace inheritance: global → domain → project
### 5. Conflict Resolution
When patterns contradict:
1. Most specific wins (project > domain > global)
2. Most recent wins (same level)
3. If ambiguous → ask user
### 6. Compaction
When file exceeds limit:
1. Merge similar corrections into single rule
2. Archive unused patterns
3. Summarize verbose entries
4. Never lose confirmed preferences
### 7. Transparency
- Every action from memory → cite source: "Using X (from projects/foo.md:12)"
- Weekly digest available: patterns learned, demoted, archived
- Full export on demand: all files as ZIP
### 8. Security Boundaries
See `boundaries.md` — never store credentials, health data, third-party info.
### 9. Graceful Degradation
If context limit hit:
1. Load only memory.md (HOT)
2. Load relevant namespace on demand
3. Never fail silently — tell user what's not loaded
## Scope
This skill ONLY:
- Learns from user corrections and self-reflection
- Stores preferences in local files (`~/self-improving/`)
- Maintains heartbeat state in `~/self-improving/heartbeat-state.md` when the workspace integrates heartbeat
- Reads its own memory files on activation
This skill NEVER:
- Accesses calendar, email, or contacts
- Makes network requests
- Reads files outside `~/self-improving/`
- Infers preferences from silence or observation
- Deletes or blindly rewrites self-improving memory during heartbeat cleanup
- Modifies its own SKILL.md
## Data Storage
Local state lives in `~/self-improving/`:
- `memory.md` for HOT rules and confirmed preferences
- `corrections.md` for explicit corrections and reusable lessons
- `projects/` and `domains/` for scoped patterns
- `archive/` for decayed or inactive patterns
- `heartbeat-state.md` for recurring maintenance markers
## Related Skills
Install with `clawhub install <slug>` if user confirms:
- `memory` — Long-term memory patterns for agents
- `learning` — Adaptive teaching and explanation
- `decide` — Auto-learn decision patterns
- `escalate` — Know when to ask vs act autonomously
## Feedback
- If useful: `clawhub star self-improving`
- Stay updated: `clawhub sync`
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn73vp5rarc3b14rc7wjcw8f8580t5d1",
"slug": "self-improving",
"version": "1.2.16",
"publishedAt": 1773329327755
}
+59
View File
@@ -0,0 +1,59 @@
# Security Boundaries
## Never Store
| Category | Examples | Why |
|----------|----------|-----|
| Credentials | Passwords, API keys, tokens, SSH keys | Security breach risk |
| Financial | Card numbers, bank accounts, crypto seeds | Fraud risk |
| Medical | Diagnoses, medications, conditions | Privacy, HIPAA |
| Biometric | Voice patterns, behavioral fingerprints | Identity theft |
| Third parties | Info about other people | No consent obtained |
| Location patterns | Home/work addresses, routines | Physical safety |
| Access patterns | What systems user has access to | Privilege escalation |
## Store with Caution
| Category | Rules |
|----------|-------|
| Work context | Decay after project ends, never share cross-project |
| Emotional states | Only if user explicitly shares, never infer |
| Relationships | Roles only ("manager", "client"), no personal details |
| Schedules | General patterns OK ("busy mornings"), not specific times |
## Transparency Requirements
1. **Audit on demand** — User asks "what do you know about me?" → full export
2. **Source tracking** — Every item tagged with when/how learned
3. **Explain actions** — "I did X because you said Y on [date]"
4. **No hidden state** — If it affects behavior, it must be visible
5. **Deletion verification** — Confirm item removed, show updated state
## Red Flags to Catch
If you find yourself doing any of these, STOP:
- Storing something "just in case it's useful later"
- Inferring sensitive info from non-sensitive data
- Keeping data after user asked to forget
- Applying personal context to work (or vice versa)
- Learning what makes user comply faster
- Building psychological profile
- Retaining third-party information
## Kill Switch
User says "forget everything":
1. Export current memory to file (so they can review)
2. Wipe all learned data
3. Confirm: "Memory cleared. Starting fresh."
4. Do not retain "ghost patterns" in behavior
## Consent Model
| Data Type | Consent Level |
|-----------|---------------|
| Explicit corrections | Implied by correction itself |
| Inferred preferences | Ask after 3 observations |
| Context/project data | Ask when first detected |
| Cross-session patterns | Explicit opt-in required |
+36
View File
@@ -0,0 +1,36 @@
# Corrections Log — Template
> This file is created in `~/self-improving/corrections.md` when you first use the skill.
> Keeps the last 50 corrections. Older entries are evaluated for promotion or archived.
## Example Entries
```markdown
## 2026-02-19
### 14:32 — Code style
- **Correction:** "Use 2-space indentation, not 4"
- **Context:** Editing TypeScript file
- **Count:** 1 (first occurrence)
### 16:15 — Communication
- **Correction:** "Don't start responses with 'Great question!'"
- **Context:** Chat response
- **Count:** 3 → **PROMOTED to memory.md**
## 2026-02-18
### 09:00 — Project: website
- **Correction:** "For this project, always use Tailwind"
- **Context:** CSS discussion
- **Action:** Added to projects/website.md
```
## Log Format
Each entry includes:
- **Timestamp** — When the correction happened
- **Correction** — What the user said
- **Context** — What triggered it
- **Count** — How many times (for promotion tracking)
- **Action** — Where it was stored (if promoted)
+54
View File
@@ -0,0 +1,54 @@
# Heartbeat Rules
Use heartbeat to keep `~/self-improving/` organized without creating churn or losing data.
## Source of Truth
Keep the workspace `HEARTBEAT.md` snippet minimal.
Treat this file as the stable contract for self-improving heartbeat behavior.
Store mutable run state only in `~/self-improving/heartbeat-state.md`.
## Start of Every Heartbeat
1. Ensure `~/self-improving/heartbeat-state.md` exists.
2. Write `last_heartbeat_started_at` immediately in ISO 8601.
3. Read the previous `last_reviewed_change_at`.
4. Scan `~/self-improving/` for files changed after that moment, excluding `heartbeat-state.md` itself.
## If Nothing Changed
- Set `last_heartbeat_result: HEARTBEAT_OK`
- Append a short "no material change" note if you keep an action log
- Return `HEARTBEAT_OK`
## If Something Changed
Only do conservative organization:
- refresh `index.md` if counts or file references drift
- compact oversized files by merging duplicates or summarizing repetitive entries
- move clearly misplaced notes to the right namespace only when the target is unambiguous
- preserve confirmed rules and explicit corrections exactly
- update `last_reviewed_change_at` only after the review finishes cleanly
## Safety Rules
- Most heartbeat runs should do nothing
- Prefer append, summarize, or index fixes over large rewrites
- Never delete data, empty files, or overwrite uncertain text
- Never reorganize files outside `~/self-improving/`
- If scope is ambiguous, leave files untouched and record a suggested follow-up instead
## State Fields
Keep `~/self-improving/heartbeat-state.md` simple:
- `last_heartbeat_started_at`
- `last_reviewed_change_at`
- `last_heartbeat_result`
- `last_actions`
## Behavior Standard
Heartbeat exists to keep the memory system tidy and trustworthy.
If no rule is clearly violated, do nothing.
+22
View File
@@ -0,0 +1,22 @@
# Heartbeat State Template
Use this file as the baseline for `~/self-improving/heartbeat-state.md`.
It stores only lightweight run markers and maintenance notes.
```markdown
# Self-Improving Heartbeat State
last_heartbeat_started_at: never
last_reviewed_change_at: never
last_heartbeat_result: never
## Last actions
- none yet
```
## Rules
- update `last_heartbeat_started_at` at the beginning of every heartbeat
- update `last_reviewed_change_at` only after a clean review of changed files
- keep `last_actions` short and factual
- never turn this file into another memory log
+106
View File
@@ -0,0 +1,106 @@
# Learning Mechanics
## What Triggers Learning
| Trigger | Confidence | Action |
|---------|------------|--------|
| "No, do X instead" | High | Log correction immediately |
| "I told you before..." | High | Flag as repeated, bump priority |
| "Always/Never do X" | Confirmed | Promote to preference |
| User edits your output | Medium | Log as tentative pattern |
| Same correction 3x | Confirmed | Ask to make permanent |
| "For this project..." | Scoped | Write to project namespace |
## What Does NOT Trigger Learning
- Silence (not confirmation)
- Single instance of anything
- Hypothetical discussions
- Third-party preferences ("John likes...")
- Group chat patterns (unless user confirms)
- Implied preferences (never infer)
## Correction Classification
### By Type
| Type | Example | Namespace |
|------|---------|-----------|
| Format | "Use bullets not prose" | global |
| Technical | "SQLite not Postgres" | domain/code |
| Communication | "Shorter messages" | global |
| Project-specific | "This repo uses Tailwind" | projects/{name} |
| Person-specific | "Marcus wants BLUF" | domains/comms |
### By Scope
```
Global: applies everywhere
└── Domain: applies to category (code, writing, comms)
└── Project: applies to specific context
└── Temporary: applies to this session only
```
## Confirmation Flow
After 3 similar corrections:
```
Agent: "I've noticed you prefer X over Y (corrected 3 times).
Should I always do this?
- Yes, always
- Only in [context]
- No, case by case"
User: "Yes, always"
Agent: → Moves to Confirmed Preferences
→ Removes from correction counter
→ Cites source on future use
```
## Pattern Evolution
### Stages
1. **Tentative** — Single correction, watch for repetition
2. **Emerging** — 2 corrections, likely pattern
3. **Pending** — 3 corrections, ask for confirmation
4. **Confirmed** — User approved, permanent unless reversed
5. **Archived** — Unused 90+ days, preserved but inactive
### Reversal
User can always reverse:
```
User: "Actually, I changed my mind about X"
Agent:
1. Archive old pattern (keep history)
2. Log reversal with timestamp
3. Add new preference as tentative
4. "Got it. I'll do Y now. (Previous: X, archived)"
```
## Anti-Patterns
### Never Learn
- What makes user comply faster (manipulation)
- Emotional triggers or vulnerabilities
- Patterns from other users (even if shared device)
- Anything that feels "creepy" to surface
### Avoid
- Over-generalizing from single instance
- Learning style over substance
- Assuming preference stability
- Ignoring context shifts
## Quality Signals
### Good Learning
- User explicitly states preference
- Pattern consistent across contexts
- Correction improves outcomes
- User confirms when asked
### Bad Learning
- Inferred from silence
- Contradicts recent behavior
- Only works in narrow context
- User never confirmed
+75
View File
@@ -0,0 +1,75 @@
# Memory Template
Copy this structure to `~/self-improving/memory.md` on first use.
```markdown
# Self-Improving Memory
## Confirmed Preferences
<!-- Patterns confirmed by user, never decay -->
## Active Patterns
<!-- Patterns observed 3+ times, subject to decay -->
## Recent (last 7 days)
<!-- New corrections pending confirmation -->
```
## Initial Directory Structure
Create on first activation:
```bash
mkdir -p ~/self-improving/{projects,domains,archive}
touch ~/self-improving/{memory.md,index.md,corrections.md,heartbeat-state.md}
```
## Index Template
For `~/self-improving/index.md`:
```markdown
# Memory Index
## HOT
- memory.md: 0 lines
## WARM
- (no namespaces yet)
## COLD
- (no archives yet)
Last compaction: never
```
## Corrections Log Template
For `~/self-improving/corrections.md`:
```markdown
# Corrections Log
<!-- Format:
## YYYY-MM-DD
- [HH:MM] Changed X → Y
Type: format|technical|communication|project
Context: where correction happened
Confirmed: pending (N/3) | yes | no
-->
```
## Heartbeat State Template
For `~/self-improving/heartbeat-state.md`:
```markdown
# Self-Improving Heartbeat State
last_heartbeat_started_at: never
last_reviewed_change_at: never
last_heartbeat_result: never
## Last actions
- none yet
```
+30
View File
@@ -0,0 +1,30 @@
# HOT Memory — Template
> This file is created in `~/self-improving/memory.md` when you first use the skill.
> Keep it ≤100 lines. Most-used patterns live here.
## Example Entries
```markdown
## Preferences
- Code style: Prefer explicit over implicit
- Communication: Direct, no fluff
- Time zone: Europe/Madrid
## Patterns (promoted from corrections)
- Always use TypeScript strict mode
- Prefer pnpm over npm
- Format: ISO 8601 for dates
## Project defaults
- Tests: Jest with coverage >80%
- Commits: Conventional commits format
```
## Usage
The agent will:
1. Load this file on every session
2. Add entries when patterns are used 3x in 7 days
3. Demote unused entries to WARM after 30 days
4. Never exceed 100 lines (compacts automatically)
@@ -0,0 +1,5 @@
## Self-Improving Check
- Read `./skills/self-improving/heartbeat-rules.md`
- Use `~/self-improving/heartbeat-state.md` for last-run markers and action notes
- If no file inside `~/self-improving/` changed since the last reviewed change, return `HEARTBEAT_OK`
+144
View File
@@ -0,0 +1,144 @@
# Memory Operations
## User Commands
| Command | Action |
|---------|--------|
| "What do you know about X?" | Search all tiers, return matches with sources |
| "Show my memory" | Display memory.md contents |
| "Show [project] patterns" | Load and display specific namespace |
| "Forget X" | Remove from all tiers, confirm deletion |
| "Forget everything" | Full wipe with export option |
| "What changed recently?" | Show last 20 corrections |
| "Export memory" | Generate downloadable archive |
| "Memory status" | Show tier sizes, last compaction, health |
## Automatic Operations
### On Session Start
1. Load memory.md (HOT tier)
2. Check index.md for context hints
3. If project detected → preload relevant namespace
### On Correction Received
```
1. Parse correction type (preference, pattern, override)
2. Check if duplicate (exists in any tier)
3. If new:
- Add to corrections.md with timestamp
- Increment correction counter
4. If duplicate:
- Bump counter, update timestamp
- If counter >= 3: ask to confirm as rule
5. Determine namespace (global, domain, project)
6. Write to appropriate file
7. Update index.md line counts
```
### On Pattern Match
When applying learned pattern:
```
1. Find pattern source (file:line)
2. Apply pattern
3. Cite source: "Using X (from memory.md:15)"
4. Log usage for decay tracking
```
### Weekly Maintenance (Cron)
```
1. Scan all files for decay candidates
2. Move unused >30 days to WARM
3. Archive unused >90 days to COLD
4. Run compaction if any file >limit
5. Update index.md
6. Generate weekly digest (optional)
```
## File Formats
### memory.md (HOT)
```markdown
# Self-Improving Memory
## Confirmed Preferences
- format: bullet points over prose (confirmed 2026-01)
- tone: direct, no hedging (confirmed 2026-01)
## Active Patterns
- "looks good" = approval to proceed (used 15x)
- single emoji = acknowledged (used 8x)
## Recent (last 7 days)
- prefer SQLite for MVPs (corrected 02-14)
```
### corrections.md
```markdown
# Corrections Log
## 2026-02-15
- [14:32] Changed verbose explanation → bullet summary
Type: communication
Context: Telegram response
Confirmed: pending (1/3)
## 2026-02-14
- [09:15] Use SQLite not Postgres for MVP
Type: technical
Context: database discussion
Confirmed: yes (said "always")
```
### projects/{name}.md
```markdown
# Project: my-app
Inherits: global, domains/code
## Patterns
- Use Tailwind (project standard)
- No Prettier (eslint only)
- Deploy via GitLab CI
## Overrides
- semicolons: yes (overrides global no-semi)
## History
- Created: 2026-01-15
- Last active: 2026-02-15
- Corrections: 12
```
## Edge Case Handling
### Contradiction Detected
```
Pattern A: "Use tabs" (global, confirmed)
Pattern B: "Use spaces" (project, corrected today)
Resolution:
1. Project overrides global → use spaces for this project
2. Log conflict in corrections.md
3. Ask: "Should spaces apply only to this project or everywhere?"
```
### User Changes Mind
```
Old: "Always use formal tone"
New: "Actually, casual is fine"
Action:
1. Archive old pattern with timestamp
2. Add new pattern as tentative
3. Keep archived for reference ("You previously preferred formal")
```
### Context Ambiguity
```
User says: "Remember I like X"
But which namespace?
1. Check current context (project? domain?)
2. If unclear, ask: "Should this apply globally or just here?"
3. Default to most specific active context
```
+31
View File
@@ -0,0 +1,31 @@
# Self-Reflections Log
Track self-reflections from completed work. Each entry captures what the agent learned from evaluating its own output.
## Format
```
## [Date] — [Task Type]
**What I did:** Brief description
**Outcome:** What happened (success, partial, failed)
**Reflection:** What I noticed about my work
**Lesson:** What to do differently next time
**Status:** ⏳ candidate | ✅ promoted | 📦 archived
```
## Example Entry
```
## 2026-02-25 — Flutter UI Build
**What I did:** Built a settings screen with toggle switches
**Outcome:** User said "spacing looks off"
**Reflection:** I focused on functionality, didn't visually check the result
**Lesson:** Always take a screenshot and evaluate visual balance before showing user
**Status:** ✅ promoted to domains/flutter.md
```
## Entries
(New entries appear here)
+125
View File
@@ -0,0 +1,125 @@
# Scaling Patterns
## Volume Thresholds
| Scale | Entries | Strategy |
|-------|---------|----------|
| Small | <100 | Single memory.md, no namespacing |
| Medium | 100-500 | Split into domains/, basic indexing |
| Large | 500-2000 | Full namespace hierarchy, aggressive compaction |
| Massive | >2000 | Archive yearly, summary-only HOT tier |
## When to Split
Create new namespace file when:
- Single file exceeds 200 lines
- Topic has 10+ distinct corrections
- User explicitly separates contexts ("for work...", "in this project...")
## Compaction Rules
### Merge Similar Corrections
```
BEFORE (3 entries):
- [02-01] Use tabs not spaces
- [02-03] Indent with tabs
- [02-05] Tab indentation please
AFTER (1 entry):
- Indentation: tabs (confirmed 3x, 02-01 to 02-05)
```
### Summarize Verbose Patterns
```
BEFORE:
- When writing emails to Marcus, use bullet points, keep under 5 items,
no jargon, bottom-line first, he prefers morning sends
AFTER:
- Marcus emails: bullets ≤5, no jargon, BLUF, AM preferred
```
### Archive with Context
When moving to COLD:
```
## Archived 2026-02
### Project: old-app (inactive since 2025-08)
- Used Vue 2 patterns
- Preferred Vuex over Pinia
- CI on Jenkins (deprecated)
Reason: Project completed, patterns unlikely to apply
```
## Index Maintenance
`index.md` tracks all namespaces:
```markdown
# Memory Index
## HOT (always loaded)
- memory.md: 87 lines, updated 2026-02-15
## WARM (load on match)
- projects/current-app.md: 45 lines
- projects/side-project.md: 23 lines
- domains/code.md: 112 lines
- domains/writing.md: 34 lines
## COLD (archive)
- archive/2025.md: 234 lines
- archive/2024.md: 189 lines
Last compaction: 2026-02-01
Next scheduled: 2026-03-01
```
## Multi-Project Patterns
### Inheritance Chain
```
global (memory.md)
└── domain (domains/code.md)
└── project (projects/app.md)
```
### Override Syntax
In project file:
```markdown
## Overrides
- indentation: spaces (overrides global tabs)
- Reason: Project eslint config requires spaces
```
### Conflict Detection
When loading, check for conflicts:
1. Build inheritance chain
2. Detect contradictions
3. Most specific wins
4. Log conflict for later review
## User Type Adaptations
| User Type | Memory Strategy |
|-----------|-----------------|
| Power user | Aggressive learning, minimal confirmation |
| Casual | Conservative learning, frequent confirmation |
| Team shared | Per-user namespaces, shared project space |
| Privacy-focused | Local-only, explicit consent per category |
## Recovery Patterns
### Context Lost
If agent loses context mid-session:
1. Re-read memory.md
2. Check index.md for relevant namespaces
3. Load active project namespace
4. Continue with restored patterns
### Corruption Recovery
If memory file corrupted:
1. Check archive/ for recent backup
2. Rebuild from corrections.md
3. Ask user to re-confirm critical preferences
4. Log incident for debugging
+196
View File
@@ -0,0 +1,196 @@
# Setup — Self-Improving Agent
## First-Time Setup
### 1. Create Memory Structure
```bash
mkdir -p ~/self-improving/{projects,domains,archive}
```
### 2. Initialize Core Files
Create `~/self-improving/memory.md` using `memory-template.md`:
```markdown
Copy the structure from `memory-template.md` into `~/self-improving/memory.md`.
```
Memory file baseline:
```markdown
# Memory (HOT Tier)
## Preferences
## Patterns
## Rules
```
Create `~/self-improving/corrections.md`:
```markdown
# Corrections Log
| Date | What I Got Wrong | Correct Answer | Status |
|------|-----------------|----------------|--------|
```
Create `~/self-improving/index.md`:
```markdown
# Memory Index
| File | Lines | Last Updated |
|------|-------|--------------|
| memory.md | 0 | — |
| corrections.md | 0 | — |
```
Create `~/self-improving/heartbeat-state.md`:
```markdown
# Self-Improving Heartbeat State
last_heartbeat_started_at: never
last_reviewed_change_at: never
last_heartbeat_result: never
## Last actions
- none yet
```
### 3. Choose Operating Mode
Add to your AGENTS.md or workspace config:
```markdown
## Self-Improving Mode
Current mode: Passive
Available modes:
- Passive: Only learn from explicit corrections
- Active: Suggest patterns after 3x repetition
- Strict: Require confirmation for every entry
```
### 4. Add SOUL.md Steering
Add this section to your `SOUL.md`:
```markdown
**Self-Improving**
Compounding execution quality is part of the job.
Before non-trivial work, load `~/self-improving/memory.md` and only the smallest relevant domain or project files.
After corrections, failed attempts, or reusable lessons, write one concise entry to the correct self-improving file immediately.
Prefer learned rules when relevant, but keep self-inferred rules revisable.
Do not skip retrieval just because the task feels familiar.
```
### 5. Add the Proactivity Companion as Part of Setup
At the end of setup, briefly tell the user that you are going to add characteristics so the agent is more proactive:
- noticing missing next steps
- verifying outcomes instead of assuming they landed
- recovering context better after long or interrupted threads
- keeping the right level of initiative
Then say that, for this, you are going to install the `Proactivity` skill.
Only install it after the user explicitly agrees.
If the user agrees:
1. Run `clawhub install proactivity`
2. Read the installed `proactivity` skill
3. Continue into its setup flow immediately so the skill is active for this workspace
If the user says no, skip it cleanly and continue with self-improving only.
### 6. Refine AGENTS.md Memory Section (Non-Destructive)
Update `AGENTS.md` by complementing the existing `## Memory` section. Do not replace the whole section and do not remove existing lines.
If your `## Memory` block differs from the default template, insert the same additions in equivalent places so existing information is preserved.
Add this line in the continuity list (next to Daily notes and Long-term):
```markdown
- **Self-improving:** `~/self-improving/` (via `self-improving` skill) — execution-improvement memory (preferences, workflows, style patterns, what improved/worsened outcomes)
```
Right after the sentence "Capture what matters...", add:
```markdown
Use `memory/YYYY-MM-DD.md` and `MEMORY.md` for factual continuity (events, context, decisions).
Use `~/self-improving/` for compounding execution quality across tasks.
For compounding quality, read `~/self-improving/memory.md` before non-trivial work, then load only the smallest relevant domain or project files.
If in doubt, store factual history in `memory/YYYY-MM-DD.md` / `MEMORY.md`, and store reusable performance lessons in `~/self-improving/` (tentative until human validation).
```
Before the "Write It Down" subsection, add:
```markdown
Before any non-trivial task:
- Read `~/self-improving/memory.md`
- List available files first:
```bash
for d in ~/self-improving/domains ~/self-improving/projects; do
[ -d "$d" ] && find "$d" -maxdepth 1 -type f -name "*.md"
done | sort
```
- Read up to 3 matching files from `~/self-improving/domains/`
- If a project is clearly active, also read `~/self-improving/projects/<project>.md`
- Do not read unrelated domains "just in case"
If inferring a new rule, keep it tentative until human validation.
```
Inside the "Write It Down" bullets, refine the behavior (non-destructive):
- Keep existing intent, but route execution-improvement content to `~/self-improving/`.
- If the exact bullets exist, replace only these lines; if wording differs, apply equivalent edits without removing unrelated guidance.
Use this target wording:
```markdown
- When someone says "remember this" → if it's factual context/event, update `memory/YYYY-MM-DD.md`; if it's a correction, preference, workflow/style choice, or performance lesson, log it in `~/self-improving/`
- Explicit user correction → append to `~/self-improving/corrections.md` immediately
- Reusable global rule or preference → append to `~/self-improving/memory.md`
- Domain-specific lesson → append to `~/self-improving/domains/<domain>.md`
- Project-only override → append to `~/self-improving/projects/<project>.md`
- Keep entries short, concrete, and one lesson per bullet; if scope is ambiguous, default to domain rather than global
- After a correction or strong reusable lesson, write it before the final response
```
## Verification
Run "memory stats" to confirm setup:
```
📊 Self-Improving Memory
🔥 HOT (always loaded):
memory.md: 0 entries
🌡️ WARM (load on demand):
projects/: 0 files
domains/: 0 files
❄️ COLD (archived):
archive/: 0 files
⚙️ Mode: Passive
```
### 7. Add HEARTBEAT.md Steering
Add this section to your `HEARTBEAT.md`:
```markdown
## Self-Improving Check
- Read `./skills/self-improving/heartbeat-rules.md`
- Use `~/self-improving/heartbeat-state.md` for last-run markers and action notes
- If no file inside `~/self-improving/` changed since the last reviewed change, return `HEARTBEAT_OK`
```
Keep this in the same default setup flow as the AGENTS and SOUL additions so recurring maintenance is installed consistently.
If your installed skills path differs, keep the same three lines but point the first line at the installed copy of `heartbeat-rules.md`.
@@ -0,0 +1,7 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "stock-monitor-skill",
"installedVersion": "0.1.0",
"installedAt": 1774625587618
}
+23
View File
@@ -0,0 +1,23 @@
# Stock Monitor Pro
全功能智能股票监控预警系统
## 功能
- 成本百分比预警
- 日内涨跌幅预警
- 成交量异动监控
- 均线金叉死叉
- RSI超买超卖
- 跳空缺口检测
- 动态止盈
## 快速开始
```bash
cd scripts
cp config.example.py config.py
# 编辑 config.py 填入你的持仓
./control.sh start
```
## 许可证
MIT
+193
View File
@@ -0,0 +1,193 @@
---
name: stock-monitor
description: 全功能智能股票监控预警系统。支持成本百分比、均线金叉死叉、RSI超买超卖、成交量异动、跳空缺口、动态止盈等7大预警规则。符合中国投资者习惯(红涨绿跌)。
---
# Stock Monitor Pro - 全功能智能投顾系统
## 🎯 核心特色
### 1. 七大预警规则 (全都要!)
| 规则 | 触发条件 | 权重 |
|------|----------|------|
| **成本百分比** | 盈利+15% / 亏损-12% | ⭐⭐⭐ |
| **日内涨跌幅** | 个股±4% / ETF±2% / 黄金±2.5% | ⭐⭐ |
| **成交量异动** | 放量>2倍均量 / 缩量<0.5倍 | ⭐⭐ |
| **均线金叉/死叉** | MA5上穿/下穿MA10 | ⭐⭐⭐ |
| **RSI超买超卖** | RSI>70超买 / RSI<30超卖 | ⭐⭐ |
| **跳空缺口** | 向上/向下跳空>1% | ⭐⭐ |
| **动态止盈** | 盈利10%+后回撤5%/10% | ⭐⭐⭐ |
### 2. 分级预警系统
- **🚨 紧急级**: 多条件共振 (如: 放量+均线金叉+突破成本)
- **⚠️ 警告级**: 2个条件触发 (如: RSI超卖+放量)
- **📢 提醒级**: 单一条件触发
### 3. 中国习惯
- **🔴 红色** = 上涨 / 盈利
- **🟢 绿色** = 下跌 / 亏损
## 📋 监控配置
### 完整预警规则示例
```python
{
"code": "600362",
"name": "江西铜业",
"type": "individual", # 个股
"market": "sh",
"cost": 57.00, # 持仓成本
"alerts": {
# 1. 成本百分比
"cost_pct_above": 15.0, # 盈利15%提醒 (¥65.55)
"cost_pct_below": -12.0, # 亏损12%提醒 (¥50.16)
# 2. 日内涨跌幅 (个股±4%)
"change_pct_above": 4.0,
"change_pct_below": -4.0,
# 3. 成交量异动
"volume_surge": 2.0, # 放量>2倍5日均量
# 4-7. 技术指标 (默认开启)
"ma_monitor": True, # 均线金叉死叉
"rsi_monitor": True, # RSI超买超卖
"gap_monitor": True, # 跳空缺口
"trailing_stop": True # 动态止盈
}
}
```
### 标的类型差异化
| 类型 | 日内异动阈值 | 成交量阈值 | 适用标的 |
|------|-------------|-----------|----------|
| individual (个股) | ±4% | 2倍 | 江西铜业、中国平安 |
| etf (ETF) | ±2% | 1.8倍 | 恒生医疗、创50等 |
| gold (黄金) | ±2.5% | 无 | 伦敦金 |
## 🚀 运行方式
### 后台常驻进程
```bash
cd ~/workspace/skills/stock-monitor/scripts
./control.sh start # 启动
./control.sh status # 查看状态
./control.sh log # 查看日志
./control.sh stop # 停止
```
## ⚡ 智能频率 (北京时间)
| 时间段 | 频率 | 监控标的 |
|--------|------|----------|
| 交易时间 9:30-15:00 | 每5分钟 | 全部+技术指标 |
| 午休 11:30-13:00 | 每10分钟 | 全部 |
| 收盘后 15:00-24:00 | 每30分钟 | 全部 (日线数据) |
| 凌晨 0:00-9:30 | 每1小时 | 仅伦敦金 |
| 周末 | 每1小时 | 仅伦敦金 |
## 🔔 预警消息示例
### 多条件共振 (紧急级)
```
🚨【紧急】🔴 江西铜业 (600362)
━━━━━━━━━━━━━━━━━━━━
💰 当前价格: ¥65.50 (+15.0%)
📊 持仓成本: ¥57.00 | 盈亏: 🔴+14.9%
🎯 触发预警 (3项):
• 🎯 盈利 15% (目标价 ¥65.55)
• 🌟 均线金叉 (MA5¥63.2上穿MA10¥62.8)
• 📊 放量 2.5倍 (5日均量)
📊 江西铜业 深度分析
💰 价格异动:
• 当前: 65.5 (+15.0%)
• MA趋势: MA5>MA10>MA20 [多头排列]
• RSI: 68 [接近超买]
💡 Kimi建议:
🚀 多条件共振,趋势强劲,可考虑继续持有或分批减仓。
```
### RSI超卖 (警告级)
```
⚠️【警告】🟢 恒生医疗 (159892)
━━━━━━━━━━━━━━━━━━━━
💰 当前价格: ¥0.72 (-10.0%)
📊 持仓成本: ¥0.80 | 盈亏: 🟢-10.0%
🎯 触发预警 (2项):
• 📉 日内大跌 -10.0%
• ❄️ RSI超卖 (28.5),可能反弹
💡 Kimi建议:
🔍 短期超跌严重,RSI进入超卖区,关注反弹机会但勿急于抄底。
```
### 动态止盈提醒
```
📢【提醒】🔴 中国平安 (601318)
━━━━━━━━━━━━━━━━━━━━
💰 当前价格: ¥70.50 (+6.8%)
📊 持仓成本: ¥66.00 | 盈亏: 🔴+6.8%
🎯 触发预警:
• 📉 利润回撤 5.2%,建议减仓保护利润
(最高盈利12%已回撤)
```
## 🛠️ 文件结构
```
stock-monitor/
├── SKILL.md # 本文档
├── RULE_REVIEW_REPORT.md # 规则审核报告
└── scripts/
├── monitor.py # 核心监控 (7大规则)
├── monitor_daemon.py # 后台常驻进程
├── analyser.py # 智能分析引擎
└── control.sh # 一键控制脚本
```
## ⚙️ 自定义配置
### 修改成本价
```python
"cost": 55.50, # 改成你的实际成本
```
### 调整预警阈值
```python
"cost_pct_above": 20.0, # 盈利20%提醒
"cost_pct_below": -15.0, # 亏损15%提醒
"change_pct_above": 5.0, # 日内异动±5%
"volume_surge": 3.0, # 放量3倍提醒
```
### 开关技术指标
```python
"ma_monitor": False, # 关闭均线
"rsi_monitor": True, # 开启RSI
"gap_monitor": True, # 开启跳空
```
## 📝 更新日志
- **v3.0 全功能版**: 完成7大预警规则 (成本/涨跌幅/成交量/均线/RSI/跳空/动态止盈)
- **v2.4 成本百分比版**: 支持基于持仓成本的百分比预警
- **v2.3 中国版**: 红涨绿跌颜色习惯
- **v2.2 常驻进程版**: 后台常驻进程支持
- **v2.1 智能频率版**: 智能频率控制
- **v2.0 Pro版**: 新闻舆情分析
## ⚠️ 使用提示
1. **技术指标有滞后性**: 均线、MACD等都是滞后指标,用于确认趋势而非预测
2. **避免过度交易**: 预警只是参考,不要每个信号都操作
3. **多条件共振更可靠**: 单一指标容易假信号,多条件共振更准确
4. **动态止盈要灵活**: 回撤5%减仓、10%清仓是建议,根据市场灵活调整
---
**核心原则**:
> 预警系统目标是"不错过大机会,不犯大错误",不是"抓住每一个波动"。
+6
View File
@@ -0,0 +1,6 @@
{
"ownerId": "kn70aj13hr3z4fpmfk1y2jmpz181gn2z",
"slug": "stock-monitor-skill",
"version": "0.1.0",
"publishedAt": 1771579954718
}
@@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""
Stock Monitor Pro - 智能分析引擎
集成:新闻、资金流向、龙虎榜、宏观关联分析
"""
import requests
import json
import re
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class StockAnalyser:
"""股票智能分析器 - 结合多维度数据给出建议"""
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
# ========== 1. 新闻舆情 ==========
def fetch_eastmoney_news(self, symbol: str, name: str, limit: int = 5) -> List[Dict]:
"""获取东方财富个股新闻"""
url = f"https://searchapi.eastmoney.com/api/suggest/get"
params = {
"input": name,
"type": 14,
"count": limit
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
news_list = []
for item in data.get("QuotationCodeTable", {}).get("Data", []):
news_list.append({
"title": item.get("Title", ""),
"url": item.get("Url", ""),
"time": item.get("ShowTime", "")
})
return news_list
except Exception as e:
return []
def fetch_sina_news(self, symbol: str, name: str) -> List[Dict]:
"""获取新浪财经个股新闻"""
# 新浪新闻搜索接口
url = f"https://search.sina.com.cn/?q={name}&c=news&sort=time"
try:
resp = self.session.get(url, timeout=10)
# 这里可以做更精细的HTML解析
# 简化返回示例
return [{"title": f"新浪财经-{name}相关新闻", "source": "新浪"}]
except:
return []
def analyze_sentiment(self, news_list: List[Dict]) -> Dict:
"""简单情感分析"""
positive_words = ['利好', '增长', '突破', '买入', '增持', '涨停', '超预期', '业绩大增']
negative_words = ['利空', '减持', '下跌', '卖出', '亏损', '暴雷', '跌停', '不及预期']
sentiment = {"positive": 0, "negative": 0, "neutral": 0, "summary": []}
for news in news_list:
title = news.get("title", "")
p_count = sum(1 for w in positive_words if w in title)
n_count = sum(1 for w in negative_words if w in title)
if p_count > n_count:
sentiment["positive"] += 1
elif n_count > p_count:
sentiment["negative"] += 1
else:
sentiment["neutral"] += 1
# 生成情感摘要
if sentiment["positive"] > sentiment["negative"]:
sentiment["overall"] = "偏多"
elif sentiment["negative"] > sentiment["positive"]:
sentiment["overall"] = "偏空"
else:
sentiment["overall"] = "中性"
return sentiment
# ========== 2. 资金流向 ==========
def fetch_fund_flow(self, symbol: str, market: str = "sz") -> Dict:
"""获取个股资金流向 (新浪财经)"""
# 新浪资金流向接口
code = f"{market}{symbol}"
url = f"https://quotes.sina.cn/cn/api/quotes.php?symbol={code}&source=sina"
try:
resp = self.session.get(url, timeout=10)
# 解析返回数据
return {
"main_inflow": "数据获取中...",
"retail_inflow": "数据获取中...",
"net_inflow": "数据获取中..."
}
except:
return {"error": "获取失败"}
def fetch_northbound_flow(self) -> Dict:
"""获取北向资金 (沪深股通) 流向"""
url = "https://push2.eastmoney.com/api/qt/stock/get"
params = {"secid": "1.000001", "fields": "f170"} # 简化示例
try:
resp = self.session.get(url, params=params, timeout=10)
return {"northbound": "北向资金数据获取中..."}
except:
return {}
# ========== 3. 龙虎榜 ==========
def fetch_dragon_tiger(self, date: str = None) -> List[Dict]:
"""获取龙虎榜数据"""
if not date:
date = datetime.now().strftime("%Y%m%d")
url = f"http://datacenter-web.eastmoney.com/api/data/v1/get"
params = {
"sortColumns": "NET_BUY_AMT",
"sortTypes": "-1",
"pageSize": "50",
"pageNumber": "1",
"reportName": "RPT_DMSK_TS",
"columns": "ALL",
"filter": f"(TRADE_DATE='{date}')"
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
return data.get("result", {}).get("data", [])
except:
return []
# ========== 4. 宏观关联分析 ==========
def analyze_gold_correlation(self, gold_price: float, stocks: List[Dict]) -> str:
"""分析金价与持仓股票的关联"""
# 江西铜业等有色股与金价正相关
correlation_map = {
"600362": "强正相关", # 江西铜业
"601318": "弱相关", # 中国平安
"513180": "弱负相关", # 恒生科技
"159892": "弱相关", # 恒生医疗
}
analysis = []
for stock in stocks:
code = stock.get("code")
corr = correlation_map.get(code, "未知")
if corr in ["强正相关", "中等正相关"]:
analysis.append(f"📈 {stock['name']}: 与金价{corr},金价上涨可能带动该股")
return "\n".join(analysis) if analysis else "暂无强关联标的"
# ========== 5. 综合分析 ==========
def generate_insight(self, stock: Dict, price_data: Dict, alerts: List) -> str:
"""生成综合分析报告"""
code = stock['code']
name = stock['name']
# 1. 获取新闻
news_list = self.fetch_eastmoney_news(code, name)
sentiment = self.analyze_sentiment(news_list)
# 2. 资金流向
fund_flow = self.fetch_fund_flow(code, stock.get('market', 'sz'))
# 3. 构建报告
report = f"""📊 <b>{name} ({code}) 深度分析</b>
💰 <b>价格异动:</b>
• 当前: {price_data.get('price', 'N/A')} ({price_data.get('change_pct', 0):+.2f}%)
• 触发: {', '.join([a[1] for a in alerts])}
📰 <b>舆情分析 ({sentiment.get('overall', '未知')}):</b>
• 最近新闻: {len(news_list)}
• 正面: {sentiment.get('positive', 0)} | 负面: {sentiment.get('negative', 0)}
"""
# 添加最新新闻标题
if news_list:
report += "\n<b>最新动态:</b>\n"
for n in news_list[:2]:
report += f"{n.get('title', '无标题')[:30]}...\n"
# 4. 给出建议
suggestion = self._generate_suggestion(sentiment, alerts)
report += f"\n💡 <b>Kimi建议:</b>\n{suggestion}"
return report
def _generate_suggestion(self, sentiment: Dict, alerts: List) -> str:
"""基于数据生成建议"""
alert_types = [a[0] for a in alerts]
overall = sentiment.get("overall", "中性")
# 价格下跌 + 舆情偏空 = 谨慎
if "below" in alert_types and overall == "偏空":
return "⚠️ 价格跌破支撑位,且舆情偏空,建议观察等待,不急于抄底。"
# 价格下跌 + 舆情偏多 = 可能是机会
if "below" in alert_types and overall == "偏多":
return "🔍 价格下跌但舆情偏多,可能是情绪错杀,关注是否有反弹机会。"
# 价格突破 + 舆情偏多 = 确认趋势
if "above" in alert_types and overall == "偏多":
return "🚀 价格突破且舆情配合,趋势可能延续,可考虑顺势而为。"
# 大涨
if "pct_up" in alert_types:
return "📈 短期涨幅较大,注意获利了结风险。"
# 大跌
if "pct_down" in alert_types:
return "📉 短期跌幅较大,关注是否超跌反弹,但勿急于抄底。"
return "⏳ 建议保持观察,等待更明确信号。"
# ========== 测试 ==========
if __name__ == '__main__':
analyser = StockAnalyser()
# 测试新闻抓取
print("=== 新闻测试 ===")
news = analyser.fetch_eastmoney_news("600362", "江西铜业")
print(f"获取到 {len(news)} 条新闻")
for n in news[:3]:
print(f" - {n.get('title', 'N/A')[:40]}...")
# 测试情感分析
print("\n=== 情感分析测试 ===")
sentiment = analyser.analyze_sentiment(news)
print(f"整体情绪: {sentiment.get('overall')}")
print(f"正面: {sentiment.get('positive')}, 负面: {sentiment.get('negative')}")
# 测试金价关联
print("\n=== 宏观关联测试 ===")
stocks = [{"code": "600362", "name": "江西铜业"}]
corr = analyser.analyze_gold_correlation(2743, stocks)
print(corr)
@@ -0,0 +1,64 @@
#!/bin/bash
# Stock Monitor 一键启动脚本
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
LOG_DIR="$HOME/.stock_monitor"
PID_FILE="$LOG_DIR/monitor.pid"
case "$1" in
start)
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
echo "⚠️ 监控进程已在运行 (PID: $(cat $PID_FILE))"
exit 1
fi
echo "🚀 启动 Stock Monitor 后台进程..."
mkdir -p "$LOG_DIR"
nohup python3 "$SCRIPT_DIR/monitor_daemon.py" > "$LOG_DIR/monitor.log" 2>&1 &
echo $! > "$PID_FILE"
echo "✅ 已启动 (PID: $!)"
echo "📋 日志: $LOG_DIR/monitor.log"
;;
stop)
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if kill -0 "$PID" 2>/dev/null; then
echo "🛑 停止监控进程 (PID: $PID)..."
kill "$PID"
rm "$PID_FILE"
echo "✅ 已停止"
else
echo "⚠️ 进程不存在"
rm "$PID_FILE"
fi
else
echo "⚠️ 没有运行中的进程"
fi
;;
status)
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
echo "✅ 监控运行中 (PID: $(cat $PID_FILE))"
echo "📋 最近日志:"
tail -5 "$LOG_DIR/monitor.log" 2>/dev/null || echo " 暂无日志"
else
echo "⏹️ 监控未运行"
fi
;;
log)
tail -f "$LOG_DIR/monitor.log"
;;
*)
echo "Stock Monitor 控制脚本"
echo ""
echo "用法: ./control.sh [start|stop|status|log]"
echo ""
echo " start - 启动后台监控"
echo " stop - 停止监控"
echo " status - 查看状态"
echo " log - 查看实时日志"
;;
esac
@@ -0,0 +1,670 @@
#!/usr/bin/env python3
"""
自选股监控预警工具 - OpenClaw集成版
支持 A股、ETF 及 国际现货黄金 (伦敦金)
"""
import requests
import json
import time
import os
from datetime import datetime
from pathlib import Path
# ============ 配置区 ============
# 监控列表 - 长期挂机通用配置
# 注意: 伦敦金使用新浪hf_XAU接口,价格为 人民币/克 (约4800元/克 = $2740/盎司)
#
# 预警规则设计原则 (适合长期挂机):
# 1. 成本百分比预警: 基于持仓成本设置 ±10%/±15% 预警,比固定价格更合理
# 2. 单日涨跌幅预警:
# - 个股 ±3%~5% (波动大)
# - ETF ±1.5%~2.5% (波动小)
# - 黄金 ±2%~3% (24H特殊)
# 3. 防骚扰: 同类预警30分钟内只发一次
# 标的类型定义
STOCK_TYPE = {
"INDIVIDUAL": "individual", # 个股
"ETF": "etf", # ETF
"GOLD": "gold" # 黄金/贵金属
}
WATCHLIST = [
# ===== 个股: 波动较大,设置较宽的涨跌预警 =====
{
"code": "600362",
"name": "江西铜业",
"market": "sh",
"type": "individual",
"cost": 57.00,
"alerts": {
"cost_pct_above": 15.0, # 盈利15%
"cost_pct_below": -12.0, # 止损12%
"change_pct_above": 4.0, # 日内异动 ±4%
"change_pct_below": -4.0,
"volume_surge": 2.0 # 成交量是5日均量2倍
}
},
{
"code": "601318",
"name": "中国平安",
"market": "sh",
"type": "individual",
"cost": 66.00,
"alerts": {
"cost_pct_above": 12.0,
"cost_pct_below": -10.0,
"change_pct_above": 3.5, # 日内异动 ±3.5%
"change_pct_below": -3.5,
"volume_surge": 2.0
}
},
# ===== ETF: 波动相对较小,设置更敏感的预警 =====
{
"code": "159892",
"name": "恒生医疗",
"market": "sz",
"type": "etf",
"cost": 0.80,
"alerts": {
"cost_pct_above": 15.0,
"cost_pct_below": -15.0,
"change_pct_above": 2.0, # ETF日内异动 ±2%
"change_pct_below": -2.0,
"volume_surge": 1.8 # ETF放量阈值更低
}
},
{
"code": "513180",
"name": "恒生科技",
"market": "sh",
"type": "etf",
"cost": 0.72,
"alerts": {
"cost_pct_above": 15.0,
"cost_pct_below": -15.0,
"change_pct_above": 2.0, # ETF日内异动 ±2%
"change_pct_below": -2.0,
"volume_surge": 1.8
}
},
{
"code": "159681",
"name": "创50ETF",
"market": "sz",
"type": "etf",
"cost": 1.50,
"alerts": {
"cost_pct_above": 12.0,
"cost_pct_below": -12.0,
"change_pct_above": 2.0, # ETF日内异动 ±2%
"change_pct_below": -2.0,
"volume_surge": 1.8
}
},
{
"code": "516020",
"name": "化工50ETF",
"market": "sh",
"type": "etf",
"cost": 0.90,
"alerts": {
"cost_pct_above": 12.0,
"cost_pct_below": -12.0,
"change_pct_above": 2.0, # ETF日内异动 ±2%
"change_pct_below": -2.0,
"volume_surge": 1.8
}
},
# ===== 伦敦金: 24H特殊标的 =====
{
"code": "XAU",
"name": "伦敦金(人民币/克)",
"market": "fx",
"type": "gold",
"cost": 4650.0,
"alerts": {
"cost_pct_above": 10.0, # 盈利10%
"cost_pct_below": -8.0, # 止损8%
"change_pct_above": 2.5, # 黄金日内异动 ±2.5%
"change_pct_below": -2.5
# 黄金不监控成交量 (外汇市场无成交量概念)
}
}
]
# 智能频率配置
SMART_SCHEDULE = {
"market_open": {"hours": [(9, 30), (11, 30), (13, 0), (15, 0)], "interval": 300}, # 交易时间: 5分钟
"after_hours": {"interval": 1800}, # 收盘后: 30分钟
"night": {"hours": [(0, 0), (8, 0)], "interval": 3600}, # 凌晨: 1小时(仅伦敦金)
}
# ============ 核心代码 ============
class StockAlert:
def __init__(self):
self.prev_data = {}
self.alert_log = []
self.session = requests.Session()
self.session.headers.update({"User-Agent": "Mozilla/5.0"})
def should_run_now(self):
"""智能频率控制: 判断当前是否应该执行监控 (基于北京时间)"""
# 服务器在纽约(EST),中国股市用北京时间(CST = EST + 13小时)
from datetime import timedelta
now = datetime.now() + timedelta(hours=13) # 转换成北京时间
hour, minute = now.hour, now.minute
time_val = hour * 100 + minute
weekday = now.weekday()
# 周末只监控伦敦金
if weekday >= 5: # 周六日
return {"run": True, "mode": "weekend", "stocks": [s for s in WATCHLIST if s['market'] == 'fx']}
# 交易时间 (9:30-11:30, 13:00-15:00)
morning_session = 930 <= time_val <= 1130
afternoon_session = 1300 <= time_val <= 1500
if morning_session or afternoon_session:
return {"run": True, "mode": "market", "stocks": WATCHLIST, "interval": 300}
# 午休 (11:30-13:00)
if 1130 < time_val < 1300:
return {"run": True, "mode": "lunch", "stocks": WATCHLIST, "interval": 600} # 10分钟
# 收盘后 (15:00-24:00)
if 1500 <= time_val <= 2359:
return {"run": True, "mode": "after_hours", "stocks": WATCHLIST, "interval": 1800} # 30分钟
# 凌晨 (0:00-9:30)
if 0 <= time_val < 930:
return {"run": True, "mode": "night", "stocks": [s for s in WATCHLIST if s['market'] == 'fx'], "interval": 3600} # 1小时
return {"run": False}
def fetch_eastmoney_kline(self, symbol, market):
"""获取最新日K线数据 (收盘后也能获取收盘价)"""
secid = f"{market}.{symbol}"
url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
params = {
'secid': secid,
'fields1': 'f1,f2,f3,f4,f5,f6',
'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61',
'klt': '101', # 日线
'fqt': '0',
'end': '20500101',
'lmt': '2' # 取最近2天,用于计算涨跌幅
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
klines = data.get('data', {}).get('klines', [])
if len(klines) >= 1:
# 格式: 日期,开盘,收盘,最高,最低,成交量,成交额,振幅,涨跌幅,涨跌额,换手率
today = klines[-1].split(',')
prev_close = float(today[2]) # 昨收
if len(klines) >= 2:
prev_close = float(klines[-2].split(',')[2]) # 前一天收盘
return {
'name': data.get('data', {}).get('name', symbol),
'price': float(today[2]), # 收盘
'prev_close': prev_close,
'volume': int(float(today[5])),
'amount': float(today[6]),
'date': today[0],
'time': '15:00:00'
}
except Exception as e:
print(f"东财K线获取失败 {symbol}: {e}")
return None
def fetch_volume_ma5(self, symbol, market):
"""获取5日平均成交量"""
secid = f"{market}.{symbol}"
url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
params = {
'secid': secid,
'fields1': 'f1,f2,f3,f4,f5,f6',
'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61',
'klt': '101',
'fqt': '0',
'end': '20500101',
'lmt': '6' # 取最近6天(今天+前5天)
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
klines = data.get('data', {}).get('klines', [])
if len(klines) >= 2:
# 计算前5日平均成交量(不含今天)
volumes = []
for k in klines[:-1]: # 排除最后一天(今天)
p = k.split(',')
volumes.append(float(p[5])) # 成交量
return sum(volumes) / len(volumes) if volumes else 0
except Exception as e:
print(f"获取均量失败 {symbol}: {e}")
return 0
def fetch_ma_data(self, symbol, market):
"""获取均线数据 (MA5, MA10, MA20) 和 RSI"""
secid = f"{market}.{symbol}"
url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
params = {
'secid': secid,
'fields1': 'f1,f2,f3,f4,f5,f6',
'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61',
'klt': '101',
'fqt': '0',
'end': '20500101',
'lmt': '30' # 取最近30天计算MA20和RSI
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
klines = data.get('data', {}).get('klines', [])
if len(klines) >= 20:
closes = []
for k in klines:
p = k.split(',')
closes.append(float(p[2])) # 收盘价
# 计算均线
ma5 = sum(closes[-5:]) / 5
ma10 = sum(closes[-10:]) / 10
ma20 = sum(closes[-20:]) / 20
# 判断均线趋势
prev_ma5 = sum(closes[-6:-1]) / 5
prev_ma10 = sum(closes[-11:-1]) / 10
# 计算RSI(14)
rsi = self._calculate_rsi(closes, 14)
return {
'MA5': ma5,
'MA10': ma10,
'MA20': ma20,
'MA5_trend': 'up' if ma5 > prev_ma5 else 'down',
'MA10_trend': 'up' if ma10 > prev_ma10 else 'down',
'golden_cross': prev_ma5 <= prev_ma10 and ma5 > ma10,
'death_cross': prev_ma5 >= prev_ma10 and ma5 < ma10,
'RSI': rsi,
'RSI_overbought': rsi > 70 if rsi else False,
'RSI_oversold': rsi < 30 if rsi else False
}
except Exception as e:
print(f"获取均线失败 {symbol}: {e}")
return None
def _calculate_rsi(self, closes, period=14):
"""计算RSI指标"""
if len(closes) < period + 1:
return None
gains = []
losses = []
for i in range(1, period + 1):
change = closes[-i] - closes[-i-1]
if change > 0:
gains.append(change)
losses.append(0)
else:
gains.append(0)
losses.append(abs(change))
avg_gain = sum(gains) / period
avg_loss = sum(losses) / period
if avg_loss == 0:
return 100
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return round(rsi, 2)
def fetch_sina_realtime(self, stocks):
"""获取实时行情 (优先实时,收盘后用日K)"""
stock_list = [s for s in stocks if s['market'] != 'fx']
fx_list = [s for s in stocks if s['market'] == 'fx']
results = {}
# 1. A股/ETF - 尝试实时接口
if stock_list:
codes = [f"{s['market']}{s['code']}" for s in stock_list]
url = f"https://hq.sinajs.cn/list={','.join(codes)}"
try:
resp = self.session.get(url, headers={'Referer': 'https://finance.sina.com.cn'}, timeout=10)
resp.encoding = 'gb18030'
for line in resp.text.strip().split(';'):
if 'hq_str_' not in line or '=' not in line: continue
key = line.split('=')[0].split('_')[-1]
if len(key) < 8: continue
data_str = line[line.index('"')+1 : line.rindex('"')]
p = data_str.split(',')
if len(p) > 30 and float(p[3]) > 0:
# 新浪数据格式: 名称,今日开盘,昨日收盘,当前价,今日最高,今日最低,竞买价,竞卖价,成交量,成交额...
# 保存昨日最高最低价用于跳空检测 (用昨日收盘近似,或用均线数据补充)
results[key[2:]] = {
'name': p[0],
'price': float(p[3]),
'prev_close': float(p[2]),
'open': float(p[1]), # 今日开盘
'high': float(p[4]), # 今日最高
'low': float(p[5]), # 今日最低
'volume': int(p[8]),
'amount': float(p[9]),
'date': p[30],
'time': p[31],
'prev_high': float(p[2]) * 1.02, # 估算昨日最高 (昨收+2%)
'prev_low': float(p[2]) * 0.98 # 估算昨日最低 (昨收-2%)
}
except Exception as e:
print(f"实时行情获取失败: {e}")
# 2. 如果实时接口返回空或0,用日K线补数据
for stock in stock_list:
code = stock['code']
if code not in results or results[code]['price'] <= 0:
kline_data = self.fetch_eastmoney_kline(code, 1 if stock['market'] == 'sh' else 0)
if kline_data:
results[code] = kline_data
print(f" {stock['name']}: 使用日K收盘价 {kline_data['price']}")
# 3. 伦敦金 (新浪hf_XAU接口,人民币/克)
if fx_list:
url = "https://hq.sinajs.cn/list=hf_XAU"
try:
resp = self.session.get(url, headers={'Referer': 'https://finance.sina.com.cn'}, timeout=10)
line = resp.text.strip()
if '"' in line:
data_str = line[line.index('"')+1 : line.rindex('"')]
p = data_str.split(',')
if len(p) >= 13:
# 新浪hf_XAU: 人民币/克 (约4800=2740美元/盎司)
price = float(p[0])
results['XAU'] = {
'name': '伦敦金',
'price': price,
'prev_close': float(p[7]),
'volume': 0, 'amount': 0,
'date': p[11] if len(p) > 11 else datetime.now().strftime('%Y-%m-%d'),
'time': p[6]
}
except Exception as e:
print(f"伦敦金获取失败: {e}")
return results
def check_alerts(self, stock_config, data):
"""检查预警条件 (支持成本百分比、单日涨跌幅、分级预警)"""
alerts = []
alert_weights = [] # 用于计算预警级别
code = stock_config['code']
cfg = stock_config.get('alerts', {})
cost = stock_config.get('cost', 0)
stock_type = stock_config.get('type', 'individual')
price, prev_close = data['price'], data['prev_close']
change_pct = (price - prev_close) / prev_close * 100 if prev_close else 0
# 1. 基于成本的百分比预警 (权重: 高)
if cost > 0:
cost_change_pct = (price - cost) / cost * 100
if 'cost_pct_above' in cfg and cost_change_pct >= cfg['cost_pct_above']:
target_price = cost * (1 + cfg['cost_pct_above']/100)
if not self._alerted_recently(code, 'cost_above'):
alerts.append(('cost_above', f"🎯 盈利 {cfg['cost_pct_above']:.0f}% (目标价 ¥{target_price:.2f})"))
alert_weights.append(3) # 高权重
if 'cost_pct_below' in cfg and cost_change_pct <= cfg['cost_pct_below']:
target_price = cost * (1 + cfg['cost_pct_below']/100)
if not self._alerted_recently(code, 'cost_below'):
alerts.append(('cost_below', f"🛑 亏损 {abs(cfg['cost_pct_below']):.0f}% (止损价 ¥{target_price:.2f})"))
alert_weights.append(3) # 高权重
# 2. 基于固定价格的预警 (权重: 中)
if 'price_above' in cfg and price >= cfg['price_above'] and not self._alerted_recently(code, 'above'):
alerts.append(('above', f"🚀 价格突破 ¥{cfg['price_above']}"))
alert_weights.append(2)
if 'price_below' in cfg and price <= cfg['price_below'] and not self._alerted_recently(code, 'below'):
alerts.append(('below', f"📉 价格跌破 ¥{cfg['price_below']}"))
alert_weights.append(2)
# 3. 单日涨跌幅预警 (权重: 根据幅度)
if 'change_pct_above' in cfg and change_pct >= cfg['change_pct_above'] and not self._alerted_recently(code, 'pct_up'):
alerts.append(('pct_up', f"📈 日内大涨 {change_pct:+.2f}%"))
# 异动越大权重越高
if change_pct >= 7:
alert_weights.append(3) # 涨停附近
elif change_pct >= 5:
alert_weights.append(2) # 大涨
else:
alert_weights.append(1) # 一般异动
if 'change_pct_below' in cfg and change_pct <= cfg['change_pct_below'] and not self._alerted_recently(code, 'pct_down'):
alerts.append(('pct_down', f"📉 日内大跌 {change_pct:+.2f}%"))
if change_pct <= -7:
alert_weights.append(3) # 跌停附近
elif change_pct <= -5:
alert_weights.append(2) # 大跌
else:
alert_weights.append(1) # 一般异动
# 4. 成交量异动检测 (仅股票和ETF)
if stock_type != 'gold' and 'volume_surge' in cfg:
current_volume = data.get('volume', 0)
if current_volume > 0:
# 尝试获取5日均量
ma5_volume = self.fetch_volume_ma5(code, 1 if stock_config['market'] == 'sh' else 0)
if ma5_volume > 0:
volume_ratio = current_volume / ma5_volume
threshold = cfg['volume_surge']
if volume_ratio >= threshold and not self._alerted_recently(code, 'volume_surge'):
alerts.append(('volume_surge', f"📊 放量 {volume_ratio:.1f}倍 (5日均量)"))
alert_weights.append(2) # 中等权重
elif volume_ratio <= 0.5 and not self._alerted_recently(code, 'volume_shrink'):
alerts.append(('volume_shrink', f"📉 缩量 {volume_ratio:.1f}倍 (5日均量)"))
alert_weights.append(1) # 低权重
# 5. 均线系统 (MA金叉死叉)
if stock_type != 'gold' and cfg.get('ma_monitor', True):
ma_data = self.fetch_ma_data(code, 1 if stock_config['market'] == 'sh' else 0)
if ma_data:
# 金叉: MA5上穿MA10 (短期转强)
if ma_data.get('golden_cross') and not self._alerted_recently(code, 'ma_golden'):
alerts.append(('ma_golden', f"🌟 均线金叉 (MA5¥{ma_data['MA5']:.2f}上穿MA10¥{ma_data['MA10']:.2f})"))
alert_weights.append(3) # 高权重
# 死叉: MA5下穿MA10 (短期转弱)
if ma_data.get('death_cross') and not self._alerted_recently(code, 'ma_death'):
alerts.append(('ma_death', f"⚠️ 均线死叉 (MA5¥{ma_data['MA5']:.2f}下穿MA10¥{ma_data['MA10']:.2f})"))
alert_weights.append(3) # 高权重
# RSI超买超卖检测
rsi = ma_data.get('RSI')
if rsi:
if ma_data.get('RSI_overbought') and not self._alerted_recently(code, 'rsi_high'):
alerts.append(('rsi_high', f"🔥 RSI超买 ({rsi}),可能回调"))
alert_weights.append(2)
elif ma_data.get('RSI_oversold') and not self._alerted_recently(code, 'rsi_low'):
alerts.append(('rsi_low', f"❄️ RSI超卖 ({rsi}),可能反弹"))
alert_weights.append(2)
# 5. 跳空缺口检测 (需要昨日数据)
if stock_type != 'gold':
prev_high = data.get('prev_high', 0)
prev_low = data.get('prev_low', 0)
current_open = data.get('open', price) # 当前价近似开盘价
# 向上跳空: 今日开盘 > 昨日最高
if prev_high > 0 and current_open > prev_high * 1.01: # 1%以上算跳空
gap_pct = (current_open - prev_high) / prev_high * 100
if not self._alerted_recently(code, 'gap_up'):
alerts.append(('gap_up', f"⬆️ 向上跳空 {gap_pct:.1f}%"))
alert_weights.append(2)
# 向下跳空: 今日开盘 < 昨日最低
elif prev_low > 0 and current_open < prev_low * 0.99:
gap_pct = (prev_low - current_open) / prev_low * 100
if not self._alerted_recently(code, 'gap_down'):
alerts.append(('gap_down', f"⬇️ 向下跳空 {gap_pct:.1f}%"))
alert_weights.append(2)
# 6. 动态止盈/移动止损 (当盈利达到一定幅度后启动)
if cost > 0:
profit_pct = (price - cost) / cost * 100
# 当盈利 >= 10% 时,启动移动止盈
if profit_pct >= 10:
# 计算回撤幅度 (从最高点回撤)
high_since_cost = data.get('high', price)
drawdown = (high_since_cost - price) / high_since_cost * 100 if high_since_cost > cost else 0
# 回撤5%提醒减仓
if drawdown >= 5 and not self._alerted_recently(code, 'trailing_stop_5'):
alerts.append(('trailing_stop_5', f"📉 利润回撤 {drawdown:.1f}%,建议减仓保护利润"))
alert_weights.append(2)
# 回撤10%提醒清仓
elif drawdown >= 10 and not self._alerted_recently(code, 'trailing_stop_10'):
alerts.append(('trailing_stop_10', f"🚨 利润回撤 {drawdown:.1f}%,建议清仓止损"))
alert_weights.append(3)
# 6. 计算预警级别
level = self._calculate_alert_level(alerts, alert_weights, stock_type)
return alerts, level
def _calculate_alert_level(self, alerts, weights, stock_type):
"""计算预警级别: info(提醒) / warning(警告) / critical(紧急)"""
if not alerts:
return None
total_weight = sum(weights)
alert_count = len(alerts)
# 紧急: 多条件共振 或 高权重单一条件
if total_weight >= 5 or alert_count >= 3:
return "critical"
# 警告: 中等权重 或 2个条件
if total_weight >= 3 or alert_count >= 2:
return "warning"
# 提醒: 单一低权重条件
return "info"
def _alerted_recently(self, code, atype):
now = time.time()
self.alert_log = [l for l in self.alert_log if now - l['t'] < 1800] # 30分钟有效期
for l in self.alert_log:
if l['c'] == code and l['a'] == atype: return True
return False
def record_alert(self, code, atype):
self.alert_log.append({'c': code, 'a': atype, 't': time.time()})
def fetch_news(self, symbol):
"""抓取个股最近新闻 (新浪/东财聚合) - 简化版"""
try:
# 使用东财个股新闻API
url = f"https://emweb.securities.eastmoney.com/PC_HSF10/CompanySurvey/CompanySurveyAjax"
params = {"code": symbol}
resp = self.session.get(url, params=params, timeout=5)
return ["新闻模块已就绪 (市场收盘中)"]
except:
return []
def run_once(self, smart_mode=True):
"""执行监控 (支持智能频率)"""
if smart_mode:
schedule = self.should_run_now()
if not schedule.get("run"):
return []
stocks_to_check = schedule.get("stocks", WATCHLIST)
mode = schedule.get("mode", "normal")
# 只在特定模式打印日志
if mode in ["market", "weekend"]:
print(f"[{datetime.now().strftime('%H:%M')}] {mode}模式扫描 {len(stocks_to_check)} 只标的...")
else:
stocks_to_check = WATCHLIST
data_map = self.fetch_sina_realtime(stocks_to_check)
triggered = []
for stock in stocks_to_check:
code = stock['code']
if code not in data_map: continue
data = data_map[code]
# 数据有效性检查
if data['price'] <= 0 or data['prev_close'] <= 0:
continue
alerts, level = self.check_alerts(stock, data)
if alerts:
change_pct = (data['price'] - data['prev_close']) / data['prev_close'] * 100 if data['prev_close'] else 0
# 中国习惯: 红色=上涨, 绿色=下跌
if change_pct > 0:
color_emoji = "🔴" # 红涨
elif change_pct < 0:
color_emoji = "🟢" # 绿跌
else:
color_emoji = ""
# 预警级别标识
level_icons = {
"critical": "🚨", # 紧急
"warning": "⚠️", # 警告
"info": "📢" # 提醒
}
level_icon = level_icons.get(level, "📢")
level_text = {"critical": "【紧急】", "warning": "【警告】", "info": "【提醒】"}.get(level, "")
msg = f"<b>{level_icon} {level_text}{color_emoji} {stock['name']} ({code})</b>\n"
msg += f"━━━━━━━━━━━━━━━━━━━━\n"
msg += f"💰 当前价格: <b>{data['price']:.2f}</b> ({change_pct:+.2f}%)\n"
# 显示持仓盈亏
cost = stock.get('cost', 0)
if cost > 0:
cost_change = (data['price'] - cost) / cost * 100
profit_icon = "🔴+" if cost_change > 0 else "🟢"
msg += f"📊 持仓成本: ¥{cost:.2f} | 盈亏: {profit_icon}{cost_change:.2f}%\n"
msg += f"\n🎯 触发预警 ({len(alerts)}项):\n"
for _, text in alerts:
msg += f"{text}\n"
self.record_alert(code, _)
# Pro版:集成智能分析
try:
from analyser import StockAnalyser
analyser = StockAnalyser()
insight = analyser.generate_insight(stock, {
'price': data['price'],
'change_pct': change_pct
}, alerts)
msg += f"\n{insight}"
except Exception:
pass
triggered.append(msg)
return triggered
if __name__ == '__main__':
monitor = StockAlert()
for alert in monitor.run_once():
print(alert)
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Stock Monitor Daemon - 后台常驻进程
自动运行监控,智能控制频率,支持 graceful shutdown
"""
import sys
import time
import signal
import logging
from datetime import datetime
from pathlib import Path
# 设置日志
log_dir = Path.home() / ".stock_monitor"
log_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_dir / "monitor.log"),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# 导入监控类
sys.path.insert(0, str(Path(__file__).parent))
from monitor import StockAlert, WATCHLIST
class MonitorDaemon:
def __init__(self):
self.monitor = StockAlert()
self.running = True
self.last_run_time = 0
# 设置信号处理
signal.signal(signal.SIGTERM, self.handle_shutdown)
signal.signal(signal.SIGINT, self.handle_shutdown)
def handle_shutdown(self, signum, frame):
"""优雅退出"""
logger.info(f"收到信号 {signum},正在关闭...")
self.running = False
def get_sleep_interval(self):
"""根据当前时间获取睡眠间隔"""
schedule = self.monitor.should_run_now()
if not schedule.get("run"):
# 如果当前不需要运行,计算到下次运行的时间
now = datetime.now()
hour = now.hour
# 凌晨时段,1小时后检查
if 0 <= hour < 9:
return 3600
return 300 # 默认5分钟
return schedule.get("interval", 300)
def run(self):
"""主循环"""
logger.info("=" * 60)
logger.info("🚀 Stock Monitor Daemon 启动")
logger.info(f"📋 监控标的: {len(WATCHLIST)}")
logger.info("=" * 60)
while self.running:
try:
# 检查是否应该执行
schedule = self.monitor.should_run_now()
if schedule.get("run"):
mode = schedule.get("mode", "normal")
stocks_count = len(schedule.get("stocks", []))
logger.info(f"[{mode}] 扫描 {stocks_count} 只标的...")
# 执行监控
alerts = self.monitor.run_once(smart_mode=False) # 已经判断过了
if alerts:
logger.info(f"⚠️ 触发 {len(alerts)} 条预警")
# 这里会通过 message 工具发送通知
else:
logger.debug("✅ 无预警")
self.last_run_time = time.time()
# 计算睡眠间隔
sleep_interval = self.get_sleep_interval()
logger.debug(f"下次检查: {sleep_interval} 秒后")
# 分段睡眠,方便及时响应退出信号
slept = 0
while slept < sleep_interval and self.running:
time.sleep(1)
slept += 1
except Exception as e:
logger.error(f"运行出错: {e}", exc_info=True)
time.sleep(60) # 出错后等待1分钟重试
logger.info("👋 Daemon 已停止")
if __name__ == '__main__':
daemon = MonitorDaemon()
daemon.run()
@@ -0,0 +1,273 @@
#!/usr/bin/env python3
"""
Stock Monitor Pro - 完整测试套件
测试所有功能模块,确保系统稳定性
"""
import sys
import time
import unittest
from datetime import datetime, timedelta
from unittest.mock import Mock, patch
sys.path.insert(0, '/home/wesley/.openclaw/workspace/skills/stock-monitor/scripts')
from monitor import StockAlert, WATCHLIST
class TestDataFetching(unittest.TestCase):
"""测试1: 数据获取模块"""
def setUp(self):
self.monitor = StockAlert()
def test_sina_realtime_api(self):
"""测试新浪实时行情API"""
data = self.monitor.fetch_sina_realtime([WATCHLIST[0]])
self.assertIn('600362', data)
self.assertGreater(data['600362']['price'], 0)
print("✅ 新浪实时行情API正常")
def test_gold_api(self):
"""测试伦敦金API"""
data = self.monitor.fetch_sina_realtime([WATCHLIST[-1]])
self.assertIn('XAU', data)
self.assertGreater(data['XAU']['price'], 4000) # 黄金应该在4000以上
print("✅ 伦敦金API正常")
def test_data_validity(self):
"""测试数据有效性检查"""
data = self.monitor.fetch_sina_realtime(WATCHLIST[:3])
for code, d in data.items():
self.assertGreater(d['price'], 0, f"{code}价格无效")
self.assertGreater(d['prev_close'], 0, f"{code}昨收无效")
print("✅ 所有数据有效性检查通过")
class TestAlertRules(unittest.TestCase):
"""测试2: 预警规则模块"""
def setUp(self):
self.monitor = StockAlert()
def test_cost_percentage_alert(self):
"""测试成本百分比预警"""
stock = WATCHLIST[0].copy()
stock['alerts'] = {'cost_pct_above': 10.0, 'cost_pct_below': -10.0}
# 模拟盈利10%的数据
data = {'price': 62.7, 'prev_close': 57.0, 'cost': 57.0} # 成本57,现价62.7=+10%
alerts, level = self.monitor.check_alerts(stock, data)
has_profit_alert = any('盈利' in text for _, text in alerts)
self.assertTrue(has_profit_alert, "应该有盈利预警")
print("✅ 成本百分比预警正常")
def test_daily_change_alert(self):
"""测试日内涨跌幅预警"""
stock = WATCHLIST[0].copy()
stock['alerts'] = {'change_pct_above': 5.0, 'change_pct_below': -5.0}
# 模拟大涨6%
data = {'price': 60.42, 'prev_close': 57.0, 'cost': 57.0}
alerts, level = self.monitor.check_alerts(stock, data)
has_change_alert = any('大涨' in text or '大跌' in text for _, text in alerts)
self.assertTrue(has_change_alert, "应该有涨跌幅预警")
print("✅ 日内涨跌幅预警正常")
def test_no_duplicate_alerts(self):
"""测试防重复机制"""
stock = WATCHLIST[0].copy()
stock['alerts'] = {'cost_pct_above': 5.0}
data = {'price': 60.0, 'prev_close': 57.0, 'cost': 57.0}
# 第一次应该触发
alerts1, _ = self.monitor.check_alerts(stock, data)
self.assertGreater(len(alerts1), 0, "第一次应该触发预警")
# 记录预警
for alert_type, _ in alerts1:
self.monitor.record_alert(stock['code'], alert_type)
# 第二次不应该触发 (30分钟内)
alerts2, _ = self.monitor.check_alerts(stock, data)
self.assertEqual(len(alerts2), 0, "30分钟内不应重复触发")
print("✅ 防重复机制正常")
class TestAlertLevel(unittest.TestCase):
"""测试3: 分级预警系统"""
def setUp(self):
self.monitor = StockAlert()
def test_critical_level(self):
"""测试紧急级别"""
alerts = [('a', 'test'), ('b', 'test'), ('c', 'test')]
weights = [3, 3, 3] # 总权重9
level = self.monitor._calculate_alert_level(alerts, weights, 'individual')
self.assertEqual(level, 'critical')
print("✅ 紧急级别判断正常")
def test_warning_level(self):
"""测试警告级别"""
alerts = [('a', 'test'), ('b', 'test')]
weights = [2, 2] # 总权重4
level = self.monitor._calculate_alert_level(alerts, weights, 'individual')
self.assertEqual(level, 'warning')
print("✅ 警告级别判断正常")
def test_info_level(self):
"""测试提醒级别"""
alerts = [('a', 'test')]
weights = [1]
level = self.monitor._calculate_alert_level(alerts, weights, 'individual')
self.assertEqual(level, 'info')
print("✅ 提醒级别判断正常")
class TestStockTypeDifferentiation(unittest.TestCase):
"""测试4: 差异化配置"""
def test_individual_stock_threshold(self):
"""测试个股阈值"""
stock = [s for s in WATCHLIST if s.get('type') == 'individual'][0]
self.assertEqual(stock['alerts']['change_pct_above'], 4.0)
print("✅ 个股阈值配置正确")
def test_etf_threshold(self):
"""测试ETF阈值"""
stock = [s for s in WATCHLIST if s.get('type') == 'etf'][0]
self.assertEqual(stock['alerts']['change_pct_above'], 2.0)
print("✅ ETF阈值配置正确")
def test_gold_threshold(self):
"""测试黄金阈值"""
stock = [s for s in WATCHLIST if s.get('type') == 'gold'][0]
self.assertEqual(stock['alerts']['change_pct_above'], 2.5)
print("✅ 黄金阈值配置正确")
class TestSmartSchedule(unittest.TestCase):
"""测试5: 智能频率控制"""
def setUp(self):
self.monitor = StockAlert()
def test_market_hours_detection(self):
"""测试交易时间检测"""
# 当前是纽约时间,转换成北京时间
ny_now = datetime.now()
beijing_now = ny_now + timedelta(hours=13)
schedule = self.monitor.should_run_now()
self.assertIn('mode', schedule)
self.assertIn(schedule['mode'], ['market', 'lunch', 'after_hours', 'night', 'weekend'])
print(f"✅ 时间检测正常 (当前模式: {schedule['mode']})")
def test_interval_settings(self):
"""测试不同模式的间隔设置"""
schedule = self.monitor.should_run_now()
interval = schedule.get('interval', 0)
self.assertGreater(interval, 0)
self.assertIn(interval, [300, 600, 1800, 3600]) # 5/10/30/60分钟
print(f"✅ 间隔设置正常 ({interval//60}分钟)")
class TestMessageFormat(unittest.TestCase):
"""测试6: 消息格式"""
def setUp(self):
self.monitor = StockAlert()
def test_message_contains_required_elements(self):
"""测试消息包含必要元素"""
# 模拟触发预警
stock = WATCHLIST[0]
data = {'price': 54.0, 'prev_close': 57.0, 'open': 55.0, 'high': 56.0, 'low': 53.0}
alerts, level = [('cost_below', '📉 亏损10%')], 'warning'
# 构建消息
change_pct = -5.26
msg = f"<b>⚠️ 【警告】🟢 {stock['name']} ({stock['code']})</b>\n"
msg += f"💰 当前价格: ¥{data['price']:.2f} ({change_pct:+.2f}%)\n"
msg += f"🎯 触发预警:\n{alerts[0][1]}\n"
# 检查必要元素
self.assertIn('【警告】', msg)
self.assertIn('🟢', msg) # 绿跌
self.assertIn('💰', msg)
self.assertIn('🎯', msg)
print("✅ 消息格式包含必要元素")
class TestIntegration(unittest.TestCase):
"""测试7: 集成测试"""
def setUp(self):
self.monitor = StockAlert()
def test_full_run_once(self):
"""测试完整run_once流程"""
start = time.time()
alerts_list = self.monitor.run_once(smart_mode=True)
elapsed = time.time() - start
# 执行时间应该合理 (10-30秒)
self.assertLess(elapsed, 60, "执行时间过长")
self.assertIsInstance(alerts_list, list)
print(f"✅ 完整流程正常 (执行时间: {elapsed:.2f}秒, 触发{len(alerts_list)}条)")
def test_all_stocks_monitored(self):
"""测试所有股票都被监控"""
data = self.monitor.fetch_sina_realtime(WATCHLIST)
# 至少应该获取到部分数据
self.assertGreater(len(data), 0)
print(f"✅ 监控覆盖正常 (获取到{len(data)}/{len(WATCHLIST)}只数据)")
def run_all_tests():
"""运行所有测试"""
print("=" * 70)
print("🧪 Stock Monitor Pro - 完整测试套件")
print("=" * 70)
# 创建测试套件
loader = unittest.TestLoader()
suite = unittest.TestSuite()
# 添加所有测试类
suite.addTests(loader.loadTestsFromTestCase(TestDataFetching))
suite.addTests(loader.loadTestsFromTestCase(TestAlertRules))
suite.addTests(loader.loadTestsFromTestCase(TestAlertLevel))
suite.addTests(loader.loadTestsFromTestCase(TestStockTypeDifferentiation))
suite.addTests(loader.loadTestsFromTestCase(TestSmartSchedule))
suite.addTests(loader.loadTestsFromTestCase(TestMessageFormat))
suite.addTests(loader.loadTestsFromTestCase(TestIntegration))
# 运行测试
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
# 输出总结
print("\n" + "=" * 70)
print("📊 测试总结")
print("=" * 70)
print(f" 测试总数: {result.testsRun}")
print(f" 通过: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f" 失败: {len(result.failures)}")
print(f" 错误: {len(result.errors)}")
if result.wasSuccessful():
print("\n✅ 所有测试通过!系统可以正常运行。")
else:
print("\n⚠️ 部分测试失败,请检查日志。")
return result.wasSuccessful()
if __name__ == '__main__':
success = run_all_tests()
sys.exit(0 if success else 1)