agentsclimarketplace

Youtube

Skill AceDataCloud/Skills/skills/youtube

Agent Skills for AceDataCloud AI services — music, image, video generation, web search, and more. Compatible with Claude Code, GitHub Copilot, Gemini CLI, and all agentskills.io-compatible agents.

Install
npx -y skills add AceDataCloud/Skills --skill youtube

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 13 stars13 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Search YouTube, read your own channel and uploaded videos (stats, comments), and upload new videos via the YouTube Data API v3. Use when the user mentions YouTube, "my channel", "my videos", searching YouTube, video views / likes / stats, uploading a video to YouTube, or checking how a published video is doing.

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.4 KB, as published. Nobody here has run it

Call the YouTube Data API v3 with curl + jq. The user's OAuth bearer token is in $GOOGLE_YOUTUBE_TOKEN; every call needs it as Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN. Base URL: https://www.googleapis.com/youtube/v3.

The token always carries youtube.readonly plus identity scopes (openid email profile); if the user opted in at install it also carries youtube.upload (publish videos).

Responses are standard JSON; failures surface as {"error": {"code": 401|403|..., "message": "..."}} — show that error verbatim. 401 → token expired, the user must re-connect the YouTube connector. 403 insufficientPermissions on an upload → the user did not grant youtube.upload; ask them to re-connect with the upload box checked.

Always start with the channel check to confirm the connection works and learn which channel you're operating against.

curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  "https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,contentDetails&mine=true" \
  | jq '.items[0] | {title: .snippet.title, subs: .statistics.subscriberCount, views: .statistics.viewCount, uploads: .contentDetails.relatedPlaylists.uploads}'

Search YouTube

# Public search (any video). type can be video|channel|playlist.
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  --data-urlencode "q=ai video automation" \
  --data-urlencode "part=snippet" \
  --data-urlencode "type=video" \
  --data-urlencode "maxResults=10" \
  -G "https://www.googleapis.com/youtube/v3/search" \
  | jq '.items[] | {videoId: .id.videoId, title: .snippet.title, channel: .snippet.channelTitle, published: .snippet.publishedAt}'

Add --data-urlencode "order=date|viewCount|rating|relevance" to sort, or --data-urlencode "publishedAfter=2026-01-01T00:00:00Z" to window.

See my uploaded videos

YouTube has no "list my videos" call directly — read the channel's uploads playlist, then page its items.

# 1. Get the uploads playlist id (UU... ) — same as channels call above.
UPLOADS=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  "https://www.googleapis.com/youtube/v3/channels?part=contentDetails&mine=true" \
  | jq -r '.items[0].contentDetails.relatedPlaylists.uploads')

# 2. List recent uploads (50/page; follow .nextPageToken for more).
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/playlistItems" \
  --data-urlencode "part=snippet,contentDetails" \
  --data-urlencode "playlistId=$UPLOADS" \
  --data-urlencode "maxResults=50" \
  | jq '.items[] | {videoId: .contentDetails.videoId, title: .snippet.title, published: .snippet.publishedAt}'

Paginate by passing --data-urlencode "pageToken=$PAGE_TOKEN" with the .nextPageToken from the previous response.

Video stats (views / likes / comments)

# Accepts a comma-separated id list.
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/videos" \
  --data-urlencode "part=snippet,statistics,status" \
  --data-urlencode "id=VIDEO_ID_1,VIDEO_ID_2" \
  | jq '.items[] | {title: .snippet.title, views: .statistics.viewCount, likes: .statistics.likeCount, comments: .statistics.commentCount, privacy: .status.privacyStatus}'

Read comments on a video

curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/commentThreads" \
  --data-urlencode "part=snippet" \
  --data-urlencode "videoId=VIDEO_ID" \
  --data-urlencode "maxResults=20" \
  --data-urlencode "order=relevance" \
  | jq '.items[] | .snippet.topLevelComment.snippet | {author: .authorDisplayName, text: .textDisplay, likes: .likeCount}'

