修改skill目录

This commit is contained in:
朱潮 2026-04-18 23:42:20 +08:00
parent 2911c67771
commit e217fe3403
36 changed files with 0 additions and 610 deletions

View File

@ -1,218 +0,0 @@
---
name: auto-daily-summary
description: Generate recurring summaries, daily reports, content digests, and concise action-oriented briefs from multiple inputs. Use when the user asks for daily summaries, periodic briefings, meeting digests, content condensation, or automated recurring report generation. 中文触发词包括:日报、周报、摘要、会议纪要、内容浓缩、自动汇总、每天发我一份总结。
---
# Auto Daily Summary
## Overview
This skill converts scattered information into concise, structured summaries for recurring or one-off use.
Typical scenarios:
- daily or weekly report generation
- long content condensation
- meeting or conversation summary
- multi-source digest
- action-item extraction
This skill focuses on **organization and summarization**, not source retrieval itself.
## Quick Start
When the user asks for a summary or report:
1. Identify the sources to summarize
2. Clarify the audience and desired level of detail
3. Determine whether the output is one-time or recurring
4. Summarize by theme, not by raw chronological dump unless requested
5. Extract action items and watch items when useful
### 中文任务映射
- “帮我整理成日报” → `daily_report`
- “做个周报/周总结” → `digest``daily_report`
- “把这段会议内容整理一下” → `meeting_digest`
- “浓缩成 3-5 条重点” → `digest` + `short`
- “每天早上发我一份总结” → `plan-recurring` + `schedule-job`
## Input Requirements
| Field | Required | Description |
|-------|----------|-------------|
| source content | yes | Text, notes, messages, links, reports, logs, or mixed content |
| summary objective | yes | Inform, decide, archive, handoff, or monitor |
| audience | no | Self, team, manager, executive, customer |
| time scope | no | Today, this week, meeting duration, selected period |
| desired length | no | TL;DR, short, standard, detailed |
| output style | no | Daily report, digest, executive summary, bullet list |
| action extraction | no | Whether to extract todos, risks, blockers |
## Workflow Decision Tree
### Content Summary
Use when the user wants a concise summary of a long input.
### Daily / Weekly Report
Use when the user wants a periodic report with sections and status updates.
### Meeting Digest
Use when the user wants decisions, action items, and blockers from a discussion.
### Recurring Summary Workflow
Use when the user wants this to happen on a schedule. In that case, pair with `schedule-job`.
## Instructions
### Step 1: Identify source boundaries
Clarify what should and should not be included in the summary.
### Step 2: Determine the correct abstraction level
Choose the right level for the audience:
- executive audience -> implications and decisions
- working team -> concrete tasks and blockers
- archive -> structured factual recap
### Step 3: Group by theme
Prefer grouping by:
- progress
- decisions
- blockers
- risks
- next steps
Avoid copying source order unless chronology itself matters.
### Step 4: Extract action items
When appropriate, identify:
- owner
- task
- due timing
- dependency or blocker
If ownership is unclear, say so.
### Step 5: Prepare for automation if needed
If the user wants recurring output:
- use `schedule-job` for cadence
- use `imap-smtp-email` or other enabled notification skills for delivery
## Scripts
### CLI Usage
Use the following commands when you need stable structured outputs:
```bash
poetry run python skills/auto-daily-summary/scripts/summary_cli.py validate --input-json '<JSON>'
poetry run python skills/auto-daily-summary/scripts/summary_cli.py run --input-json '<JSON>' --output json
poetry run python skills/auto-daily-summary/scripts/summary_cli.py plan-recurring --input-json '<JSON>'
```
### Recommended Uses
- `validate` - check whether the summary request payload is complete
- `run` - generate summary JSON and markdown
- `plan-recurring` - generate a schedule-ready message payload for `schedule-job`
### Daily Report
```markdown
# Daily Report
## Summary
[Short summary]
## Key Updates
- [Update]
## Decisions
- [Decision]
## Risks / Blockers
- [Risk or blocker]
## Next Actions
- [Action]
```
### Content Digest
```markdown
# Content Digest
## TL;DR
[Very short summary]
## Main Themes
### 1. [Theme]
- [Key point]
## Notable Details
- [Detail]
## Follow-up
- [Suggested follow-up]
```
## Quality Checklist
Before finalizing, verify:
- the summary matches the audience level
- repetition and noise are removed
- key decisions are not buried
- action items are explicit when relevant
- uncertainty is preserved rather than flattened away
- the result is shorter and clearer than the source material
## Fallback Strategy
If the input is too fragmented:
- produce a partial summary by theme
- list gaps or unclear areas
- ask for additional source material only if needed for the users stated goal
## Related Skills
- `skills/schedule-job/SKILL.md` - automate recurring execution
- `skills/imap-smtp-email/SKILL.md` - send summaries via email
- `skills/market-academic-insight/SKILL.md` - use when the task is deeper research synthesis rather than pure summarization
- `skills/competitor-news-intel/SKILL.md` - use when competitor monitoring and intelligence is the real task
## Examples
**User**: "帮我把今天的工作内容整理成日报"
Expected output:
- summary
- key updates
- blockers
- next actions
**User**: "把这篇长文浓缩成 5 条重点"
Expected output:
- TL;DR
- 5 concise points
- optional follow-up note
**User**: "每天早上自动给我发新闻摘要"
Expected output:
- summary format definition
- recommendation to combine with `schedule-job`
- delivery method confirmation
**User**: "把这段会议记录整理成会议纪要"
Expected output:
- summary
- decisions
- action items
- blockers if any
**User**: "给我做个今天的三段式总结"
Expected output:
- summary
- key updates
- next actions

