Compare commits

...

10 Commits

Author SHA1 Message Date
Misaka Company
6350f7a92a Add Gitea repository management skill
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 23:18:39 +08:00
Misaka Company
cb8f57c12b Add worksheet selection to excel-to-markdown skill
Add --sheet parameter to specify worksheets by name or index (1-based). Change default behavior to use first worksheet instead of active sheet, with proper error messages for invalid worksheet references.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-29 10:10:57 +08:00
Misaka Company
ae8642cdfe Refactor rename logic into reusable function
Extract duplicate rename code from --rename option and interactive 'r' branch into find_unique_filename() function. Reduces code duplication and improves maintainability.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 14:36:23 +08:00
Misaka Company
45c8623666 Add mutual exclusion validation for conflict handling options
Ensure --force, --skip, --backup, --rename options are used exclusively
to prevent ambiguous conflict resolution behavior.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 14:05:32 +08:00
Misaka Company
51eae214cc Add file conflict handling to minio-upload skill
Implement comprehensive file conflict detection and resolution:
- Check for existing files before upload using mc stat (fixes mc ls bug)
- Interactive prompt when conflict detected: overwrite, skip, backup, rename
- Command-line options for non-interactive use: --force, --skip, --backup, --rename
- Auto-rename with numeric suffix when file exists
- Backup existing files with .bak extension before overwriting
- Update SKILL.md with conflict handling documentation and examples

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 13:45:43 +08:00
Misaka Company
cbe3cfccba Clarify minio-upload skill as Obsidian vault upload tool
Update documentation to reflect that the obsidian bucket serves as sync directory for Obsidian note-taking app. Replace generic "path" terminology with "vault" concept, explaining that each top-level directory is a separate Obsidian vault. Add vault structure explanation and update examples to show vault-specific uploads.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 12:57:57 +08:00
Misaka Company
16a7619ed0 Emphasize path configuration rules in minio-upload skill documentation
Add prominent Path Configuration section that explicitly states files must upload to main path when no path is specified. Includes constraints and examples to prevent path assumptions.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 12:30:49 +08:00
Misaka Company
5f9d79fbb0 Add minio-upload skill and update README.md to match actual project structure 2026-01-28 09:20:17 +08:00
Misaka Company
bdd2df7e75 Optimize excel-report-converter verification for large files
Add guidance to verify only first 10 records when validating large export files, avoiding processing time and memory issues. Also add temp/ to .gitignore.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-27 17:48:38 +08:00
Misaka Company
06c4f8eafa Reorganize project structure: move scripts to demo_scripts
- Rename scripts/ directory to demo_scripts/ for clarity
- Move excel_to_markdown.py to demo_scripts/ directory
- This better reflects the purpose of these scripts as demonstration examples

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-27 15:06:01 +08:00
9 changed files with 502 additions and 23 deletions

3
.gitignore vendored
View File

@@ -41,3 +41,6 @@ Thumbs.db
# .claude
.claude/
#temp files
temp/

View File