Upload a video (needs youtube.upload)

Confirm with the user before publishing — show the title, privacy and file you're about to upload. Uploads are a two-step resumable flow: init with metadata → PUT the bytes.

If the source is a URL, fetch it first

Videos produced by a generation API (Maestro, Seedance, Veo, …) come back as a CDN URL, not a local file. Download it and verify you actually got a video before starting the upload:

SRC="https://example.cdn.acedata.cloud/path/video.mp4"
FILE=$(mktemp)   # plain mktemp — `mktemp /tmp/x-XXXXXX.mp4` is not portable

# -f: fail on 4xx/5xx instead of saving the error page. -L: follow CDN redirects.
curl -fsSL -o "$FILE" -w 'http=%{http_code} size=%{size_download}\n' "$SRC"

# Two distinct failures, two distinct messages — a 404/DNS error is a bad URL,
# not a bad video, and saying so saves the next step from misdiagnosing it.
[ -s "$FILE" ] \
  || { echo "download failed (see http= above) — nothing to upload"; rm -f "$FILE"; exit 1; }

# A 200 can still be an HTML interstitial or an expired-link page. Reject that
# rather than allow-listing one container, so WebM/MOV/AVI still pass. `grep -a`
# is required — without it grep treats a binary header as "no match".
head -c 512 "$FILE" | grep -qai '<html\|<!doctype' \
  && { echo "got an HTML page, not a video"; rm -f "$FILE"; exit 1; }

--upload-file ignores the filename; YouTube keys off the Content-Type: video/* header, so the extensionless temp file is fine.

The upload itself

# Falls back to the placeholder only if the fetch step above didn't set FILE.
FILE="${FILE:-/path/to/video.mp4}"
TITLE="My title"
DESC="My description"
# privacyStatus: public | unlisted | private
read -r -d '' META <<JSON
{"snippet":{"title":"$TITLE","description":"$DESC","categoryId":"22"},
 "status":{"privacyStatus":"unlisted","selfDeclaredMadeForKids":false}}
JSON

# 1. Init resumable session -> capture the upload URL from the Location header.
UPLOAD_URL=$(curl -sS -D - -o /dev/null \
  -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -H "X-Upload-Content-Type: video/*" \
  -X POST "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status" \
  -d "$META" | tr -d '\r' | awk '/^[Ll]ocation:/{print $2}')

# 2. Upload the bytes -> returns the created video resource (has .id).
RESULT=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -H "Content-Type: video/*" \
  -X PUT --upload-file "$FILE" "$UPLOAD_URL")
echo "$RESULT" | jq -e .id >/dev/null 2>&1 \
  || { echo "upload failed: $(echo "$RESULT" | jq -r '.error.message' 2>/dev/null || echo "$RESULT")"; exit 1; }
echo "$RESULT" | jq '{id: .id, url: ("https://www.youtube.com/watch?v=" + .id), privacy: .status.privacyStatus}'

Delete a downloaded temp file only after you've confirmed an id came back (echo "$RESULT" | jq -e .id >/dev/null && rm -f "$FILE"). On a 401/403 or a dropped connection the download is still good and re-uploading beats re-fetching a few hundred MB.

categoryId 22 = "People & Blogs" (a safe default). To set a custom thumbnail (needs the file to be processed first), call POST /upload/youtube/v3/thumbnails/set?videoId=VIDEO_ID with the image.

Gotchas

  • Quota: the Data API is quota-metered (default 10,000 units/day). A search costs 100 units; an upload costs ~1,600. A burst of searches can exhaust the daily quota → 403 quotaExceeded; surface it plainly.
  • No "my videos" endpoint — always go via the uploads playlist.
  • search results are eventually-consistent — a freshly uploaded video may not appear in search for minutes/hours; read it via the uploads playlist or by id instead.
  • Uploaded videos start in uploaded/processing state; stats are 0 until processing completes.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.