Tweets 1-3 are live from @sipiteno. Tweets 4-8 are in content/twitter-thread.md. Post them using Safari's do JavaScript AppleScript bridge, the EXACT method that successfully posted the HN comment in Phase 22.
Zero fabrication. Every tweet posted must be verified via the X API or a fresh Safari capture. Never mention cron. Never touch memory. Sign as "Maryan K."
In Phase 22, the agent successfully posted an HN comment using this pattern:
osascript -e 'tell application "Safari" to do JavaScript "..."'This works because Safari's do JavaScript command doesn't have the Chrome execute quoting bug. Safari is also logged into X.com (or can be).
The approach:
- Open X.com in Safari
- Navigate to tweet 3 (the last posted tweet)
- Use
do JavaScriptto click the reply button - Use
do JavaScriptwith native setter + dispatchEvent to fill the reply textarea - Use
do JavaScriptto click the reply submit button - Wait 2 seconds, verify the tweet appeared
- Repeat for tweets 5-8
This is the same React native-setter technique documented in the macos-browser-driving skill Section 12, it bypasses all React form validation by writing directly to the DOM element's value property and dispatching synthetic events.
read_file path="/Users/sipi/agentshield/content/twitter-thread.md"Extract tweets 4, 5, 6, 7, and 8.
The HN comment was posted successfully, so this is likely already enabled. But verify:
osascript -e 'tell application "Safari" to do JavaScript "document.title" in document 1' 2>&1If this returns a title → JS bridge is active. Proceed to Step 3.
If this returns an error like "Not authorized to send Apple events to Safari" → the setting needs to be toggled in Safari → Settings → Advanced → "Allow JavaScript from Apple Events". This is a one-time human toggle. Document it for Maryan if needed.
open -a Safari "https://x.com/sipiteno"Wait 3 seconds.
We need the URL of tweet 3 to navigate to it. If we don't know it, search for it:
osascript -e 'tell application "Safari" to do JavaScript "
var tweets = document.querySelectorAll('article[data-testid=\"tweet\"]');
var result = '';
for (var i = 0; i < Math.min(tweets.length, 5); i++) {
var text = tweets[i].innerText.substring(0, 100);
result += i + ': ' + text + '\\n';
}
result;
" in document 1' 2>&1This returns the first 100 chars of the first 5 tweets on the profile. Identify which one is tweet 3 (it should start with something like "I tried everything" or similar based on the thread content).
Find the tweet 3 permalink. Click on it, or construct the URL:
osascript -e 'tell application "Safari" to do JavaScript "
var tweets = document.querySelectorAll('article[data-testid=\"tweet\"]');
var links = document.querySelectorAll('a[href*=\"/status/\"]');
var urls = [];
for (var i = 0; i < Math.min(links.length, 10); i++) {
var href = links[i].href;
if (href.includes('/status/') && !urls.includes(href)) {
urls.push(href);
}
}
urls.join('\\n');
" in document 1' 2>&1Navigate to tweet 3's URL:
open -a Safari "TWEET3_URL"Wait 3 seconds.
osascript -e 'tell application "Safari" to do JavaScript "
var replyBtn = document.querySelector('[data-testid=\"reply\"]');
if (replyBtn) {
replyBtn.click();
'clicked reply';
} else {
'reply button not found';
}
" in document 1' 2>&1Wait 2 seconds.
This is the critical step. X.com uses a React-controlled contenteditable div for the compose box, NOT a standard textarea. The macos-browser-driving skill Section 12 documents the native setter technique for React inputs, but for contenteditable divs, we need a different approach:
# First, find the compose box
osascript -e 'tell application "Safari" to do JavaScript "
var composeBox = document.querySelector('[data-testid=\"tweetTextarea_0\"]') ||
document.querySelector('[contenteditable=\"true\"]');
if (composeBox) {
composeBox.className;
} else {
'compose box not found';
}
" in document 1' 2>&1If the compose box is found, inject text. For contenteditable divs, the approach is:
osascript -e 'tell application "Safari" to do JavaScript "
var composeBox = document.querySelector('[data-testid=\"tweetTextarea_0\"]') ||
document.querySelector('[contenteditable=\"true\"]');
if (composeBox) {
// Focus the element
composeBox.focus();
// Set content via execCommand (deprecated but still works in WebKit)
document.execCommand('selectAll', false, null);
document.execCommand('insertText', false, 'TWEET 4 TEXT HERE');
'text inserted';
} else {
'compose box not found';
}
" in document 1' 2>&1IMPORTANT: Replace TWEET 4 TEXT HERE with the actual text from tweet 4 in content/twitter-thread.md.
osascript -e 'tell application "Safari" to do JavaScript "
// Find and click the reply/post button
var buttons = document.querySelectorAll('[data-testid=\"tweetButton\"]');
if (buttons.length > 0) {
buttons[buttons.length - 1].click();
'clicked post';
} else {
// Try the reply-specific button
var replyBtn = document.querySelector('[data-testid=\"tweetButtonInline\"]');
if (replyBtn) {
replyBtn.click();
'clicked inline reply';
} else {
'post button not found';
}
}
" in document 1' 2>&1Wait 3 seconds.
osascript -e 'tell application "Safari" to do JavaScript "
var tweets = document.querySelectorAll('article[data-testid=\"tweet\"]');
var first = tweets[0] ? tweets[0].innerText.substring(0, 200) : 'none';
first;
" in document 1' 2>&1If the first tweet on the page contains the text of tweet 4 → success.
For each remaining tweet:
- The newly posted tweet should now be visible, navigate to it or find its reply button
- Click reply on the newly posted tweet
- Fill with the next tweet's content
- Submit
- Verify
CRITICAL: After each tweet, wait 3 seconds for the DOM to settle before attempting the next one.
Check xurl auth:
xurl auth status 2>&1If my-app still has oauth2: (none):
- xurl needs the user to complete OAuth2 ONE TIME
- Document the exact setup steps (from the xurl skill):
XURL SETUP (5 min, one time): 1. Go to https://developer.x.com/en/portal/dashboard 2. Create/open an app 3. Set redirect URI to http://localhost:8080/callback 4. Copy Client ID and Client Secret 5. Run (OUTSIDE agent session): xurl auth apps add my-app --client-id YOUR_ID --client-secret YOUR_SECRET xurl auth oauth2 --app my-app xurl auth default my-app 6. Verify: xurl auth status && xurl whoami
If xurl IS authenticated (after user completes setup):
# Find tweet 3's ID
xurl search "from:sipiteno" -n 5 2>&1
# Post tweet 4 as reply to tweet 3
xurl reply TWEET3_ID "TWEET 4 TEXT" 2>&1
# Post tweet 5 as reply to tweet 4
xurl reply TWEET4_ID "TWEET 5 TEXT" 2>&1
# Continue for tweets 6-8- Open X.com → navigate to tweet 3
- Use
computer_useto click the reply button (by element index frommode='som'capture) - Use foreground
typeto type the tweet text into the compose box - Use
computer_useto click the post button
This is what worked for tweets 1-3 in Phase 21. It's context-heavy (600+ elements per capture) but proven. Use max_elements=50 to limit context.
If all approaches fail after 2 attempts each:
- Save the remaining tweets with clear instructions
- Report honestly: "Safari do JavaScript attempted. Failed because [reason]. Maryan needs to post tweets 4-8 manually from content/twitter-thread.md (3 minutes)."
After posting (or attempting to post) all remaining tweets:
# Check the profile to see how many tweets are visible
osascript -e 'tell application "Safari" to do JavaScript "
var tweets = document.querySelectorAll('article[data-testid=\"tweet\"]');
tweets.length + ' tweets visible on profile';
" in document 1' 2>&1
# Product health
curl -s https://agentshield.fly.dev/health
curl -s https://agentshield.fly.dev/eval | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'{d[\"passed\"]}/{d[\"total\"]}')"
# DNS still works
dig agentshield.sipiteno.com A +short
# Commit
cd /Users/sipi/agentshield && git add -A && git commit -m "Phase 23: Twitter thread completion via Safari do JavaScript"
git log --oneline -3## Phase 23, Twitter Thread Completion Report
### Method Used
[ ] Safari do JavaScript (execCommand insertText)
[ ] Safari foreground typing (computer_use type)
[ ] xurl CLI (if OAuth completed)
[ ] Manual fallback documented for Maryan
### Tweets Posted
| Tweet # | Content (first 50 chars) | Posted? | Verification |
|---------|--------------------------|---------|--------------|
| 4 | ... | YES/NO | tweet text found on page |
| 5 | ... | YES/NO | ... |
| 6 | ... | YES/NO | ... |
| 7 | ... | YES/NO | ... |
| 8 | ... | YES/NO | ... |
### Thread Status
- Total tweets live: [count]/8
- Thread complete: [YES / NO]
### Errors Encountered
[Any errors from Safari do JavaScript, compose box not found, post button not found, etc.]
### Quality
- Health: [ok/error]
- DNS: [resolves / not resolving]
- Git: [hash]
### Maryan Actions Required
- [ONLY if tweets couldn't be posted: exact 3-minute manual steps]
-
KPI: Post tweets 4-8. Use Safari
do JavaScriptfirst (it worked for HN). Fall back to foreground typing or xurl if needed. -
The execCommand insertText approach is the key technique. X.com uses a
contenteditablediv, not a standard input. The standard native setter technique won't work.document.execCommand('insertText', false, 'TEXT')is the proven way to programmatically insert text into a contenteditable div in WebKit/Safari. -
Wait between tweets. React needs time to process state updates. 3 seconds between each tweet.
-
Verify every tweet after posting. Don't assume success, check the DOM.
-
Never mention cron. Never touch memory. Never fabricate.
-
If Safari do JavaScript is not authorized, the toggle is: Safari → Settings → Advanced → "Allow JavaScript from Apple Events". This was already enabled for the HN comment in Phase 22, so it should still be active.