From yux-blog
Upload images to Alibaba Cloud OSS and replace local paths with CDN URLs in blog articles. Use when the user wants to upload blog images to OSS — e.g., "upload to oss", "upload images to oss", "上传到oss", "上传博客图片". Supports batch upload from blog image plans or manual file upload. Automatically updates article markdown with CDN URLs after upload.
npx claudepluginhub wuyuxiangx/yux-claude-hub --plugin yux-blogThis skill is limited to using the following tools:
Upload images to Alibaba Cloud OSS via REST API. Supports two modes: automatic batch upload from a blog image plan, or manual upload of specific files.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Upload images to Alibaba Cloud OSS via REST API. Supports two modes: automatic batch upload from a blog image plan, or manual upload of specific files.
Run which curl openssl jq to confirm all tools are available. If any are missing, inform the user and stop.
Check the following required environment variables:
echo "${ALIYUN_OSS_ACCESS_KEY_ID:+set}"
echo "${ALIYUN_OSS_ACCESS_KEY_SECRET:+set}"
echo "${ALIYUN_OSS_BUCKET:+set}"
echo "${ALIYUN_OSS_ENDPOINT:+set}"
All four must be set. If any are missing, display the setup guide below and stop immediately:
Missing OSS credentials. Add them to ~/.claude/settings.json:
{
"env": {
"ALIYUN_OSS_ACCESS_KEY_ID": "LTAI5t...",
"ALIYUN_OSS_ACCESS_KEY_SECRET": "your-secret",
"ALIYUN_OSS_BUCKET": "my-blog-bucket",
"ALIYUN_OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
"ALIYUN_OSS_CDN_DOMAIN": "cdn.myblog.com" // optional
}
}
Optional variable:
ALIYUN_OSS_CDN_DOMAIN: Custom CDN domain for cleaner URLs. If not set, URLs use https://{BUCKET}.{ENDPOINT}/{ObjectName}.Parse the user's request to determine the upload mode:
| Mode | Condition | Source |
|---|---|---|
| A: Blog plan | No explicit file paths provided AND plan file(s) exist in .claude/blog-image-plans/ | Upload all images with status: "completed" from the plan |
| B: Specific files | User provides explicit file paths (e.g., "upload ./img1.png ./img2.png to oss") | Upload the specified files |
Decision logic:
.claude/blog-image-plans/ directory:
.claude/blog-image-plans/{slug}.jsonRead the selected plan file (.claude/blog-image-plans/{slug}.json) and extract file paths:
jq -r '.images[] | select(.status == "completed") | .file_path' .claude/blog-image-plans/{slug}.json
If no completed images are found, inform the user: "No completed images found in the plan. Run 'generate article images' first." and stop.
Parse the file paths from the user's message.
For each file:
Verify the file exists:
ls -la "$FILE_PATH"
If the file does not exist, warn the user and skip this file.
Detect MIME type:
file --mime-type -b "$FILE_PATH"
Expected types: image/png, image/jpeg, image/gif, image/webp, image/svg+xml.
For each validated file, perform the following:
FILENAME=$(basename "$FILE_PATH")
OBJECT_NAME="blog-images/$(date +%Y%m%d-%H%M%S)-${FILENAME}"
Generate an RFC 2822 formatted date:
DATE=$(date -u '+%a, %d %b %Y %H:%M:%S GMT')
CONTENT_TYPE=$(file --mime-type -b "$FILE_PATH")
Per OSS V1 signature spec, the StringToSign is:
PUT\n\n{Content-Type}\n{Date}\n/{Bucket}/{ObjectName}
Note: The second line (Content-MD5) is empty.
SIGNATURE=$(printf "PUT\n\n${CONTENT_TYPE}\n${DATE}\n/${ALIYUN_OSS_BUCKET}/${OBJECT_NAME}" \
| openssl dgst -sha1 -hmac "$ALIYUN_OSS_ACCESS_KEY_SECRET" -binary \
| openssl base64 -A)
HTTP_CODE=$(curl -s -o /tmp/oss-upload-response.xml -w "%{http_code}" \
-X PUT \
-T "$FILE_PATH" \
-H "Date: ${DATE}" \
-H "Content-Type: ${CONTENT_TYPE}" \
-H "Authorization: OSS ${ALIYUN_OSS_ACCESS_KEY_ID}:${SIGNATURE}" \
"https://${ALIYUN_OSS_BUCKET}.${ALIYUN_OSS_ENDPOINT}/${OBJECT_NAME}")
cat /tmp/oss-upload-response.xml
Common causes: wrong credentials, clock skew, incorrect endpoint. Show the error and stop.if [ -n "$ALIYUN_OSS_CDN_DOMAIN" ]; then
URL="https://${ALIYUN_OSS_CDN_DOMAIN}/${OBJECT_NAME}"
else
URL="https://${ALIYUN_OSS_BUCKET}.${ALIYUN_OSS_ENDPOINT}/${OBJECT_NAME}"
fi
rm -f /tmp/oss-upload-response.xml
This step only applies when uploading from the blog plan.
Read the article file from the plan's article_path:
jq -r '.article_path' .claude/blog-image-plans/{slug}.json
For each uploaded image, replace the local path in the article with the CDN URL:
 (or relative path)Match by filename — the local path in the article should correspond to the file_path in the plan.
For each uploaded image, update its entry in .claude/blog-image-plans/{slug}.json:
status to "uploaded""cdn_url" field with the public URL"uploaded_at" field with the current ISO 8601 timestampWrite the updated plan back to .claude/blog-image-plans/{slug}.json.
Write the updated article content back to the original article file.
Present a summary table:
| # | File | Size | CDN URL | Status |
|---|------|------|---------|--------|
| 1 | blog-image-1.png | 245 KB | https://cdn.example.com/blog-images/... | uploaded |
| 2 | blog-image-2.png | 182 KB | https://cdn.example.com/blog-images/... | uploaded |
For Mode A, also note: "Article updated with CDN URLs: {article_path}"
| Error | Action |
|---|---|
| Missing env vars | Show ~/.claude/settings.json setup guide, stop |
| Missing tools (curl/openssl/jq) | List missing tools, stop |
| HTTP 403 (SignatureDoesNotMatch) | "Check credentials and endpoint region", stop |
| HTTP 404 (NoSuchBucket) | "Bucket not found — verify ALIYUN_OSS_BUCKET and ALIYUN_OSS_ENDPOINT", stop |
| File not found | Skip file, warn user, continue with remaining files |
| Plan file missing (Mode A) | "No image plan found. Run 'analyze article images' first.", stop |
| No completed images in plan | "No completed images to upload. Run 'generate article images' first.", stop |
| Network error | Show curl error output, stop |