Skill: hoyt-tian/tencent-cos-uploader (version 0.1.0)

--- name: tencent-cos-uploader description: 使用腾讯云 COS Python SDK 上传指定本地文件到目标 Bucket，并生成可用于查看和下载的预签名访问链接。用户提到 COS、对象存储、Bucket、上传文件、临时分享链接、签名 URL、下载直链时使用此 skill。 --- # Tencent COS Uploader ## Overview Use Tencent COS Python SDK to upload a local file and return presigned URLs for browser preview and file download. ## Prerequisites 1. Install dependency: ```bash pip install cos-python-sdk-v5 ``` 2. Prepare COS credentials (env vars preferred): - `COS_SECRET_ID` - `COS_SECRET_KEY` - `COS_REGION` - `COS_BUCKET` - `COS_SESSION_TOKEN` (optional, for temporary credentials) ## Workflow 1. Confirm required inputs: - `region` (from `--region` or env) - `bucket` (from `--bucket` or env) - `--file` local file path - `--key` object key in bucket (default is filename) 2. Run the script: ```bash python3 scripts/cos_upload_and_presign.py \ --region ap-guangzhou \ --bucket my-bucket-1250000000 \ --file /absolute/path/to/file.pdf \ --key reports/2026/file.pdf \ --expires 3600 ``` 3. Return JSON output containing: - `view_url`: GET presigned URL for viewing - `download_url`: presigned URL with `attachment` disposition - `upload_result`: SDK upload response ## Script Parameters - `--region` COS region, optional if `COS_REGION` exists - `--bucket` bucket name with appid suffix, optional if `COS_BUCKET` exists - `--file` local file path to upload, required - `--key` object key in bucket, optional - `--expires` URL expiration seconds, default `3600` - `--secret-id` optional if `COS_SECRET_ID` exists - `--secret-key` optional if `COS_SECRET_KEY` exists - `--session-token` optional if `COS_SESSION_TOKEN` exists - `--scheme` `https` or `http`, default `https` - `--download-filename` custom filename for download prompt (optional) ## Notes - Keep `expires` as short as practical to reduce link leakage risk. - `download_url` is generated via `get_presigned_download_url`; `view_url` is generated via `get_presigned_url(Method='GET')`. - If `--key` is omitted, the script uses the source filename as object key.

path: agents/openai.yaml
content: interface:
  display_name: "Tencent COS Uploader"
  short_description: "Upload files to Tencent COS and create signed links"
  default_prompt: "上传指定文件到腾讯云 COS 桶，并返回可查看/下载的预签名链接。"


path: scripts/cos_upload_and_presign.py
content: #!/usr/bin/env python3
"""Upload a file to Tencent COS and generate presigned access links."""

import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import quote

try:
    from qcloud_cos import CosConfig
    from qcloud_cos import CosS3Client
    from qcloud_cos.cos_exception import CosClientError
    from qcloud_cos.cos_exception import CosServiceError
    _IMPORT_ERROR = None
except Exception as exc:  # noqa: BLE001
    CosConfig = None
    CosS3Client = None
    CosClientError = Exception
    CosServiceError = Exception
    _IMPORT_ERROR = exc


def _pick_value(cli_value: str | None, env_names: list[str], field_name: str, required: bool) -> str:
    if cli_value:
        return cli_value
    for env_name in env_names:
        value = os.getenv(env_name)
        if value:
            return value
    if required:
        joined = ", ".join(env_names)
        raise ValueError(f"Missing {field_name}. Set CLI arg or env var: {joined}")
    return ""


def _normalize_key(path: Path, key: str | None) -> str:
    if key:
        normalized = key.strip().replace("\\", "/").lstrip("/")
        if normalized:
            return normalized
    return path.name


