Compare commits

...

3 Commits

Author SHA1 Message Date
argenis de la rosa c4b2a21c61 ci: upgrade tweet-release with OpenClaw-style formatting, image support, and manual dispatch 2026-03-14 05:01:56 -04:00
argenis de la rosa d6170ab49b ci: add tweet-release workflow to post to X on stable releases 2026-03-14 04:59:18 -04:00
Argenis 399c896c3b chore: bump release notes to v0.1.9b (#3455)
Update What's New and Recent Contributors sections to reference
v0.1.9b release.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:57:11 -04:00
2 changed files with 178 additions and 2 deletions
+176
View File
@@ -0,0 +1,176 @@
name: Tweet Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
tweet_text:
description: "Custom tweet text (include emojis, keep it punchy)"
required: true
type: string
image_url:
description: "Optional image URL to attach (png/jpg)"
required: false
type: string
jobs:
tweet:
# Skip beta pre-releases on auto trigger
if: >-
github.event_name == 'workflow_dispatch' ||
!github.event.release.prerelease
runs-on: ubuntu-latest
steps:
- name: Build tweet text
id: tweet
shell: bash
env:
RELEASE_TAG: ${{ github.event.release.tag_name || '' }}
RELEASE_URL: ${{ github.event.release.html_url || '' }}
RELEASE_BODY: ${{ github.event.release.body || '' }}
MANUAL_TEXT: ${{ inputs.tweet_text || '' }}
run: |
if [ -n "$MANUAL_TEXT" ]; then
# Manual dispatch — use the custom text as-is
TWEET="$MANUAL_TEXT"
else
# Auto trigger — look for <!-- tweet --> block in release body
# Format in your release notes:
# <!-- tweet -->
# ZeroClaw v0.2.0 🐾
#
# 🚀 feature one
# 🔧 feature two
#
# the claw strikes
# <!-- /tweet -->
TWEET_BLOCK=$(echo "$RELEASE_BODY" | sed -n '/<!-- tweet -->/,/<!-- \/tweet -->/p' | sed '1d;$d' || true)
if [ -n "$TWEET_BLOCK" ]; then
TWEET="$TWEET_BLOCK"
else
# Fallback: auto-generate a simple announcement
TWEET=$(printf "ZeroClaw %s 🐾\n\nFull release notes 👇\n%s" "$RELEASE_TAG" "$RELEASE_URL")
fi
fi
# Append release URL if not already present and we have one
if [ -n "$RELEASE_URL" ] && ! echo "$TWEET" | grep -q "$RELEASE_URL"; then
TWEET=$(printf "%s\n\n%s" "$TWEET" "$RELEASE_URL")
fi
echo "--- Tweet preview ---"
echo "$TWEET"
echo "--- ${#TWEET} chars ---"
{
echo "text<<TWEET_EOF"
echo "$TWEET"
echo "TWEET_EOF"
} >> "$GITHUB_OUTPUT"
- name: Post to X
shell: bash
env:
TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
TWEET_TEXT: ${{ steps.tweet.outputs.text }}
IMAGE_URL: ${{ inputs.image_url || '' }}
run: |
set -euo pipefail
pip install requests requests-oauthlib --quiet
python3 - <<'PYEOF'
import os, sys, time
from requests_oauthlib import OAuth1Session
consumer_key = os.environ["TWITTER_CONSUMER_KEY"]
consumer_secret = os.environ["TWITTER_CONSUMER_SECRET"]
access_token = os.environ["TWITTER_ACCESS_TOKEN"]
access_token_secret = os.environ["TWITTER_ACCESS_TOKEN_SECRET"]
tweet_text = os.environ["TWEET_TEXT"]
image_url = os.environ.get("IMAGE_URL", "")
oauth = OAuth1Session(
consumer_key,
client_secret=consumer_secret,
resource_owner_key=access_token,
resource_owner_secret=access_token_secret,
)
media_id = None
# Upload image if provided
if image_url:
import requests
print(f"Downloading image: {image_url}")
img_resp = requests.get(image_url, timeout=30)
img_resp.raise_for_status()
content_type = img_resp.headers.get("content-type", "image/png")
# X media upload (v1.1 chunked INIT/APPEND/FINALIZE)
init_resp = oauth.post(
"https://upload.twitter.com/1.1/media/upload.json",
data={
"command": "INIT",
"total_bytes": len(img_resp.content),
"media_type": content_type,
},
)
if init_resp.status_code != 202:
print(f"Media INIT failed: {init_resp.status_code} {init_resp.text}", file=sys.stderr)
sys.exit(1)
media_id = init_resp.json()["media_id_string"]
append_resp = oauth.post(
"https://upload.twitter.com/1.1/media/upload.json",
data={"command": "APPEND", "media_id": media_id, "segment_index": 0},
files={"media_data": img_resp.content},
)
if append_resp.status_code not in (200, 204):
print(f"Media APPEND failed: {append_resp.status_code} {append_resp.text}", file=sys.stderr)
sys.exit(1)
fin_resp = oauth.post(
"https://upload.twitter.com/1.1/media/upload.json",
data={"command": "FINALIZE", "media_id": media_id},
)
if fin_resp.status_code not in (200, 201):
print(f"Media FINALIZE failed: {fin_resp.status_code} {fin_resp.text}", file=sys.stderr)
sys.exit(1)
# Wait for processing if needed
state = fin_resp.json().get("processing_info", {}).get("state")
while state == "pending" or state == "in_progress":
wait = fin_resp.json().get("processing_info", {}).get("check_after_secs", 2)
time.sleep(wait)
status_resp = oauth.get(
"https://upload.twitter.com/1.1/media/upload.json",
params={"command": "STATUS", "media_id": media_id},
)
state = status_resp.json().get("processing_info", {}).get("state")
fin_resp = status_resp
print(f"Image uploaded: media_id={media_id}")
# Post tweet
payload = {"text": tweet_text}
if media_id:
payload["media"] = {"media_ids": [media_id]}
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
if resp.status_code == 201:
data = resp.json()
tweet_id = data["data"]["id"]
print(f"Tweet posted: https://x.com/zeroclawlabs/status/{tweet_id}")
else:
print(f"Failed to post tweet: {resp.status_code}", file=sys.stderr)
print(resp.text, file=sys.stderr)
sys.exit(1)
PYEOF
+2 -2
View File
@@ -84,7 +84,7 @@ Built by students and members of the Harvard, MIT, and Sundai.Club communities.
<p align="center"><code>Trait-driven architecture · secure-by-default runtime · provider/channel/tool swappable · pluggable everything</code></p>
### 🚀 What's New in v0.1.9a (March 2026)
### 🚀 What's New in v0.1.9b (March 2026)
| Area | Highlights |
|---|---|
@@ -1142,7 +1142,7 @@ A heartfelt thank you to the communities and institutions that inspire and fuel
We're building in the open because the best ideas come from everywhere. If you're reading this, you're part of it. Welcome. 🦀❤️
### 🌟 Recent Contributors (v0.1.9a)
### 🌟 Recent Contributors (v0.1.9b)
Special recognition to the contributors who shipped features, fixes, and improvements in this release cycle: