- Generate post: Extract highlights from CHANGELOG for version
- User confirmation: Show preview, allow editing, get approval
- Post to X: Send via API (http://5.161.75.135:8080)
- Poll for tweet ID: Wait until post is live
- Reply with link: Post GitHub release URL as threaded reply
- Display URLs: Show both tweet links for verification
Example flow:
Generating X announcement for v2.7.0...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
X Announcement Preview
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Spec-Flow v2.7.0 is here!
- One-command releases with CI validation
- Auto-close GitHub issues when features ship
- Essential cleanup for all deployment models
Ship features faster with less manual work.
Characters: 187/280
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Options:
1. Post as-is
2. Edit post text
3. Skip X announcement
Posting to X... (ID: 12345)
Waiting for publish... (3s)
Posted to X!
Posting GitHub link as threaded reply... (ID: 12346)
GitHub link posted!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
X Announcement Posted!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Main Post:
https://x.com/username/status/1234567890
GitHub Link Reply:
https://x.com/username/status/1234567891
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Context available:
NEW_VERSION- Version number (e.g., "2.7.0")CHANGELOG.md- Release notes for extractionREADME.md- Feature highlightsCOMMIT_SHA- Git commit hash
Content extraction:
- Read
CHANGELOG.md→ Extract## [NEW_VERSION]section - Identify top features: Prioritize Added > Fixed > Changed
- Format with engaging tone (no emojis)
- Validate character count (≤280)
See references/post-templates.md for examples and guidelines.
Extract release highlights:
# Read CHANGELOG section for version
SECTION=$(sed -n "/## \[${NEW_VERSION}\]/,/## \[/p" CHANGELOG.md | head -n -1)
# Extract Added features
ADDED=$(echo "$SECTION" | grep -A 10 "### Added" | grep "^-" | head -3)
# Extract Fixed items
FIXED=$(echo "$SECTION" | grep -A 10 "### Fixed" | grep "^-" | head -2)
# Format into engaging post
# Template: 🚀 Spec-Flow v${NEW_VERSION} is here!
# ${FEATURE_EMOJI} ${FEATURE_1}
# ${FEATURE_EMOJI} ${FEATURE_2}
# ${BENEFIT_STATEMENT}
Display preview with character count for user review.
Use AskUserQuestion or direct prompt:
Options:
1. ✅ Post as-is
2. ✏️ Edit post text
3. ❌ Skip X announcement
If user selects Edit:
- Prompt for new text
- Validate ≤280 characters
- Show updated preview
- Ask for confirmation again
If user selects Skip:
- Exit gracefully
- Continue with release summary
Send POST request with confirmed content:
# Write content to temp file for proper UTF-8 encoding
cat > /tmp/x-post.txt << 'EOF'
<user-confirmed text>
EOF
POST_CONTENT=$(cat /tmp/x-post.txt | jq -Rs .)
RESPONSE=$(curl -s -X POST "http://5.161.75.135:8080/api/v1/posts/" \
-H "Content-Type: application/json" \
-d "{\"content\": $POST_CONTENT, \"scheduled_at\": null}")
POST_ID=$(echo "$RESPONSE" | jq -r '.id')
echo "📤 Posting to X... (ID: $POST_ID)"
Error handling:
- API unreachable → Warn, provide manual posting instructions
- POST fails → Display error, offer manual fallback
- Continue release process regardless
See references/api-reference.md for complete API documentation.
Wait for post to be published:
MAX_ATTEMPTS=20 # 60 seconds total (20 × 3s)
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
STATUS_RESPONSE=$(curl -s "http://5.161.75.135:8080/api/v1/posts/$POST_ID")
POST_STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status')
TWEET_ID=$(echo "$STATUS_RESPONSE" | jq -r '.tweet_id // empty')
if [ "$POST_STATUS" = "posted" ] && [ -n "$TWEET_ID" ]; then
echo "✅ Posted to X!"
break
elif [ "$POST_STATUS" = "failed" ]; then
ERROR_REASON=$(echo "$STATUS_RESPONSE" | jq -r '.error_reason')
echo "❌ Post failed: $ERROR_REASON"
exit 1
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 3
done
Display progress: ⏳ Waiting for publish... (Xs)
Once main post is live, create threaded reply:
GITHUB_URL="https://github.com/marcusgoll/Spec-Flow/releases/tag/v${NEW_VERSION}"
# Write reply content to temp file for proper UTF-8 encoding
cat > /tmp/reply.txt << EOF
🔗 Release notes: ${GITHUB_URL}
EOF
REPLY_CONTENT=$(cat /tmp/reply.txt | jq -Rs .)
# Post as threaded reply using in_reply_to_tweet_id
REPLY_RESPONSE=$(curl -s -X POST "http://5.161.75.135:8080/api/v1/posts/" \
-H "Content-Type: application/json" \
-d "{\"content\": $REPLY_CONTENT, \"scheduled_at\": null, \"in_reply_to_tweet_id\": \"$TWEET_ID\"}")
REPLY_POST_ID=$(echo "$REPLY_RESPONSE" | jq -r '.id')
# Poll for reply tweet ID (same logic as step 4)
# ...
echo "✅ GitHub link posted!"
Fallback if reply fails:
- Main post succeeded → Display main tweet URL
- Prompt user to manually reply with GitHub link
- Low impact (main announcement visible)
Show both tweet URLs:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📱 X Announcement Posted!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Main Post:
https://x.com/username/status/{TWEET_ID}
GitHub Link Reply:
https://x.com/username/status/{REPLY_TWEET_ID}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Continue with release summary in /release command.
Release completed successfully, but X announcement could not be posted.
Manual posting option:
- Copy the post text above
- Post manually to X: https://x.com/compose
- Reply with: 🔗 Release notes: https://github.com/marcusgoll/Spec-Flow/releases/tag/v{VERSION}
Release will continue...
**Scenario 2: Post Timeout**
⏱️ Timeout waiting for post to publish (60s exceeded)
Post may still succeed in background. Check status: http://5.161.75.135:8080/api/v1/posts/{POST_ID}
Release will continue...
**Scenario 3: Main Post Succeeds, Reply Fails**
✅ Main post successful! ⚠️ Reply post failed
Main announcement: https://x.com/username/status/{TWEET_ID}
Manually reply with: 🔗 Release notes: https://github.com/marcusgoll/Spec-Flow/releases/tag/v{VERSION}
See [references/error-scenarios.md](references/error-scenarios.md) for complete error handling.
</error_handling>
<api_integration>
**X Poster API Base URL:** `http://5.161.75.135:8080/`
**Key endpoints:**
- `POST /api/v1/posts/` - Create post or threaded reply
- `GET /api/v1/posts/{id}` - Get post status and tweet_id
- `POST /api/v1/posts/{id}/cancel` - Cancel queued post
**Post request format:**
```json
{
"content": "Tweet text (max 280 chars)",
"scheduled_at": null,
"in_reply_to_tweet_id": "1234567890" | null
}
Status values:
queued- Waiting to postposting- Currently postingposted- Live (tweet_id available)failed- Error (error_reason available)
Dependencies:
jq- JSON parsing (required)curl- HTTP requests (required)- Network access to API endpoint
See references/api-reference.md for complete API documentation.
1. Posting without user confirmation
❌ BAD: Auto-post without showing preview
✅ GOOD: Display preview, get explicit confirmation
2. Exceeding character limit
❌ BAD: Generate 300-char post, API rejects
✅ GOOD: Validate ≤280 chars, trim if needed
3. Not handling API failures gracefully
❌ BAD: Crash release process if X post fails
✅ GOOD: Warn, provide manual fallback, continue release
4. Blocking release on X announcement
❌ BAD: Release fails if tweet can't be posted
✅ GOOD: X announcement is optional enhancement
5. Exposing API URL in public docs
❌ BAD: Include API base URL in error messages, logs
✅ GOOD: Network-isolated, internal-only reference
- ✓ Post generated from CHANGELOG highlights
- ✓ Preview shown with character count
- ✓ User confirmation obtained before posting
- ✓ Main post created via API successfully
- ✓ Tweet ID retrieved via polling
- ✓ Threaded reply posted with GitHub link
- ✓ Both tweet URLs displayed
- ✓ Release continues even if X post fails
Error handling passing when:
- API unreachable → Manual fallback provided
- Post timeout → Status check URL shown
- Reply fails → Main post URL + manual reply instructions
- Release never blocked by X announcement failures
- references/post-templates.md - Engaging post examples and generation guidelines
- references/api-reference.md - Complete X Poster API documentation
- references/error-scenarios.md - Comprehensive error handling strategies
- references/legacy-documentation.md - Original detailed implementation guide