diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..69645e0 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,39 @@ +{ + "enabledPlugins": { + "skill-creator@claude-plugins-official": true, + "commit-commands@claude-plugins-official": true + }, + "permissions": { + "allow": [ + "Bash(.claude/skills/**)", + "Bash(cat *)", + "Bash(cp *)", + "Bash(curl *)", + "Bash(echo *)", + "Bash(find *)", + "Bash(git add *)", + "Bash(git commit *)", + "Bash(ls *)", + "Bash(mkdir *)", + "Bash(mv *)", + "Bash(node *)", + "Bash(npm install *)", + "Bash(pip install *)", + "Bash(pip3 install *)", + "Bash(powershell *)", + "Bash(python *)", + "Bash(python3 *)", + "Bash(sed *)", + "Edit(*)", + "Read(*)", + "WebFetch(*)", + "WebSearch(*)", + "Write(*)", + "mcp__acp__Bash", + "mcp__acp__Edit", + "mcp__acp__Write", + "mcp__web-reader__webReader", + "mcp__web-search-prime__webSearchPrime" + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 6a593ba..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(curl:*)", - "Bash(echo:*)", - "Bash(find:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(mkdir:*)", - "Bash(powershell:*)", - "Bash(sed:*)", - "WebFetch", - "WebSearch", - "mcp__acp__Bash", - "mcp__acp__Edit", - "mcp__acp__Write", - "mcp__web-reader__webReader", - "mcp__web-search-prime__webSearchPrime" - ], - "deny": [], - "ask": [] - } -} diff --git a/.claude/skills/mt-add-post/SKILL.md b/.claude/skills/mt-add-post/SKILL.md index 4981b14..56881d1 100644 --- a/.claude/skills/mt-add-post/SKILL.md +++ b/.claude/skills/mt-add-post/SKILL.md @@ -1,68 +1,75 @@ --- name: mt:add-post -description: 'URL processing workflow for Hugo blog newsletter posts. Automatically processes URLs, extracts content, generates Vietnamese summaries, and creates/updates daily newsletter posts with proper formatting.' +description: 'URL processing workflow for Hugo blog newsletter posts. Use when user wants to add URLs to their newsletter, process blog content, extract content from links, or update daily newsletter posts. Automatically processes URLs, extracts content, generates Vietnamese summaries, and creates/updates daily newsletter posts with proper numbering and formatting.' --- -### Instructions +## Overview -```xml -This skill handles the complete workflow for adding newsletter posts to a Hugo blog. It processes URLs, extracts content, generates professional Vietnamese summaries, and manages daily newsletter files with proper numbering and formatting. +This skill manages the complete workflow for adding newsletter posts to a Hugo blog with Vietnamese tech content. + +**Project Context:** +- Type: Hugo static site with `hugo-theme-stack` theme +- Language: Vietnamese (99%) with minimal English tech terms (1%) +- Timezone: Asia/Ho_Chi_Minh (UTC+7) +- Content structure: `content/post/YYYY/MM/DD/index.md` + +## Quick Workflow + +``` +1. Prepare URLs (clean → validate → check duplicates → classify) +2. Find today's post (create or update) +3. Determine newsletter number +4. Extract content and generate Vietnamese summaries +5. Write formatted content +6. Provide final report ``` -### Project Info +## Step-by-Step Instructions -- **Type**: Hugo static site with Vietnamese tech content -- **Theme**: hugo-theme-stack -- **Language**: Vietnamese (with common English tech words) -- **Timezone**: Asia/Ho_Chi_Minh (UTC+7) -- **Directory Structure**: `content/post/YYYY/MM/DD/index.md` +### 1. Prepare URLs -### Workflow +For each URL, run the bundled script: +```bash +.claude/skills/mt-add-post/scripts/prepare_url.sh "" +``` -**Follow these steps when user provides URLs:** +The script handles: +- **Clean**: Remove tracking params (`utm_*`, `fbclid`, `gclid`) +- **Validate**: Check accessibility (HTTP 200) +- **Check duplicate**: Search for exact URL in project +- **Classify**: Article (for main content) or asset (for Bonus section) -#### Step 1: Prepare URLs -For each URL: -1. **Clean**: Remove tracking params (`utm_*`, `fbclid`, `gclid`, etc.) -2. **Validate**: Check accessibility (HTTP 200) -3. **Check duplicate**: Search exact URL in project files using Grep -4. **Classify**: - - Article URL → Extract for main content - - Direct asset (`.png`, `.jpg`, `.pdf`, `.mp4`, etc.) → Bonus section -5. **Skip if**: Inaccessible, duplicate, or extraction fails +**Skip** URLs that are: inaccessible, duplicates, or fail extraction. -#### Step 2: Find Today's Post -1. Get current date: `YYYY-MM-DD` -2. Check path: `content/post/YYYY/MM/DD/index.md` -3. **If exists**: Update this file -4. **If not exists**: Create new file with incremented newsletter number +### 2. Find Today's Post -#### Step 3: Determine Newsletter Number -To find next newsletter number: -1. Start from current date folder, search backwards: - - Current month folders (DD, DD-1, DD-2...) - - Previous months (MM-1, MM-2...) - - Previous years if needed (YYYY-1, YYYY-2...) -2. Find most recent `index.md` with "Newsletter #N" -3. Increment: N + 1 +Get current date in `YYYY-MM-DD` format (UTC+7). -#### Step 4: Generate Content +Check if `content/post/YYYY/MM/DD/index.md` exists: +- **Exists** → Update this file +- **Not exists** → Create new with incremented newsletter number -**For Article URLs** - Extract and generate: -- **Title**: Original article title -- **Summary**: 1-2 paragraphs, professional Vietnamese - - Max 300 words - - Use common English tech terms only when no Vietnamese equivalent exists - - Brief intro to the topic - - Give overview for readers -- **Key points** (optional): 3-5 bullet points if relevant +### 3. Determine Newsletter Number -**For Asset URLs** - Extract: -- **Type**: Image, video, PDF, etc. -- **Title**: File name or detected title -- Add to Bonus section by type +Run the bundled script: +```bash +.claude/skills/mt-add-post/scripts/find_newsletter_number.sh +``` -#### Step 5: Write Content +This searches backwards from current date to find the most recent newsletter and returns the next number. + +### 4. Generate Content + +**For Article URLs:** +- Extract title and main content +- Generate Vietnamese summary (1-2 paragraphs, max 300 words) +- Optionally add 3-5 key points as bullets + +**For Asset URLs** (images, videos, PDFs): +- Extract type and title +- Add to Bonus section + +### 5. Write Content **New Post Template:** ```markdown @@ -79,12 +86,12 @@ categories: ["Newsletter"] [Vietnamese summary - professional, max 300 words] -**Điểm chính:** (optional) +**Điểm chính:** - [Key point 1] - [Key point 2] ``` -**Update Existing Post** - Insert new articles **before** Bonus section: +**Update Existing Post** - insert new articles **before** the Bonus section: ```markdown [Existing articles...] @@ -97,8 +104,7 @@ categories: ["Newsletter"] ![image1](url1) ``` -#### Step 6: Format Bonus Section -Group assets by type with subheadings: +**Bonus Section Format:** ```markdown ### Bonus @@ -112,84 +118,36 @@ Group assets by type with subheadings: [PDF: title](url) ``` -### Language Policy +## Language Guidelines -**PRIMARY LANGUAGE**: Vietnamese (99%) -**SECONDARY LANGUAGE**: English (1% - only for unavoidable technical terms) +**Primary**: Vietnamese (99%) | **Secondary**: English (1% - unavoidable technical terms only) -**English Terms Allowed (NO VIETNAMESE EQUIVALENT)**: -- API, GitHub, AI, Rust, Go, HTTP, JSON, SQL, CI/CD, Docker, Kubernetes -- Technology names: PostgreSQL, MongoDB, Redis, etc. -- Company names: Databricks, Snowflake, Google, Microsoft, etc. -- Product names: Neon, CrunchyData, etc. -- Acronyms with no direct translation: MCP, ETL, OLAP, etc. +**Allow English when no Vietnamese equivalent exists:** +- Technology names: AI, API, GitHub, Rust, Go, Docker, Kubernetes +- Database names: PostgreSQL, MongoDB, Redis +- Company names: Google, Microsoft, Databricks +- Acronyms: MCP, CI/CD, JSON, SQL, HTTP -**TRANSLATE EVERYTHING TO VIETNAMESE**: -- error handling → xử lý lỗi -- dependency → phụ thuộc -- performance → hiệu năng -- deployment → triển khai -- scalability → khả năng mở rộng -- database → cơ sở dữ liệu -- testing → kiểm thử -- framework → khung làm việc -- developer → nhà phát triển -- engineer → kỹ sư -- code → mã nguồn -- software → phần mềm -- application → ứng dụng -- algorithm → thuật toán -- architecture → kiến trúc -- feature → tính năng -- bug → lỗi -- security → bảo mật -- authentication → xác thực -- authorization → phân quyền -- integration → tích hợp -- interface → giao diện -- monitoring → giám sát -- optimization → tối ưu hóa -- server → máy chủ -- client → máy khách +**Always translate to Vietnamese:** +- Common terms: code → mã nguồn, software → phần mềm, deployment → triển khai, performance → hiệu năng +- Action verbs: use → sử dụng, check → kiểm tra, build → xây dựng, test → kiểm thử +- For any term not listed above: translate if a common Vietnamese equivalent exists -**Action Verbs (ALWAYS USE VIETNAMESE)**: -- use → sử dụng -- make sure → đảm bảo -- check → kiểm tra -- run → chạy -- build → xây dựng -- deploy → triển khai -- test → kiểm thử -- debug → gỡ lỗi -- monitor → giám sát -- analyze → phân tích -- optimize → tối ưu hóa -- configure → cấu hình -- install → cài đặt -- update → cập nhật -- create → tạo -- delete → xóa -- manage → quản lý +**Content Requirements:** +- Audience: Junior developers +- Tone: Professional, clear, accessible +- Verify content is ≥99% Vietnamese before saving -**Content Requirements**: -- **Audience**: Junior developers -- **Tone**: Professional, clear, accessible, natural Vietnamese -- **Length**: Max 300 words per summary -- **Quality Check**: Verify before saving that Vietnamese content is ≥ 99% - -### Error Handling +## Error Handling | Issue | Action | |-------|--------| -| URL inaccessible | Skip, include in report | -| Duplicate URL found | Skip, include in report | -| Content extraction fails | Skip, treat as error | -| Directory not exist | Create directories | -| Summary too short | Accept (no minimum length) | +| URL inaccessible | Skip, note in report | +| Duplicate URL | Skip, note in report | +| Extraction fails | Skip, treat as error | +| Directory missing | Create directories | -### Final Report Format - -After processing, provide: +## Final Report ``` ✅ Newsletter Processing Complete @@ -205,28 +163,13 @@ After processing, provide: - [url]: [reason] ``` -### Best Practices +## Checklist -1. **Always verify** file paths exist before writing -2. **Preserve** existing content when updating -3. **Use exact URLs** for duplicate checking -4. **Generate quality summaries** using AI tools -5. **Handle errors gracefully** and report clearly -6. **Group Bonus assets** by type with subheadings -7. **Keep front matter minimal** (tags added separately) -8. **Use relative paths** for internal links -9. **Validate markdown** before saving -10. **Process all URLs** in one batch per request - -### Quality Checklist - -- [ ] Valid Hugo front matter +Before finalizing, verify: +- [ ] Valid Hugo front matter with correct date format - [ ] Title: `"Newsletter #[number]"` -- [ ] Date: `YYYY-MM-DD` format -- [ ] Tags: Include `"AI-Assisted"` -- [ ] Clean URLs (no tracking params) -- [ ] Correct Vietnamese grammar -- [ ] Proper markdown syntax -- [ ] Articles before Bonus section +- [ ] Tags include `"AI-Assisted"` +- [ ] URLs are clean (no tracking params) +- [ ] Vietnamese grammar is correct +- [ ] Articles appear before Bonus section - [ ] Bonus assets grouped by type -- [ ] Vietnamese content ≥ 99% diff --git a/.claude/skills/mt-add-post/scripts/find_newsletter_number.sh b/.claude/skills/mt-add-post/scripts/find_newsletter_number.sh new file mode 100644 index 0000000..652c073 --- /dev/null +++ b/.claude/skills/mt-add-post/scripts/find_newsletter_number.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Find the most recent newsletter number and return the next one +# Usage: find_newsletter_number.sh +# Outputs: The next newsletter number + +set -euo pipefail + +# Get project root +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +CONTENT_DIR="$PROJECT_ROOT/content/post" + +# Get current date in YYYY/MM/DD format +CURRENT_YEAR=$(date +%Y) +CURRENT_MONTH=$(date +%m) +CURRENT_DAY=$(date +%d) + +# Function to find newsletter number in a file +extract_newsletter_number() { + local file="$1" + # Look for "Newsletter #N" in front matter title or content + grep -oP 'Newsletter\s*#\K\d+' "$file" 2>/dev/null | head -1 || echo "0" +} + +# Function to search for newsletters in date order +find_most_recent_newsletter() { + local max_number=0 + + # Current month, going backwards by day + for day in {31..1}; do + local day_dir=$(printf "%02d" "$day") + local check_path="$CONTENT_DIR/$CURRENT_YEAR/$CURRENT_MONTH/$day_dir/index.md" + if [[ -f "$check_path" ]]; then + local num=$(extract_newsletter_number "$check_path") + if ((num > max_number)); then + max_number=$num + fi + fi + done + + # Previous months in current year + for month in {12..1}; do + if [[ $month -ge $CURRENT_MONTH ]]; then + continue + fi + local month_dir=$(printf "%02d" "$month") + for day in {31..1}; do + local day_dir=$(printf "%02d" "$day") + local check_path="$CONTENT_DIR/$CURRENT_YEAR/$month_dir/$day_dir/index.md" + if [[ -f "$check_path" ]]; then + local num=$(extract_newsletter_number "$check_path") + if ((num > max_number)); then + max_number=$num + fi + fi + done + done + + # Previous years (check last 5 years) + for year in $(seq $((CURRENT_YEAR - 1)) -1 $((CURRENT_YEAR - 5))); do + for month in {12..1}; do + local month_dir=$(printf "%02d" "$month") + for day in {31..1}; do + local day_dir=$(printf "%02d" "$day") + local check_path="$CONTENT_DIR/$year/$month_dir/$day_dir/index.md" + if [[ -f "$check_path" ]]; then + local num=$(extract_newsletter_number "$check_path") + if ((num > max_number)); then + max_number=$num + fi + fi + done + done + done + + echo $max_number +} + +# Find most recent and increment +MOST_RECENT=$(find_most_recent_newsletter) +NEXT_NUMBER=$((MOST_RECENT + 1)) + +echo "$NEXT_NUMBER" diff --git a/.claude/skills/mt-add-post/scripts/prepare_url.sh b/.claude/skills/mt-add-post/scripts/prepare_url.sh new file mode 100644 index 0000000..b299e15 --- /dev/null +++ b/.claude/skills/mt-add-post/scripts/prepare_url.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# URL preparation script for miti-add-post skill +# Usage: prepare_url.sh "" +# Outputs: JSON with status, clean_url, type, accessible, duplicate + +set -euo pipefail + +URL="$1" + +# Remove tracking parameters +clean_url() { + local url="$1" + # Remove common tracking parameters + url=$(echo "$url" | sed -E 's/[?&](utm_[^&]*|fbclid|gclid|msclkid|mc_eid)[^&]*//g') + # Clean up trailing ? or & + url=$(echo "$url" | sed -E 's/[?&]$//') + echo "$url" +} + +# Check if URL is accessible (returns HTTP status code) +check_accessibility() { + local url="$1" + # Use curl to get HTTP status only + curl -s -o /dev/null -w "%{http_code}" -L --max-time 10 "$url" 2>/dev/null || echo "000" +} + +# Check if URL is duplicate in project +check_duplicate() { + local url="$1" + local project_root="$2" + if grep -rF "$url" "$project_root/content" 2>/dev/null; then + echo "true" + else + echo "false" + fi +} + +# Classify URL type +classify_url() { + local url="$1" + local lower_url=$(echo "$url" | tr '[:upper:]' '[:lower:]') + + # Check for direct assets + if echo "$lower_url" | grep -qE '\.(png|jpg|jpeg|gif|webp|svg)(\?.*)?$'; then + echo "image" + elif echo "$lower_url" | grep -qE '\.(mp4|webm|mov|avi)(\?.*)?$'; then + echo "video" + elif echo "$lower_url" | grep -qE '\.(pdf|doc|docx|xls|xlsx)(\?.*)?$'; then + echo "document" + else + echo "article" + fi +} + +# Main execution +CLEAN_URL=$(clean_url "$URL") +HTTP_STATUS=$(check_accessibility "$CLEAN_URL") +IS_ACCESSIBLE="false" +if [ "$HTTP_STATUS" = "200" ]; then + IS_ACCESSIBLE="true" +fi + +# Get project root (assumes we're in .claude/skills/miti-add-post/scripts) +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +IS_DUPLICATE=$(check_duplicate "$CLEAN_URL" "$PROJECT_ROOT") +URL_TYPE=$(classify_url "$CLEAN_URL") + +# Output JSON +cat <