View File

@ -1,164 +0,0 @@
#!/usr/bin/env python3
import argparse
import json
import sys
from datetime import datetime, UTC
from summary_core import build_summary, validate_payload
ERROR_TEMPLATE = {
"success": False,
"code": "invalid_input",
"message": "",
"data": {},
"meta": {},
"errors": [],
}
def _now_iso() -> str:
return datetime.now(UTC).isoformat()
def _emit_json(data: dict, pretty: bool, stream=None):
print(json.dumps(data, ensure_ascii=False, indent=2 if pretty else None), file=stream or sys.stdout)
def _error_response(code: str, message: str, errors: list[str] | None = None) -> dict:
return {
**ERROR_TEMPLATE,
"code": code,
"message": message,
"meta": {"generated_at": _now_iso()},
"errors": errors or [],
}
def _parse_bool(value: str | None) -> bool | None:
if value is None:
return None
lowered = value.lower()
if lowered in {"1", "true", "yes", "y"}:
return True
if lowered in {"0", "false", "no", "n"}:
return False
raise ValueError(f"invalid boolean value: {value}")
def _load_payload(raw: str) -> dict:
return json.loads(raw)
def _apply_overrides(payload: dict, args: argparse.Namespace) -> dict:
payload.setdefault("data", {})
if args.lang:
payload["language"] = args.lang
if args.style:
payload["data"]["style"] = args.style
if args.length:
payload["data"]["length"] = args.length
if hasattr(args, "extract_actions"):
extract_actions = _parse_bool(getattr(args, "extract_actions", None))
if extract_actions is not None:
payload["data"]["extract_actions"] = extract_actions
if hasattr(args, "extract_risks"):
extract_risks = _parse_bool(getattr(args, "extract_risks", None))
if extract_risks is not None:
payload["data"]["extract_risks"] = extract_risks
return payload
def cmd_validate(args: argparse.Namespace):
payload = _apply_overrides(_load_payload(args.input_json), args)
errors = validate_payload(payload)
result = {
"success": not errors,
"code": "ok" if not errors else "invalid_input",
"message": "payload valid" if not errors else "payload invalid",
"data": {"valid": not errors},
"meta": {"generated_at": _now_iso()},
"errors": errors,
}
target_stream = sys.stdout if not errors else sys.stderr
_emit_json(result, args.pretty, target_stream)
if errors:
raise SystemExit(1)
def cmd_run(args: argparse.Namespace):
payload = _apply_overrides(_load_payload(args.input_json), args)
errors = validate_payload(payload)
if errors:
_emit_json(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
raise SystemExit(1)
data = build_summary(payload)
if args.output == "markdown":
print(data["markdown"])
return
result = {
"success": True,
"code": "ok",
"message": "summary generated",
"data": data,
"meta": {
"generated_at": _now_iso(),
"source_count": len(payload.get("data", {}).get("sources", [])),
},
"errors": [],
}
_emit_json(result, args.pretty)
def cmd_plan_recurring(args: argparse.Namespace):
payload = _apply_overrides(_load_payload(args.input_json), args)
errors = validate_payload(payload)
if errors:
_emit_json(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
raise SystemExit(1)
data = build_summary(payload)
result = {
"success": True,
"code": "ok",
"message": "recurring plan generated",
"data": {"schedule_payload": data["schedule_payload"]},
"meta": {"generated_at": _now_iso()},
"errors": [],
}
_emit_json(result, args.pretty)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Generate structured summaries")
subparsers = parser.add_subparsers(dest="command", required=True)
for name in ["validate", "run", "plan-recurring"]:
sub = subparsers.add_parser(name)
sub.add_argument("--input-json", required=True)
sub.add_argument("--lang")
sub.add_argument("--style")
sub.add_argument("--length")
sub.add_argument("--extract-actions")
sub.add_argument("--extract-risks")
sub.add_argument("--pretty", action="store_true")
if name == "run":
sub.add_argument("--output", choices=["json", "markdown"], default="json")
subparsers.choices["validate"].set_defaults(func=cmd_validate)
subparsers.choices["run"].set_defaults(func=cmd_run)
subparsers.choices["plan-recurring"].set_defaults(func=cmd_plan_recurring)
return parser
if __name__ == "__main__":
parser = build_parser()
args = parser.parse_args()
try:
args.func(args)
except json.JSONDecodeError as exc:
_emit_json(_error_response("invalid_input", f"invalid json: {exc}", [str(exc)]), getattr(args, "pretty", False), sys.stderr)
raise SystemExit(1)
except ValueError as exc:
_emit_json(_error_response("invalid_input", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
raise SystemExit(1)
except Exception as exc:
_emit_json(_error_response("internal_error", "unexpected error", [str(exc)]), getattr(args, "pretty", False), sys.stderr)
raise SystemExit(1)

View File

@ -1,228 +0,0 @@
import re
from collections import Counter
from typing import Any
SUMMARY_LENGTH_LIMITS = {
"tldr": 2,
"short": 4,
"standard": 6,
"detailed": 10,
}
ACTION_PREFIX_PATTERNS = [
r"^(?:TODO|待办|action)[:]?\s*(.+)$",
r"^(?:需要|跟进)\s*(.+)$",
]
RISK_KEYWORDS = {
"high": ["阻塞", "blocker", "故障", "失败", "严重", "不可用"],
"medium": ["风险", "延迟", "异常", "超时", "报错"],
"low": ["提醒", "注意", "观察", "待确认"],
}
BOOL_FIELDS = ["extract_actions", "extract_risks"]
VALID_STYLES = {"daily_report", "digest", "meeting_digest", "executive"}
VALID_LENGTHS = {"tldr", "short", "standard", "detailed"}
def _clean_text(text: str) -> str:
return re.sub(r"\s+", " ", (text or "").strip())
def _normalize_key(text: str) -> str:
text = _clean_text(text).lower()
return re.sub(r"[^\w\u4e00-\u9fff]+", "", text)
def _split_sentences(text: str) -> list[str]:
raw_parts = re.split(r"[。!?;.!?;]+|\n+", text)
return [_clean_text(part) for part in raw_parts if _clean_text(part)]
def _sentence_tokens(sentence: str) -> list[str]:
return re.findall(r"[A-Za-z0-9_-]+|[\u4e00-\u9fff]{2,}", sentence.lower())
def _top_sentences(texts: list[str], limit: int) -> list[str]:
sentences: list[str] = []
for text in texts:
sentences.extend(_split_sentences(text))
if not sentences:
return []
token_counter = Counter()
for sentence in sentences:
token_counter.update(_sentence_tokens(sentence))
scored: list[tuple[int, int, str]] = []
for index, sentence in enumerate(sentences):
score = sum(token_counter[token] for token in _sentence_tokens(sentence))
scored.append((score, -index, sentence))
ranked = [sentence for _, _, sentence in sorted(scored, reverse=True)]
unique_ranked = []
seen = set()
for sentence in ranked:
key = _normalize_key(sentence)
if key and key not in seen:
seen.add(key)
unique_ranked.append(sentence)
if len(unique_ranked) >= limit:
break
return unique_ranked
def _trim_fragment(text: str, max_length: int = 80) -> str:
fragment = re.split(r"[,。;;!?]", text, maxsplit=1)[0]
fragment = _clean_text(fragment)
return fragment[:max_length].strip()
def _extract_actions(texts: list[str]) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = []
for text in texts:
for sentence in _split_sentences(text):
for pattern in ACTION_PREFIX_PATTERNS:
match = re.match(pattern, sentence, re.IGNORECASE)
if not match:
continue
task = _trim_fragment(match.group(1))
if len(task) < 2:
continue
items.append({"task": task, "owner": None, "due_at": None, "blocker": None})
break
dedup = []
seen = set()
for item in items:
key = _normalize_key(item["task"])
if key and key not in seen:
seen.add(key)
dedup.append(item)
return dedup[:10]
def _extract_risks(texts: list[str]) -> list[dict[str, Any]]:
risks: list[dict[str, Any]] = []
for text in texts:
for sentence in _split_sentences(text):
lowered = sentence.lower()
for impact, keywords in RISK_KEYWORDS.items():
matched = next((keyword for keyword in keywords if keyword.lower() in lowered), None)
if not matched:
continue
start = max(0, lowered.find(matched.lower()) - 18)
end = min(len(sentence), lowered.find(matched.lower()) + len(matched) + 30)
fragment = _clean_text(sentence[start:end])
fragment = fragment[:120]
if len(fragment) < 2:
continue
risks.append({"risk": fragment, "impact": impact, "mitigation": None})
break
dedup = []
seen = set()
for item in risks:
key = _normalize_key(item["risk"])
if key and key not in seen:
seen.add(key)
dedup.append(item)
return dedup[:10]
def _build_summary_line(sentences: list[str]) -> str:
if not sentences:
return "暂无可提炼的关键信息。"
selected = sentences[:2]
return "".join(selected)
def build_summary(payload: dict[str, Any]) -> dict[str, Any]:
data = payload.get("data", {})
sources = data.get("sources", [])
texts = [_clean_text(source.get("content", "")) for source in sources if _clean_text(source.get("content", ""))]
length = data.get("length", "standard")
style = data.get("style", "daily_report")
limit = SUMMARY_LENGTH_LIMITS.get(length, SUMMARY_LENGTH_LIMITS["standard"])
top_sentences = _top_sentences(texts, limit)
summary_line = _build_summary_line(top_sentences)
summary_keys = {_normalize_key(sentence) for sentence in top_sentences[:2]}
detail_sentences = [sentence for sentence in top_sentences if _normalize_key(sentence) not in summary_keys]
sections = []
if detail_sentences:
if len(detail_sentences) == 1:
sections = [{"title": "Key Updates", "bullets": detail_sentences}]
else:
midpoint = max(1, len(detail_sentences) // 2)
sections = [
{"title": "Key Updates", "bullets": detail_sentences[:midpoint]},
{"title": "Notable Details", "bullets": detail_sentences[midpoint:]},
]
action_items = _extract_actions(texts) if data.get("extract_actions") else []
risk_items = _extract_risks(texts) if data.get("extract_risks") else []
markdown_lines = ["# Summary", "", "## Summary", f"- {summary_line}"]
for section in sections:
if not section["bullets"]:
continue
markdown_lines.extend(["", f"## {section['title']}"])
markdown_lines.extend(f"- {bullet}" for bullet in section["bullets"])
if action_items:
markdown_lines.extend(["", "## Action Items"])
markdown_lines.extend(f"- {item['task']}" for item in action_items)
if risk_items:
markdown_lines.extend(["", "## Risks"])
markdown_lines.extend(f"- [{item['impact']}] {item['risk']}" for item in risk_items)
schedule_payload = {
"suggested_name": "Daily Summary",
"message": "[Scheduled Task Triggered] 请立即汇总最新内容并输出结构化摘要,如有行动项和风险请一并列出,然后选择合适的通知方式发送给用户。",
}
return {
"summary": summary_line,
"sections": sections,
"action_items": action_items,
"risk_items": risk_items,
"markdown": "\n".join(markdown_lines),
"schedule_payload": schedule_payload,
"style": style,
}
def _validate_source(source: Any, index: int) -> list[str]:
errors = []
if not isinstance(source, dict):
return [f"data.sources[{index}] must be an object"]
if not _clean_text(str(source.get("content", ""))):
errors.append(f"data.sources[{index}].content is required")
return errors
def validate_payload(payload: dict[str, Any]) -> list[str]:
errors = []
data = payload.get("data")
if not isinstance(data, dict):
return ["data must be an object"]
sources = data.get("sources")
if not isinstance(sources, list) or not sources:
errors.append("data.sources must be a non-empty array")
else:
for index, source in enumerate(sources):
errors.extend(_validate_source(source, index))
objective = data.get("objective")
if not isinstance(objective, str) or not objective.strip():
errors.append("data.objective is required")
style = data.get("style")
if style is not None and style not in VALID_STYLES:
errors.append(f"data.style must be one of {sorted(VALID_STYLES)}")
length = data.get("length")
if length is not None and length not in VALID_LENGTHS:
errors.append(f"data.length must be one of {sorted(VALID_LENGTHS)}")
for field in BOOL_FIELDS:
value = data.get(field)
if value is not None and not isinstance(value, bool):
errors.append(f"data.{field} must be a boolean")
return errors