def _content_disposition(disposition_type: str, filename: str) -> str:
    encoded = quote(filename, safe="")
    return f"{disposition_type}; filename=\"{filename}\"; filename*=UTF-8''{encoded}"


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Upload local file to Tencent COS and generate presigned view/download URLs."
    )
    parser.add_argument(
        "--region",
        help="COS region, e.g. ap-guangzhou. Fallback env: COS_REGION",
    )
    parser.add_argument(
        "--bucket",
        help="Bucket name, e.g. my-bucket-1250000000. Fallback env: COS_BUCKET",
    )
    parser.add_argument("--file", required=True, help="Local file path to upload")
    parser.add_argument("--key", help="Object key in bucket. Default: local filename")
    parser.add_argument("--expires", type=int, default=3600, help="Presigned URL expiration in seconds")
    parser.add_argument("--scheme", default="https", choices=["https", "http"], help="Request scheme")

    parser.add_argument("--secret-id", help="Tencent Cloud SecretId")
    parser.add_argument("--secret-key", help="Tencent Cloud SecretKey")
    parser.add_argument("--session-token", help="Tencent Cloud session token (STS)")

    parser.add_argument(
        "--download-filename",
        help="Optional filename shown in browser download prompt",
    )
    return parser


def main() -> int:
    args = build_parser().parse_args()

    if _IMPORT_ERROR is not None:
        print(
            "[ERROR] Missing Tencent COS SDK. Install with: pip install cos-python-sdk-v5",
            file=sys.stderr,
        )
        print(f"[ERROR] Import detail: {_IMPORT_ERROR}", file=sys.stderr)
        return 2

    local_path = Path(args.file).expanduser().resolve()
    if not local_path.exists() or not local_path.is_file():
        print(f"[ERROR] File not found: {local_path}", file=sys.stderr)
        return 2

    if args.expires <= 0:
        print("[ERROR] --expires must be > 0", file=sys.stderr)
        return 2

    try:
        region = _pick_value(args.region, ["COS_REGION"], "region", True)
        bucket = _pick_value(args.bucket, ["COS_BUCKET"], "bucket", True)
        secret_id = _pick_value(
            args.secret_id,
            ["COS_SECRET_ID"],
            "secret id",
            True,
        )
        secret_key = _pick_value(
            args.secret_key,
            ["COS_SECRET_KEY"],
            "secret key",
            True,
        )
        session_token = _pick_value(
            args.session_token,
            ["COS_SESSION_TOKEN"],
            "session token",
            False,
        )

        key = _normalize_key(local_path, args.key)
        config = CosConfig(
            Region=region,
            SecretId=secret_id,
            SecretKey=secret_key,
            [REDACTED_SECRET] or None,
            Scheme=args.scheme,
        )
        client = CosS3Client(config)

        upload_result = client.upload_file(
            Bucket=bucket,
            LocalFilePath=str(local_path),
            Key=key,
        )

        view_url = client.get_presigned_url(
            Method="GET",
            Bucket=bucket,
            Key=key,
            Expired=args.expires,
        )

        download_params = None
        if args.download_filename:
            download_params = {
                "response-content-disposition": _content_disposition(
                    "attachment",
                    args.download_filename,
                )
            }

        download_url = client.get_presigned_download_url(
            Bucket=bucket,
            Key=key,
            Expired=args.expires,
            Params=download_params,
        )

    except (ValueError, CosClientError, CosServiceError) as exc:
        print(f"[ERROR] {exc}", file=sys.stderr)
        return 1
    except Exception as exc:  # noqa: BLE001
        print(f"[ERROR] Unexpected failure: {exc}", file=sys.stderr)
        return 1

    output = {
        "bucket": bucket,
        "region": region,
        "key": key,
        "local_file": str(local_path),
        "size_bytes": local_path.stat().st_size,
        "expires_in": args.expires,
        "generated_at_utc": datetime.now(timezone.utc).isoformat(),
        "view_url": view_url,
        "download_url": download_url,
        "upload_result": upload_result,
    }
    print(json.dumps(output, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)