mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-10 13:11:30 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c5e81fd15 | |||
| 058d25d02c | |||
| fe09ec546f | |||
| cf611a4514 | |||
| 7cf30e969f | |||
| 18b71157db |
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||
"version": "1.25.0"
|
||||
"version": "1.25.3"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
English | [中文](./CHANGELOG.zh.md)
|
||||
|
||||
## 1.25.3 - 2026-01-28
|
||||
|
||||
### Features
|
||||
- `baoyu-format-markdown`: add content type detection with user confirmation for markdown files; add CJK punctuation handling to move paired punctuation outside emphasis markers.
|
||||
|
||||
## 1.25.2 - 2026-01-28
|
||||
|
||||
### Documentation
|
||||
- `baoyu-post-to-wechat`: add WeChat API credentials configuration guide to README.
|
||||
|
||||
## 1.25.1 - 2026-01-28
|
||||
|
||||
### Features
|
||||
- `baoyu-markdown-to-html`: add pre-check step for CJK content to suggest formatting with `baoyu-format-markdown` before conversion.
|
||||
|
||||
## 1.25.0 - 2026-01-28
|
||||
|
||||
### Features
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
[English](./CHANGELOG.md) | 中文
|
||||
|
||||
## 1.25.3 - 2026-01-28
|
||||
|
||||
### 新功能
|
||||
- `baoyu-format-markdown`:新增内容类型检测,对已有 markdown 格式的文件提供用户确认选项;新增 CJK 配对标点处理,将括号、引号等标点移出加粗标记外。
|
||||
|
||||
## 1.25.2 - 2026-01-28
|
||||
|
||||
### 文档
|
||||
- `baoyu-post-to-wechat`:README 新增微信公众号 API 凭证配置说明。
|
||||
|
||||
## 1.25.1 - 2026-01-28
|
||||
|
||||
### 新功能
|
||||
- `baoyu-markdown-to-html`:新增中文内容预检查,建议在转换前使用 `baoyu-format-markdown` 格式化以修复加粗标点问题。
|
||||
|
||||
## 1.25.0 - 2026-01-28
|
||||
|
||||
### 新功能
|
||||
|
||||
@@ -535,7 +535,28 @@ Post content to WeChat Official Account (微信公众号). Two modes available:
|
||||
/baoyu-post-to-wechat 文章 --html article.html
|
||||
```
|
||||
|
||||
Prerequisites: Google Chrome installed. First run requires QR code login (session preserved).
|
||||
**Publishing Methods**:
|
||||
|
||||
| Method | Speed | Requirements |
|
||||
|--------|-------|--------------|
|
||||
| API (Recommended) | Fast | API credentials |
|
||||
| Browser | Slow | Chrome, login session |
|
||||
|
||||
**API Configuration** (for faster publishing):
|
||||
|
||||
```bash
|
||||
# Add to .baoyu-skills/.env (project-level) or ~/.baoyu-skills/.env (user-level)
|
||||
WECHAT_APP_ID=your_app_id
|
||||
WECHAT_APP_SECRET=your_app_secret
|
||||
```
|
||||
|
||||
To obtain credentials:
|
||||
1. Visit https://developers.weixin.qq.com/platform/
|
||||
2. Go to: 我的业务 → 公众号 → 开发密钥
|
||||
3. Create development key and copy AppID/AppSecret
|
||||
4. Add your machine's IP to the whitelist
|
||||
|
||||
**Browser Method** (no API setup needed): Requires Google Chrome. First run opens browser for QR code login (session preserved).
|
||||
|
||||
### AI Generation Skills
|
||||
|
||||
|
||||
+22
-1
@@ -535,7 +535,28 @@ npx skills add jimliu/baoyu-skills
|
||||
/baoyu-post-to-wechat 文章 --html article.html
|
||||
```
|
||||
|
||||
前置要求:已安装 Google Chrome,首次运行需扫码登录(登录状态会保存)
|
||||
**发布方式**:
|
||||
|
||||
| 方式 | 速度 | 要求 |
|
||||
|------|------|------|
|
||||
| API(推荐) | 快 | API 凭证 |
|
||||
| 浏览器 | 慢 | Chrome,登录会话 |
|
||||
|
||||
**API 配置**(更快的发布方式):
|
||||
|
||||
```bash
|
||||
# 添加到 .baoyu-skills/.env(项目级)或 ~/.baoyu-skills/.env(用户级)
|
||||
WECHAT_APP_ID=你的AppID
|
||||
WECHAT_APP_SECRET=你的AppSecret
|
||||
```
|
||||
|
||||
获取凭证方法:
|
||||
1. 访问 https://developers.weixin.qq.com/platform/
|
||||
2. 进入:我的业务 → 公众号 → 开发密钥
|
||||
3. 添加开发密钥,复制 AppID 和 AppSecret
|
||||
4. 将你操作的机器 IP 加入白名单
|
||||
|
||||
**浏览器方式**(无需 API 配置):需已安装 Google Chrome,首次运行需扫码登录(登录状态会保存)
|
||||
|
||||
### AI 生成技能 (AI Generation Skills)
|
||||
|
||||
|
||||
@@ -58,6 +58,55 @@ Claude performs content analysis and formatting (Steps 1-6), then runs the scrip
|
||||
|
||||
Read the user-specified markdown or plain text file.
|
||||
|
||||
### Step 1.5: Detect Content Type & Confirm
|
||||
|
||||
**Content Type Detection:**
|
||||
|
||||
| Indicator | Classification |
|
||||
|-----------|----------------|
|
||||
| Has `---` YAML frontmatter | Markdown |
|
||||
| Has `#`, `##`, `###` headings | Markdown |
|
||||
| Has `**bold**`, `*italic*` | Markdown |
|
||||
| Has `- ` or `1. ` lists | Markdown |
|
||||
| Has ``` code blocks | Markdown |
|
||||
| Has `> ` blockquotes | Markdown |
|
||||
| None of above | Plain text |
|
||||
|
||||
**Decision Flow:**
|
||||
|
||||
┌─────────────────┬────────────────────────────────────────────────┐
|
||||
│ Content Type │ Action │
|
||||
├─────────────────┼────────────────────────────────────────────────┤
|
||||
│ Plain text │ Proceed to Step 2 (format to markdown) │
|
||||
├─────────────────┼────────────────────────────────────────────────┤
|
||||
│ Markdown │ Use AskUserQuestion to confirm optimization │
|
||||
└─────────────────┴────────────────────────────────────────────────┘
|
||||
|
||||
**If Markdown detected, ask user:**
|
||||
|
||||
```
|
||||
Detected existing markdown formatting. What would you like to do?
|
||||
|
||||
1. Optimize formatting (Recommended)
|
||||
- Add/improve frontmatter, headings, bold, lists
|
||||
- Run typography script (spacing, emphasis fixes)
|
||||
- Output: {filename}-formatted.md
|
||||
|
||||
2. Keep original formatting
|
||||
- Preserve existing markdown structure
|
||||
- Run typography script (spacing, emphasis fixes)
|
||||
- Output: {filename}-formatted.md
|
||||
|
||||
3. Typography fixes only
|
||||
- Run typography script on original file in-place
|
||||
- No copy created, modifies original file directly
|
||||
```
|
||||
|
||||
**Based on user choice:**
|
||||
- **Optimize**: Continue to Step 2-8 (full workflow)
|
||||
- **Keep original**: Skip Steps 2-5, copy file → Step 6-8 (run script on copy)
|
||||
- **Typography only**: Skip Steps 2-6, run Step 7 on original file directly
|
||||
|
||||
### Step 2: Analyze Content Structure
|
||||
|
||||
Identify:
|
||||
@@ -138,6 +187,10 @@ Examples:
|
||||
- `final.md` → `final-formatted.md`
|
||||
- `draft-v1.md` → `draft-v1-formatted.md`
|
||||
|
||||
**If user chose "Keep original formatting" (from Step 1.5):**
|
||||
- Copy original file to `{filename}-formatted.md` without modifications
|
||||
- Proceed to Step 7 for typography fixes only
|
||||
|
||||
**Backup existing file:**
|
||||
|
||||
If `{filename}-formatted.md` already exists, backup before overwriting:
|
||||
|
||||
@@ -11,6 +11,21 @@ export const CJK_CHAR_RE = new RegExp(`[${CJK_SCRIPTS}]`, "u");
|
||||
const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u;
|
||||
const WORD_CHAR_RE = /[\p{L}\p{N}]/u;
|
||||
|
||||
const CJK_PUNCT_PAIRS: Record<string, string> = {
|
||||
"“": "”",
|
||||
"‘": "’",
|
||||
"(": ")",
|
||||
"〔": "〕",
|
||||
"〖": "〗",
|
||||
"〘": "〙",
|
||||
"〚": "〛",
|
||||
"「": "」",
|
||||
"『": "』",
|
||||
"〈": "〉",
|
||||
"《": "》",
|
||||
"【": "】",
|
||||
};
|
||||
|
||||
function findInlineCodeRanges(text: string): Array<[number, number]> {
|
||||
const ranges: Array<[number, number]> = [];
|
||||
let i = 0;
|
||||
@@ -69,8 +84,120 @@ function isPunctuationOrSymbol(ch: string | undefined): boolean {
|
||||
return !!ch && PUNCT_OR_SYMBOL_RE.test(ch);
|
||||
}
|
||||
|
||||
function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
function isMatchingCjkPunct(open: string, close: string): boolean {
|
||||
return CJK_PUNCT_PAIRS[open] === close;
|
||||
}
|
||||
|
||||
function mapNonCodeSegments(
|
||||
block: string,
|
||||
mapper: (segment: string) => string
|
||||
): string {
|
||||
const codeRanges = findInlineCodeRanges(block);
|
||||
if (codeRanges.length === 0) {
|
||||
return mapper(block);
|
||||
}
|
||||
|
||||
let result = "";
|
||||
let lastIndex = 0;
|
||||
for (const [start, end] of codeRanges) {
|
||||
if (start > lastIndex) {
|
||||
result += mapper(block.slice(lastIndex, start));
|
||||
}
|
||||
result += block.slice(start, end + 1);
|
||||
lastIndex = end + 1;
|
||||
}
|
||||
if (lastIndex < block.length) {
|
||||
result += mapper(block.slice(lastIndex));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function moveCjkPunctuationOutsideEmphasis(block: string): string {
|
||||
return mapNonCodeSegments(block, (segment) => {
|
||||
const delimiterPositions: number[] = [];
|
||||
let cursor = 0;
|
||||
while (cursor < segment.length - 1) {
|
||||
if (
|
||||
segment[cursor] === "*" &&
|
||||
segment[cursor + 1] === "*" &&
|
||||
segment[cursor - 1] !== "*" &&
|
||||
segment[cursor + 2] !== "*" &&
|
||||
!isEscaped(segment, cursor)
|
||||
) {
|
||||
delimiterPositions.push(cursor);
|
||||
cursor += 2;
|
||||
continue;
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
if (delimiterPositions.length < 2) return segment;
|
||||
|
||||
const stack: number[] = [];
|
||||
const pairs: Array<{ open: number; close: number }> = [];
|
||||
for (const pos of delimiterPositions) {
|
||||
if (stack.length === 0) {
|
||||
stack.push(pos);
|
||||
} else {
|
||||
const open = stack.pop() as number;
|
||||
pairs.push({ open, close: pos });
|
||||
}
|
||||
}
|
||||
|
||||
const skip = new Set<number>();
|
||||
const insertBefore = new Map<number, string>();
|
||||
|
||||
for (const pair of pairs) {
|
||||
const openPunctPos = pair.open + 2;
|
||||
const closePunctPos = pair.close - 1;
|
||||
if (openPunctPos >= closePunctPos) continue;
|
||||
|
||||
const openPunct = segment[openPunctPos];
|
||||
const closePunct = segment[closePunctPos];
|
||||
if (!openPunct || !closePunct) continue;
|
||||
if (!CJK_OPENING_PUNCT_RE.test(openPunct)) continue;
|
||||
if (!CJK_CLOSING_PUNCT_RE.test(closePunct)) continue;
|
||||
if (!isMatchingCjkPunct(openPunct, closePunct)) continue;
|
||||
if (openPunctPos + 1 >= closePunctPos) continue;
|
||||
|
||||
const inner = segment.slice(openPunctPos + 1, closePunctPos);
|
||||
if (inner.length === 0) continue;
|
||||
|
||||
skip.add(openPunctPos);
|
||||
skip.add(closePunctPos);
|
||||
|
||||
insertBefore.set(pair.open, (insertBefore.get(pair.open) ?? "") + openPunct);
|
||||
const afterClose = pair.close + 2;
|
||||
insertBefore.set(
|
||||
afterClose,
|
||||
(insertBefore.get(afterClose) ?? "") + closePunct
|
||||
);
|
||||
}
|
||||
|
||||
if (skip.size === 0) return segment;
|
||||
|
||||
let result = "";
|
||||
for (let idx = 0; idx < segment.length; idx += 1) {
|
||||
const insert = insertBefore.get(idx);
|
||||
if (insert) {
|
||||
result += insert;
|
||||
}
|
||||
if (skip.has(idx)) {
|
||||
continue;
|
||||
}
|
||||
result += segment[idx];
|
||||
}
|
||||
const tailInsert = insertBefore.get(segment.length);
|
||||
if (tailInsert) {
|
||||
result += tailInsert;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
const normalized = moveCjkPunctuationOutsideEmphasis(block);
|
||||
const codeRanges = findInlineCodeRanges(normalized);
|
||||
let rangeIndex = 0;
|
||||
|
||||
const delimiters: Array<{
|
||||
@@ -79,7 +206,7 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
canClose: boolean;
|
||||
}> = [];
|
||||
let cursor = 0;
|
||||
while (cursor < block.length - 1) {
|
||||
while (cursor < normalized.length - 1) {
|
||||
if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) {
|
||||
if (cursor <= codeRanges[rangeIndex][1]) {
|
||||
cursor = codeRanges[rangeIndex][1] + 1;
|
||||
@@ -90,14 +217,14 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
}
|
||||
|
||||
if (
|
||||
block[cursor] === "*" &&
|
||||
block[cursor + 1] === "*" &&
|
||||
block[cursor - 1] !== "*" &&
|
||||
block[cursor + 2] !== "*" &&
|
||||
!isEscaped(block, cursor)
|
||||
normalized[cursor] === "*" &&
|
||||
normalized[cursor + 1] === "*" &&
|
||||
normalized[cursor - 1] !== "*" &&
|
||||
normalized[cursor + 2] !== "*" &&
|
||||
!isEscaped(normalized, cursor)
|
||||
) {
|
||||
const before = block[cursor - 1];
|
||||
const after = block[cursor + 2];
|
||||
const before = normalized[cursor - 1];
|
||||
const after = normalized[cursor + 2];
|
||||
const beforeIsSpace = isWhitespaceChar(before);
|
||||
const afterIsSpace = isWhitespaceChar(after);
|
||||
const beforeIsPunct = isPunctuationOrSymbol(before);
|
||||
@@ -140,12 +267,12 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
if (pairs.length === 0) return block;
|
||||
if (pairs.length === 0) return normalized;
|
||||
|
||||
const insertPositions = new Set<number>();
|
||||
for (const pair of pairs) {
|
||||
const insideLast = block[pair.close - 1];
|
||||
const afterClose = block[pair.close + 2];
|
||||
const insideLast = normalized[pair.close - 1];
|
||||
const afterClose = normalized[pair.close + 2];
|
||||
if (!afterClose) continue;
|
||||
if (
|
||||
CJK_CLOSING_PUNCT_RE.test(insideLast) &&
|
||||
@@ -155,16 +282,16 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
if (insertPositions.size === 0) return block;
|
||||
if (insertPositions.size === 0) return normalized;
|
||||
|
||||
let result = "";
|
||||
for (let idx = 0; idx < block.length; idx += 1) {
|
||||
for (let idx = 0; idx < normalized.length; idx += 1) {
|
||||
if (insertPositions.has(idx)) {
|
||||
result += " ";
|
||||
}
|
||||
result += block[idx];
|
||||
result += normalized[idx];
|
||||
}
|
||||
if (insertPositions.has(block.length)) {
|
||||
if (insertPositions.has(normalized.length)) {
|
||||
result += " ";
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -47,6 +47,27 @@ test -f "$HOME/.baoyu-skills/baoyu-markdown-to-html/EXTEND.md" && echo "user"
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 0: Pre-check (Chinese Content)
|
||||
|
||||
**Condition**: Only execute if input file contains Chinese text.
|
||||
|
||||
**Detection**:
|
||||
1. Read input markdown file
|
||||
2. Check if content contains CJK characters (Chinese/Japanese/Korean)
|
||||
3. If no CJK content → skip to Step 1
|
||||
|
||||
**Format Suggestion**:
|
||||
|
||||
If CJK content detected AND `baoyu-format-markdown` skill is available:
|
||||
|
||||
Use `AskUserQuestion` to ask whether to format first. Formatting can fix:
|
||||
- Bold markers with punctuation inside causing `**` parse failures
|
||||
- CJK/English spacing issues
|
||||
|
||||
**If user agrees**: Invoke `baoyu-format-markdown` skill to format the file, then use formatted file as input.
|
||||
|
||||
**If user declines**: Continue with original file.
|
||||
|
||||
### Step 1: Confirm Theme
|
||||
|
||||
Before converting, use AskUserQuestion to confirm the theme (unless user already specified):
|
||||
|
||||
Reference in New Issue
Block a user