@@ -10,28 +10,36 @@
```
claudeskill/
├── .claude/ # Claude 配置目录
│ └── skills/ # Skill 创建器引用
├── .claude/ # Claude 配置目录(已安装的 Skills
│ └── skills/ # Skills 创建器引用
│ ├── skill-creator/ # Skill 创建指南和工具
│ ├── excel-to-markdown/ # Excel 转 Markdown Skill
── excel-report-converter/ # Excel 报表转换 Skill
├── scripts/ # 独立脚本目录
│ ├── excel_to_markdown.py # Excel 转 Markdown 转换器
│ └── report_to_db_format.py # 报表格式转数据库格式脚本
├── skills/ # 开发中的 Skills
── excel-report-converter/ # Excel 报表转换器
── excel-report-converter/ # Excel 报表转换 Skill
│ └── minio-upload/ # MinIO 文件上传 Skill
├── demo_scripts/ # 演示脚本目录
│ └── excel_to_markdown.py # Excel 转 Markdown 转换器
├── skills/ # 开发中的 Skills(用于测试和版本控制)
── excel-to-markdown/ # Excel 转 Markdown Skill
│ ├── excel-report-converter/ # Excel 报表转换 Skill
│ └── minio-upload/ # MinIO 文件上传 Skill
├── temp/ # 临时文件目录
├── .venv/ # Python 虚拟环境
├── CLAUDE.md # Claude Code 项目指南
└── README.md # 本文档
```
**说明:**
- `.claude/skills/` - 已安装的 Skills由 Claude Code 自动加载(被 gitignore
- `skills/` - Skills 的开发版本,用于版本控制和协作
- `demo_scripts/` - 可独立运行的脚本文件
## 目录说明
### `scripts/`
### `demo_scripts/`
存放可独立使用的脚本文件,这些脚本可以被 Skills 引用。
**当前脚本:**
- `excel_to_markdown.py` - 将 Excel 文件转换为 Markdown 表格格式
- `report_to_db_format.py` - 将报表格式的 Excel 文件转换为数据库记录格式
### `skills/`
存放已创建的 Skills每个 Skill 都是一个独立的功能模块。
@@ -69,6 +77,31 @@ claudeskill/
- 创建自定义的 Excel 报表转换脚本
- 将层级报表数据(表头 + 明细行 + 表尾)转换为规范化数据库记录
#### minio-upload
上传文件到 MinIO obsidian 桶的 Skill支持灵活的路径配置。
**功能:**
- 上传任意文件到 MinIO 的 obsidian 桶
- 支持自定义子路径(如 `notes/2024`, `archive/projects`
- 默认上传到 `main` 路径
- 自动处理路径规范化和文件名附加
- 完善的错误处理和验证
**使用场景:**
- 快速上传 Markdown 文档到云存储
- 按年份/分类组织文件
- 备份文件到 MinIO
- 批量上传文件到指定路径
**示例:**
```bash
# 上传到默认 main 路径
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh file.md
# 上传到自定义子路径
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh file.md "notes/2024"
```
## 工作流程
### 1. 创建新 Skill
@@ -100,7 +133,7 @@ python3 .claude/skills/skill-creator/scripts/init_skill.py <skill-name> --path s
```bash
# 测试 Excel 转 Markdown
python3 scripts/excel_to_markdown.py demo.xlsx -o demo.md --show-rows --show-cols
python3 demo_scripts/excel_to_markdown.py demo.xlsx -o demo.md --show-rows --show-cols
```
### 4. 打包 Skill(当用户主动要求才打包)
@@ -164,7 +197,7 @@ python3 .claude/skills/skill-creator/scripts/init_skill.py excel-to-markdown --p
### 步骤 2添加脚本
```bash
cp excel_to_markdown.py skills/excel-to-markdown/scripts/
cp demo_scripts/excel_to_markdown.py skills/excel-to-markdown/scripts/
```
### 步骤 3编写 SKILL.md

View File

@@ -152,7 +152,8 @@ python3 scripts/custom_converter.py input.xlsx output.xlsx
Use `excel-to-markdown` to verify the output:
```bash
python3 scripts/excel_to_markdown.py output.xlsx -o /tmp/verify.md --show-rows --show-cols
# For large files, only verify first 10 records
python3 scripts/excel_to_markdown.py output.xlsx -o /tmp/verify.md --show-rows --show-cols --rows 1:11
```
Read `/tmp/verify.md` and verify:
@@ -162,6 +163,8 @@ Read `/tmp/verify.md` and verify:
4. Footer fields are correctly populated
5. No data loss or corruption
**Important:** For large output files with many records, use `--rows 1:11` to only convert and verify the first 10 data records (plus header row). This avoids processing time and memory issues when verifying large exports.
### Step 4: Iterate if Needed
If verification reveals issues:

View File

@@ -19,6 +19,18 @@ Convert an entire Excel file to Markdown:
python3 scripts/excel_to_markdown.py input.xlsx -o output.md
```
### Specify Worksheet
Convert a specific worksheet by name or index:
```bash
# By worksheet name
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --sheet "Sheet2"
# By worksheet index (1-based)
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --sheet 2
```
### Convert with Row/Column Numbers
Display Excel row and column identifiers for reference:
@@ -51,6 +63,7 @@ python3 scripts/excel_to_markdown.py input.xlsx -o output.md --start-row 2 --sta
| `--show-cols` | Display column letters (A, B, C...) in the first row |
| `--start-row` | Starting row number (default: 1) |
| `--start-col` | Starting column number (default: 1) |
| `--sheet` | Worksheet name or index (default: first worksheet) |
## Features
@@ -67,7 +80,12 @@ The converter automatically scans the spreadsheet to identify the actual data ra
### Multi-Sheet Support
By default, the converter processes the active (last selected) sheet in the workbook.
By default, the converter processes the first worksheet in the workbook. You can specify a different worksheet using the `--sheet` parameter:
- **By name**: `--sheet "Sheet2"` or `--sheet "Data"`
- **By index**: `--sheet 2` (1-based indexing)
If the specified worksheet is not found, an error will display the list of available worksheets.
## Common Use Cases

View File

@@ -14,15 +14,34 @@ import argparse
class ExcelToMarkdown:
"""Excel转Markdown转换器"""
def __init__(self, excel_file: str):
def __init__(self, excel_file: str, sheet_name: Optional[str] = None):
"""
初始化转换器
Args:
excel_file: Excel文件路径
sheet_name: 工作表名称或索引None表示使用第一个工作表
"""
self.workbook = openpyxl.load_workbook(excel_file, data_only=True)
self.sheet = self.workbook.active
# 获取指定的工作表
if sheet_name is None:
# 使用第一个工作表
self.sheet = self.workbook.worksheets[0]
elif isinstance(sheet_name, int) or sheet_name.isdigit():
# 按索引获取从1开始
sheet_index = int(sheet_name) - 1
if 0 <= sheet_index < len(self.workbook.worksheets):
self.sheet = self.workbook.worksheets[sheet_index]
else:
raise ValueError(f"工作表索引 {sheet_name} 超出范围1-{len(self.workbook.worksheets)}")
else:
# 按名称获取
if sheet_name in self.workbook.sheetnames:
self.sheet = self.workbook[sheet_name]
else:
available_sheets = ", ".join(self.workbook.sheetnames)
raise ValueError(f"找不到工作表 '{sheet_name}',可用的工作表: {available_sheets}")
def detect_data_range(self, max_scan_rows: int = 100, max_scan_cols: int = 100) -> Tuple[int, int]:
"""
@@ -172,6 +191,12 @@ def main():
# 从第2行第2列开始转换5行3列
python excel_to_markdown.py input.xlsx -o output.md -r 5 -c 3 --start-row 2 --start-col 2
# 指定工作表(按名称)
python excel_to_markdown.py input.xlsx -o output.md --sheet "Sheet2"
# 指定工作表(按索引)
python excel_to_markdown.py input.xlsx -o output.md --sheet 2
"""
)
@@ -183,12 +208,13 @@ def main():
parser.add_argument('--show-cols', action='store_true', help='显示列号(字母形式)')
parser.add_argument('--start-row', type=int, default=1, help='起始行号默认1')
parser.add_argument('--start-col', type=int, default=1, help='起始列号默认1')
parser.add_argument('--sheet', help='工作表名称或索引(不指定则使用第一个工作表)')
args = parser.parse_args()
try:
# 创建转换器
converter = ExcelToMarkdown(args.input_file)
converter = ExcelToMarkdown(args.input_file, args.sheet)
# 执行转换
markdown_text = converter.convert(

80
skills/gitea/SKILL.md Normal file
View File

@@ -0,0 +1,80 @@
---
name: gitea
description: Gitea repository management via REST API. Use when user explicitly mentions "gitea" for: (1) Creating repositories, (2) Listing repositories, (3) Deleting repositories, (4) Pushing code to Gitea. Default server: https://gitea.server10086.icu, default account: admin. Requires GITEA_TOKEN environment variable.
---
# Gitea Repository Management
## Configuration
- **Server**: `https://gitea.server10086.icu`
- **API Base**: `https://gitea.server10086.icu/api/v1`
- **Default Account**: `admin`
- **Auth Header**: `Authorization: token $GITEA_TOKEN`
**Always verify GITEA_TOKEN exists before any operation:**
```bash
if [ -z "$GITEA_TOKEN" ]; then
echo "Error: GITEA_TOKEN environment variable not set"
exit 1
fi
```
## Repository Operations
### List Repositories
```bash
# List all repos for current user
curl -s -X GET "https://gitea.server10086.icu/api/v1/user/repos" \
-H "Authorization: token $GITEA_TOKEN"
```
### Create Repository
```bash
curl -X POST "https://gitea.server10086.icu/api/v1/admin/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "repo-name",
"description": "Repository description",
"private": false,
"auto_init": false
}'
```
### Delete Repository
```bash
curl -X DELETE "https://gitea.server10086.icu/api/v1/repos/admin/repo-name" \
-H "Authorization: token $GITEA_TOKEN"
```
### Create and Push Current Directory
```bash
REPO_NAME=$(basename $(pwd))
# Create repo via API
curl -X POST "https://gitea.server10086.icu/api/v1/admin/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$REPO_NAME\",\"auto_init\":false}"
# Initialize and push
git init
git add .
git commit -m "Initial commit"
git remote add origin "https://gitea.server10086.icu/admin/$REPO_NAME.git"
git push -u origin main
```
## Error Handling
| Status | Meaning |
|--------|---------|
| 401 | Invalid or missing GITEA_TOKEN |
| 409 | Repository already exists |
| 404 | Repository not found |
| 204 | Successful deletion (expected) |

View File

@@ -0,0 +1,118 @@
---
name: minio-upload
description: Upload files to Obsidian vaults via MinIO obsidian bucket. Use when user asks to upload files to Obsidian, transfer notes to vaults, sync markdown files to Obsidian, or specify a particular vault destination. The obsidian bucket serves as the sync directory for Obsidian note-taking app.
---
# MinIO Upload
Upload files to Obsidian vaults through the MinIO obsidian bucket. This bucket serves as the synchronization directory for the Obsidian note-taking application, where each top-level directory represents a separate vault.
## Quick Start
Upload a file to the default `main` vault:
```bash
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh path/to/file.md
```
Upload to a specific vault:
```bash
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh path/to/file.md "journal"
```
## File Conflict Handling
**CRITICAL**: When a file with the same name already exists in the target vault, the script will prompt you to choose an action (unless an option is specified).
### Interactive Prompt (Default)
If the file exists and no option is provided, you'll see:
```
⚠️ File 'filename.md' already exists in vault 'main'
Choose an action:
[o] Overwrite - Replace existing file
[s] Skip - Cancel upload
[b] Backup - Backup existing file (.bak) then overwrite
[r] Rename - Upload with new name (adds numeric suffix)
```
### Command-Line Options
You can specify the behavior explicitly to skip the prompt:
- `--force` - Overwrite existing files without prompting
- `--skip` - Skip upload if file already exists
- `--backup` - Backup existing file (creates `.bak` file) before overwriting
- `--rename` - Automatically rename with numeric suffix (e.g., `file-1.md`, `file-2.md`)
### Examples
```bash
# Force overwrite without prompting
upload_to_obsidian.sh file.md main --force
# Skip if file exists
upload_to_obsidian.sh file.md journal --skip
# Backup before overwriting
upload_to_obsidian.sh file.md work --backup
# Auto-rename if conflict
upload_to_obsidian.sh file.md personal --rename
```
## Vault Configuration
**CRITICAL RULE**: When no vault is explicitly specified by the user, files MUST be uploaded to the `main` vault only. Do NOT assume or invent other vault names.
### Understanding Vault Structure
The MinIO obsidian bucket contains Obsidian vaults as top-level directories:
- **Bucket root**: `minio/obsidian/`
- **Vaults**: Each top-level directory is a separate Obsidian vault (e.g., `main/`, `journal/`, `work/`)
- **Files uploaded to**: `minio/obsidian/{vault-name}/{filename}`
### Vault Selection Behavior
- **No vault specified**: Upload to `main` vault (DEFAULT - use this unless user explicitly requests otherwise)
- **Specific vault specified**: Use the exact vault name provided by the user (e.g., `journal`, `work-notes`, `personal`)
- **Nested directories**: If a vault name contains slashes, it will be normalized (e.g., `notes/journal` becomes `notes/journal/`)
### Important Constraints
1. **Never assume vaults**: If the user doesn't specify a vault, always use `main`
2. **User intent only**: Only use non-default vaults when the user explicitly names them
3. **Vault validation**: The script automatically normalizes paths and appends the filename
### Examples
- User says "upload file.md" → Upload to `main` vault
- User says "upload to my journal vault" → Upload to `journal` vault
- User says "upload to work-notes" → Upload to `work-notes` vault
## Workflow
1. **Verify file exists** - Check that the source file is accessible
2. **Determine target vault** - Use specified vault name or default to `main`
3. **Check for conflicts** - Detect if file already exists in target vault
4. **Resolve conflict** - Prompt user or use specified option to handle conflict
5. **Upload with mc** - Execute `mc cp` command to transfer file
6. **Confirm success** - Verify upload completed and report target location
## Error Handling
The script will:
- Exit with error if file doesn't exist
- Exit with error if no file argument provided
- Propagate any mc command failures
## Resources
### scripts/upload_to_obsidian.sh
Bash script that handles the upload process. Validates inputs, constructs the target vault path, and executes the mc copy command.
**Key features:**
- Default to `main` vault when no vault name specified
- Support vault names with proper path normalization
- Clear error messages for invalid inputs
- Success confirmation with source and target paths

View File

@@ -0,0 +1,198 @@
#!/bin/bash
#
# Upload files to MinIO obsidian bucket
# Usage: upload_to_obsidian.sh <file_path> [subpath] [options]
#
# Arguments:
# file_path - Path to the file to upload
# subpath - Optional subpath/vault within obsidian bucket (default: main)
#
# Options:
# --force - Overwrite existing files without prompting
# --skip - Skip upload if file already exists
# --backup - Backup existing file before overwriting (.bak extension)
# --rename - Automatically rename if file exists (adds numeric suffix)
#
# Examples:
# upload_to_obsidian.sh temp/file.md
# upload_to_obsidian.sh temp/file.md journal
# upload_to_obsidian.sh temp/file.md main --force
# upload_to_obsidian.sh temp/file.md work --skip
set -e
# Initialize variables
FORCE=false
SKIP=false
BACKUP=false
RENAME=false
FILE_PATH=""
SUBPATH="main"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--force)
FORCE=true
shift
;;
--skip)
SKIP=true
shift
;;
--backup)
BACKUP=true
shift
;;
--rename)
RENAME=true
shift
;;
-*)
echo "Error: Unknown option '$1'" >&2
echo "Usage: $0 <file_path> [subpath] [--force|--skip|--backup|--rename]" >&2
exit 1
;;
*)
if [ -z "$FILE_PATH" ]; then
FILE_PATH="$1"
elif [ "$SUBPATH" = "main" ]; then
SUBPATH="$1"
else
echo "Error: Too many arguments" >&2
exit 1
fi
shift
;;
esac
done
# Validate mutual exclusion of conflict handling options
CONFLICT_OPTS_COUNT=0
[ "$FORCE" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
[ "$SKIP" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
[ "$BACKUP" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
[ "$RENAME" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
if [ $CONFLICT_OPTS_COUNT -gt 1 ]; then
echo "Error: Conflict handling options are mutually exclusive." >&2
echo "Use only one of: --force, --skip, --backup, --rename" >&2
exit 1
fi
# Check file path argument
if [ -z "$FILE_PATH" ]; then
echo "Usage: $0 <file_path> [subpath] [--force|--skip|--backup|--rename]" >&2
exit 1
fi
# Validate file exists
if [ ! -f "$FILE_PATH" ]; then
echo "Error: File '$FILE_PATH' does not exist" >&2
exit 1
fi
# Get filename
FILENAME=$(basename "$FILE_PATH")
# Function to find unique filename by adding numeric suffix
# Sets global variables: TARGET_PATH, FILENAME
find_unique_filename() {
local original_filename="$1"
local subpath="$2"
local counter=1
local base_name="${original_filename%.*}"
local ext="${original_filename##*.}"
# Handle files without extension
if [ "$base_name" = "$ext" ]; then
base_name="$original_filename"
ext=""
else
ext=".$ext"
fi
while true; do
local new_name="${base_name}-${counter}${ext}"
local new_target="minio/obsidian/${subpath}/${new_name}"
if ! mc stat "$new_target" >/dev/null 2>&1; then
TARGET_PATH="$new_target"
FILENAME="$new_name"
echo "📝 Renamed to: $FILENAME"
return 0
fi
counter=$((counter + 1))
done
}
# Construct target path
TARGET_PATH="minio/obsidian/${SUBPATH}/${FILENAME}"
# Ensure subpath ends without trailing slash for mc
TARGET_PATH=$(echo "$TARGET_PATH" | sed 's://*:/:g')
# Check if file already exists
if mc stat "$TARGET_PATH" >/dev/null 2>&1; then
# User explicitly specified --skip
if [ "$SKIP" = true ]; then
echo " File already exists, skipping upload: $TARGET_PATH"
exit 0
fi
# User explicitly specified --backup
if [ "$BACKUP" = true ]; then
BACKUP_PATH="${TARGET_PATH}.bak"
echo "📦 Backing up existing file: $TARGET_PATH -> $BACKUP_PATH"
mc cp "$TARGET_PATH" "$BACKUP_PATH"
# User explicitly specified --rename
elif [ "$RENAME" = true ]; then
find_unique_filename "$FILENAME" "$SUBPATH"
# User explicitly specified --force
elif [ "$FORCE" = true ]; then
echo "⚠️ Overwriting existing file: $TARGET_PATH"
else
# No explicit option - prompt user
echo ""
echo "⚠️ File '$FILENAME' already exists in vault '${SUBPATH}'"
echo "Target: $TARGET_PATH"
echo ""
echo "Choose an action:"
echo " [o] Overwrite - Replace existing file"
echo " [s] Skip - Cancel upload"
echo " [b] Backup - Backup existing file (.bak) then overwrite"
echo " [r] Rename - Upload with new name (adds numeric suffix)"
echo ""
while true; do
read -p "Your choice [o/s/b/r]: " choice
case "$choice" in
o|O|overwrite)
echo "Overwriting existing file..."
break
;;
s|S|skip)
echo "❌ Upload cancelled"
exit 0
;;
b|B|backup)
BACKUP_PATH="${TARGET_PATH}.bak"
echo "📦 Backing up existing file: $TARGET_PATH -> $BACKUP_PATH"
mc cp "$TARGET_PATH" "$BACKUP_PATH"
break
;;
r|R|rename)
find_unique_filename "$FILENAME" "$SUBPATH"
break
;;
*)
echo "Invalid choice. Please enter o, s, b, or r"
;;
esac
done
fi
fi
# Upload file
mc cp "$FILE_PATH" "$TARGET_PATH"
echo "✅ Uploaded: $FILE_PATH -> $TARGET_PATH"