Compare commits

...

11 Commits

Author SHA1 Message Date
Jim Liu 宝玉 a1b935b2d8 chore: release v1.115.3 2026-05-11 16:24:14 -05:00
Jim Liu 宝玉 9968d79a26 fix(baoyu-post-to-x): use toolbar media upload instead of image clipboard paste 2026-05-11 16:23:17 -05:00
Jim Liu 宝玉 6f75fb17e4 chore: add .codex-tmp and outputs to .gitignore 2026-05-11 16:23:09 -05:00
Jim Liu 宝玉 adba24281b Merge pull request #149 from fengxiaodong28/fix-browser-copy-paste
fix(browser): ensure tab activation before copy/paste in WeChat editor
2026-05-10 11:37:40 -05:00
Jim Liu 宝玉 61342ecfea chore: release v1.115.2 2026-05-10 02:49:50 -05:00
Jim Liu 宝玉 5f753dd584 fix(baoyu-post-to-x): respect Chrome plugin mode 2026-05-10 02:49:42 -05:00
Jim Liu 宝玉 076192d58e chore: release v1.115.1 2026-05-09 22:10:54 -05:00
Jim Liu 宝玉 d6d434e714 fix(image): update MiniMax default endpoint 2026-05-09 22:07:26 -05:00
Jim Liu 宝玉 6f3600d8e5 chore: release v1.115.0 2026-05-09 22:05:27 -05:00
Jim Liu 宝玉 045fe5e57e feat(baoyu-post-to-x): add Chrome Computer Use as preferred execution mode
In Codex, prefer the bundled Chrome Computer Use path for all X UI
actions (compose, article, quote, video). CDP scripts become a fallback
when Computer Use is unavailable or explicitly not requested.
2026-05-09 22:04:58 -05:00
FENG/XIAODONG 0b3b7d13b5 fix(browser): ensure tab activation before copy/paste in WeChat editor
When posting articles via browser automation, the HTML preview tab
needs to be active before copying content, and the editor tab needs
to be active before pasting. Without explicit Target.activateTarget
calls, AppleScript Cmd+C/Cmd+V would act on the wrong tab, causing
the editor body to remain empty after paste.

This fix adds Target.activateTarget before both copy and paste
operations, ensuring the correct tab is in focus for system-level
clipboard operations.
2026-05-09 22:13:06 +08:00
18 changed files with 432 additions and 72 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.114.1"
"version": "1.115.3"
},
"plugins": [
{
+2
View File
@@ -173,3 +173,5 @@ diagram/
.worktrees/
youtube-transcript/
.omx/
.codex-tmp/
outputs/
+22
View File
@@ -2,6 +2,28 @@
English | [中文](./CHANGELOG.zh.md)
## 1.115.3 - 2026-05-11
### Fixes
- `baoyu-post-to-wechat`: ensure tab activation before copy/paste in WeChat editor (by @fengxiaodong28)
- `baoyu-post-to-x`: use toolbar media upload instead of image clipboard paste for X Articles
## 1.115.2 - 2026-05-10
### Fixes
- `baoyu-post-to-x`: honor explicit Codex Chrome plugin requests as a distinct browser-control mode, keep Chrome Computer Use and CDP fallbacks from silently taking over, and improve X Articles draft creation detection.
## 1.115.1 - 2026-05-10
### Fixes
- `baoyu-imagine`: change the default MiniMax image API endpoint to `https://api.minimaxi.com` to match the current official image generation documentation, while keeping `https://api.minimax.io` available through `MINIMAX_BASE_URL` overrides.
- `baoyu-image-gen`: sync the deprecated image-generation entrypoint with the same MiniMax default endpoint and regression coverage.
## 1.115.0 - 2026-05-09
### Features
- `baoyu-post-to-x`: add Chrome Computer Use as the preferred execution mode in Codex. When Computer Use tools are available, all X UI actions (compose, article, quote, video) go through the user's real Chrome window instead of CDP scripts. CDP scripts become a fallback when Computer Use is unavailable or explicitly not requested.
## 1.114.1 - 2026-05-08
### Fixes
+22
View File
@@ -2,6 +2,28 @@
[English](./CHANGELOG.md) | 中文
## 1.115.3 - 2026-05-11
### 修复
- `baoyu-post-to-wechat`:修复微信编辑器中复制粘贴前未激活标签页的问题 (by @fengxiaodong28)
- `baoyu-post-to-x`:X 文章图片插入改用工具栏媒体上传替代剪贴板粘贴方式
## 1.115.2 - 2026-05-10
### 修复
- `baoyu-post-to-x`:将显式请求 Codex Chrome 插件的场景作为独立浏览器控制模式处理,避免 Chrome Computer Use 或 CDP 回退流程静默接管;同时改进 X Articles 草稿创建按钮检测。
## 1.115.1 - 2026-05-10
### 修复
- `baoyu-imagine`:将默认 MiniMax 图片 API 端点改为 `https://api.minimaxi.com`,与当前官方图片生成文档保持一致;仍可通过 `MINIMAX_BASE_URL` 覆盖为 `https://api.minimax.io`
- `baoyu-image-gen`:同步已废弃图片生成入口的 MiniMax 默认端点和回归测试。
## 1.115.0 - 2026-05-09
### 新功能
- `baoyu-post-to-x`:新增 Chrome Computer Use 作为 Codex 环境下的首选执行模式。当 Computer Use 工具可用时,所有 X 界面操作(发帖、文章、引用、视频)均通过用户真实 Chrome 窗口完成,不再使用 CDP 脚本。CDP 脚本降级为 Computer Use 不可用或用户明确要求时的回退方案。
## 1.114.1 - 2026-05-08
### 修复
+2 -2
View File
@@ -834,7 +834,7 @@ AI SDK-based image generation using OpenAI GPT Image 2, Azure OpenAI, Google, Op
| `DASHSCOPE_BASE_URL` | Custom DashScope endpoint | - |
| `ZAI_BASE_URL` | Custom Z.AI endpoint | `https://api.z.ai/api/paas/v4` |
| `BIGMODEL_BASE_URL` | Backward-compatible alias for Z.AI endpoint | - |
| `MINIMAX_BASE_URL` | Custom MiniMax endpoint | `https://api.minimax.io` |
| `MINIMAX_BASE_URL` | Custom MiniMax endpoint | `https://api.minimaxi.com` |
| `REPLICATE_BASE_URL` | Custom Replicate endpoint | - |
| `JIMENG_BASE_URL` | Custom Jimeng endpoint | `https://visual.volcengineapi.com` |
| `JIMENG_REGION` | Jimeng region | `cn-north-1` |
@@ -1167,7 +1167,7 @@ ZAI_IMAGE_MODEL=glm-image
# MiniMax
MINIMAX_API_KEY=xxx
MINIMAX_IMAGE_MODEL=image-01
# MINIMAX_BASE_URL=https://api.minimax.io
# MINIMAX_BASE_URL=https://api.minimaxi.com
# Replicate
REPLICATE_API_TOKEN=r8_xxx
+2 -2
View File
@@ -834,7 +834,7 @@ AI 驱动的生成后端。
| `DASHSCOPE_BASE_URL` | 自定义 DashScope 端点 | - |
| `ZAI_BASE_URL` | 自定义 Z.AI 端点 | `https://api.z.ai/api/paas/v4` |
| `BIGMODEL_BASE_URL` | Z.AI 端点向后兼容别名 | - |
| `MINIMAX_BASE_URL` | 自定义 MiniMax 端点 | `https://api.minimax.io` |
| `MINIMAX_BASE_URL` | 自定义 MiniMax 端点 | `https://api.minimaxi.com` |
| `REPLICATE_BASE_URL` | 自定义 Replicate 端点 | - |
| `JIMENG_BASE_URL` | 自定义即梦端点 | `https://visual.volcengineapi.com` |
| `JIMENG_REGION` | 即梦区域 | `cn-north-1` |
@@ -1167,7 +1167,7 @@ ZAI_IMAGE_MODEL=glm-image
# MiniMax
MINIMAX_API_KEY=xxx
MINIMAX_IMAGE_MODEL=image-01
# MINIMAX_BASE_URL=https://api.minimax.io
# MINIMAX_BASE_URL=https://api.minimaxi.com
# Replicate
REPLICATE_API_TOKEN=r8_xxx
@@ -24,6 +24,6 @@ Read when the user picks `--provider minimax` or sets `default_model.minimax`. D
## Official References
- [Image Generation Guide](https://platform.minimax.io/docs/guides/image-generation)
- [Text-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-t2i)
- [Image-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-i2i)
- [Image Generation Guide](https://platform.minimaxi.com/docs/guides/image-generation)
- [Text-to-Image API](https://platform.minimaxi.com/docs/api-reference/image-generation-t2i)
- [Image-to-Image API](https://platform.minimaxi.com/docs/api-reference/image-generation-i2i)
@@ -60,8 +60,11 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
};
}
test("MiniMax URL builder normalizes /v1 suffixes", (t) => {
useEnv(t, { MINIMAX_BASE_URL: "https://api.minimax.io" });
test("MiniMax URL builder uses documented default and normalizes /v1 suffixes", (t) => {
useEnv(t, { MINIMAX_BASE_URL: null });
assert.equal(buildMinimaxUrl(), "https://api.minimaxi.com/v1/image_generation");
process.env.MINIMAX_BASE_URL = "https://api.minimax.io";
assert.equal(buildMinimaxUrl(), "https://api.minimax.io/v1/image_generation");
process.env.MINIMAX_BASE_URL = "https://proxy.example.com/custom/v1/";
@@ -44,7 +44,7 @@ function getApiKey(): string | null {
}
export function buildMinimaxUrl(): string {
const base = (process.env.MINIMAX_BASE_URL || "https://api.minimax.io").replace(/\/+$/g, "");
const base = (process.env.MINIMAX_BASE_URL || "https://api.minimaxi.com").replace(/\/+$/g, "");
return base.endsWith("/v1") ? `${base}/image_generation` : `${base}/v1/image_generation`;
}
@@ -197,7 +197,7 @@ export async function generateImage(
): Promise<Uint8Array> {
const apiKey = getApiKey();
if (!apiKey) {
throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimax.io/");
throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimaxi.com/");
}
const body = await buildRequestBody(prompt, model, args);
@@ -24,6 +24,6 @@ Read when the user picks `--provider minimax` or sets `default_model.minimax`. D
## Official References
- [Image Generation Guide](https://platform.minimax.io/docs/guides/image-generation)
- [Text-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-t2i)
- [Image-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-i2i)
- [Image Generation Guide](https://platform.minimaxi.com/docs/guides/image-generation)
- [Text-to-Image API](https://platform.minimaxi.com/docs/api-reference/image-generation-t2i)
- [Image-to-Image API](https://platform.minimaxi.com/docs/api-reference/image-generation-i2i)
@@ -61,8 +61,11 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
};
}
test("MiniMax URL builder normalizes /v1 suffixes", (t) => {
useEnv(t, { MINIMAX_BASE_URL: "https://api.minimax.io" });
test("MiniMax URL builder uses documented default and normalizes /v1 suffixes", (t) => {
useEnv(t, { MINIMAX_BASE_URL: null });
assert.equal(buildMinimaxUrl(), "https://api.minimaxi.com/v1/image_generation");
process.env.MINIMAX_BASE_URL = "https://api.minimax.io";
assert.equal(buildMinimaxUrl(), "https://api.minimax.io/v1/image_generation");
process.env.MINIMAX_BASE_URL = "https://proxy.example.com/custom/v1/";
@@ -44,7 +44,7 @@ function getApiKey(): string | null {
}
export function buildMinimaxUrl(): string {
const base = (process.env.MINIMAX_BASE_URL || "https://api.minimax.io").replace(/\/+$/g, "");
const base = (process.env.MINIMAX_BASE_URL || "https://api.minimaxi.com").replace(/\/+$/g, "");
return base.endsWith("/v1") ? `${base}/image_generation` : `${base}/v1/image_generation`;
}
@@ -197,7 +197,7 @@ export async function generateImage(
): Promise<Uint8Array> {
const apiKey = getApiKey();
if (!apiKey) {
throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimax.io/");
throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimaxi.com/");
}
const body = await buildRequestBody(prompt, model, args);
@@ -180,6 +180,10 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string, con
}, { sessionId });
await sleep(300);
console.log('[wechat] Activating HTML tab for copy...');
await cdp.send('Target.activateTarget', { targetId });
await sleep(300);
console.log('[wechat] Copying content...');
await sendCopy(cdp, sessionId);
await sleep(1000);
@@ -189,6 +193,11 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string, con
}
async function pasteFromClipboardInEditor(session: ChromeSession): Promise<void> {
console.log('[wechat] Activating editor tab for paste...');
if (session.targetId) {
await session.cdp.send('Target.activateTarget', { targetId: session.targetId });
await sleep(300);
}
console.log('[wechat] Pasting content...');
await sendPaste(session.cdp, session.sessionId);
await sleep(1000);
+145 -10
View File
@@ -1,7 +1,7 @@
---
name: baoyu-post-to-x
description: Posts content and articles to X (Twitter). Supports regular posts with images/videos and X Articles (long-form Markdown). Uses real Chrome with CDP to bypass anti-automation. Use when user asks to "post to X", "tweet", "publish to Twitter", or "share on X".
version: 1.56.2
description: Posts content and articles to X (Twitter). Supports regular posts with images/videos and X Articles (long-form Markdown). In Codex, honor explicit requests for the Codex Chrome plugin/@chrome by using the Chrome Extension workflow; otherwise use Chrome Computer Use when available and fall back to real Chrome CDP scripts only when allowed. Use when user asks to "post to X", "tweet", "publish to Twitter", or "share on X".
version: 1.57.2
metadata:
openclaw:
homepage: https://github.com/JimLiu/baoyu-skills#baoyu-post-to-x
@@ -13,7 +13,12 @@ metadata:
# Post to X (Twitter)
Posts text, images, videos, and long-form articles to X via real Chrome browser (bypasses anti-bot detection).
Posts text, images, videos, and long-form articles to X via a real Chrome browser.
In Codex, do not conflate these browser paths:
- **Codex Chrome plugin / `@chrome` / Chrome Extension**: use the bundled `chrome:Chrome` skill and its Node REPL browser client. This is required whenever the user says "Codex Chrome plugin", "Codex 自带的 Chrome 插件", `@chrome`, or similar.
- **Chrome Computer Use**: use `mcp__computer_use__.*` against the visible Google Chrome UI only when the user asks for Computer Use or no Chrome-plugin preference is stated and Computer Use is available.
- **CDP script mode**: use only as a fallback when the selected mode is unavailable or the user explicitly asks for CDP/script mode.
## Script Directory
@@ -28,15 +33,67 @@ Posts text, images, videos, and long-form articles to X via real Chrome browser
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/x-browser.ts` | Regular posts (text + images) |
| `scripts/x-video.ts` | Video posts (text + video) |
| `scripts/x-quote.ts` | Quote tweet with comment |
| `scripts/x-article.ts` | Long-form article publishing (Markdown) |
| `scripts/x-browser.ts` | Regular posts (text + images), CDP fallback |
| `scripts/x-video.ts` | Video posts (text + video), CDP fallback |
| `scripts/x-quote.ts` | Quote tweet with comment, CDP fallback |
| `scripts/x-article.ts` | Long-form article publishing (Markdown), CDP fallback |
| `scripts/md-to-html.ts` | Markdown → HTML conversion |
| `scripts/copy-to-clipboard.ts` | Copy content to clipboard |
| `scripts/paste-from-clipboard.ts` | Send real paste keystroke |
| `scripts/check-paste-permissions.ts` | Verify environment & permissions |
## Execution Mode Selection (Required)
Choose exactly one mode before interacting with X:
1. If the user explicitly asks for the Codex Chrome plugin, `@chrome`, the Chrome extension, or "Codex 自带的 Chrome 插件", use **Codex Chrome Plugin Mode**. Do not call Computer Use first.
2. If the user explicitly asks for Chrome Computer Use, use **Chrome Computer Use Mode**. Do not fall back to CDP, Playwright, the in-app Browser, or the Chrome plugin without telling the user and getting approval.
3. If the user explicitly asks for CDP/script mode, use **CDP Script Mode**.
4. Otherwise, prefer **Chrome Computer Use Mode**. For Markdown **X Articles with local content images**, use the tested X editor flow: insert each body image from the toolbar (`Insert` -> `Media` -> dialog icon button `Add photos or video`) at its placeholder, then delete the placeholder text. Use CDP Script Mode only when the selected browser-control mode is unavailable or the UI upload/selection flow is unreliable.
Never use the in-app Browser for X publishing workflows.
## Codex Chrome Plugin Mode
Use this mode whenever the user requests the Codex Chrome plugin, `@chrome`, or the Chrome Extension path. This uses the user's real Chrome profile and X login through the bundled Chrome plugin, not Computer Use and not CDP.
**Setup**
1. Load the `chrome:Chrome` skill before browser work.
2. Use `tool_search` for `node_repl js` if the Node REPL `js` tool is not already visible.
3. Initialize the Chrome browser client exactly as the Chrome skill specifies, then run a lightweight call such as `browser.user.openTabs()` to verify the extension connection.
4. If the first lightweight call fails, wait 2 seconds and retry once. If it still fails, follow the Chrome skill's extension checks and recovery steps. If checks pass but communication still fails, ask the user before opening a new Chrome window. Do not switch to Computer Use or CDP silently.
**General rules**
- Use the Chrome plugin's `browser.tabs.*`, `tab.playwright.*`, `tab.cua.*`, and file chooser APIs for X UI actions.
- Shell commands are allowed for Markdown preprocessing and rich-HTML clipboard preparation. For X Article body images, do not rely on image clipboard paste; use the editor's `Insert` -> `Media` upload flow.
- If a file upload fails with `Not allowed`, tell the user: `To enable file upload, go to chrome://extensions in Chrome, click Details under the Codex extension, and enable "Allow access to file URLs." See https://developers.openai.com/codex/app/chrome-extension#upload-files for details.`
- If the Chrome plugin reports `native pipe is closed`, retry the lightweight browser call once after 2 seconds, then run the Chrome skill health checks. If Chrome is running, the extension is enabled, and the native host manifest is correct, ask permission to open a new Chrome window and retry. Do not keep sending browser actions through the broken pipe.
- Never click `Publish`, `Post`, or any externally visible submit action without explicit final confirmation from the user in the current conversation.
**X Articles**
1. Convert Markdown and keep the image map:
```bash
${BUN_X} {baseDir}/scripts/md-to-html.ts article.md --save-html /tmp/x-article-body.html > /tmp/x-article.json
```
2. Read the JSON output for `title`, `coverImage`, and `contentImages` (`placeholder` → `localPath`).
3. Open or create the article draft at `https://x.com/compose/articles`.
4. Upload the cover with the Chrome plugin file chooser flow. If upload is blocked by extension permissions, stop and report the exact permission fix above.
5. Fill the title, then copy rich HTML:
```bash
${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts html --file /tmp/x-article-body.html
```
6. Paste into the article body with a real paste keystroke through the Chrome plugin. On macOS use `Meta+V`.
7. Verify the editor text contains the article body and `XIMGPH_` placeholders. Do not rely on `tab.clipboard.readText()` as proof of the system clipboard after shell clipboard writes; on macOS verify with `pbpaste` if needed.
8. For each `contentImages` item in placeholder order:
- Locate the visible placeholder text (`XIMGPH_N`) and click it to place the caret there.
- Open the toolbar menu `Insert` -> `Media`.
- In the modal, click the icon button with `aria-label="Add photos or video"`; do not click the text/dropzone or hidden file input.
- Use the file chooser to upload that image's `localPath`.
- After the image appears, if `XIMGPH_N` remains above it, select exactly that placeholder and press `Delete` first. Use `Backspace` only if `Delete` fails and the selected text is confirmed to be exactly the placeholder.
- Verify the placeholder count for that `XIMGPH_N` is `0`.
9. Open Preview and verify title, cover, body, links, and images.
10. Ask for explicit confirmation before clicking `Publish`.
## Preferences (EXTEND.md)
Check EXTEND.md in priority order — the first one found wins:
@@ -86,6 +143,73 @@ Checks: Chrome, profile isolation, Bun, Accessibility, clipboard, paste keystrok
---
## Chrome Computer Use Mode
Use this mode when the user explicitly asks for Chrome Computer Use, or when no Chrome-plugin preference is stated and Codex can control `Google Chrome` with Computer Use. This uses the user's existing Chrome window, cookies, login, extensions, and X session.
**General rules**:
- Start each assistant turn that controls Chrome by calling `get_app_state` for `Google Chrome`.
- Prefer element-index actions when available; use coordinates only for editor text selection or drag selection.
- Do not use the in-app Browser, the Chrome plugin, Playwright, or CDP for X UI actions in this mode unless the user approves a mode change.
- Never click `Publish`, `Post`, or any externally visible submit action without an explicit final confirmation from the user in the current conversation.
**Regular posts**:
1. Open or navigate Chrome to `https://x.com/compose/post`.
2. Type the post text into the composer using Computer Use.
3. For each image, run:
```bash
${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /absolute/path/to/image.png
```
4. Paste with Computer Use (`super+v` on macOS, `control+v` on Windows/Linux), then wait until X finishes uploading media.
5. Ask for confirmation before clicking `Post`.
**Video posts**:
1. Open or navigate Chrome to `https://x.com/compose/post`.
2. Type the post text into the composer.
3. Use the visible media upload/file picker UI to attach the video.
4. Wait for upload and processing to complete.
5. Ask for confirmation before clicking `Post`.
**Quote tweets**:
1. Open the tweet URL in Chrome.
2. Use the visible quote/repost UI to choose Quote.
3. Type the comment.
4. Ask for confirmation before clicking `Post`.
**X Articles**:
1. Convert Markdown and keep the image map:
```bash
${BUN_X} {baseDir}/scripts/md-to-html.ts article.md --save-html /tmp/x-article-body.html > /tmp/x-article.json
```
2. Read the JSON output for `title`, `coverImage`, and `contentImages` (`placeholder` → `localPath`).
3. In Chrome, open `https://x.com/compose/articles`, create or open the draft, upload the cover if present, and fill the title.
4. Copy rich HTML to the clipboard:
```bash
${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts html --file /tmp/x-article-body.html
```
5. Paste into the article body with Computer Use.
6. For each `contentImages` entry in placeholder order:
- Locate the exact visible placeholder text such as `XIMGPH_3` and click it to set the insertion point.
- Open the toolbar `Insert` dropdown, choose `Media`, then click the modal's icon button labeled `Add photos or video`.
- Use the native file picker to choose the image's `localPath`.
- Wait until the image block appears and any upload activity is finished.
- If the placeholder remains above the inserted image, reselect exactly that placeholder text and press `Delete` first. Use `Backspace` only if `Delete` fails and the selected text is confirmed to be exactly the placeholder.
7. Verify no `XIMGPH_` placeholders remain and the expected images appear.
8. Open Preview and verify title, cover, body, links, and images.
9. Ask for explicit confirmation before clicking `Publish`.
If Computer Use selection, toolbar upload, or file-picker control becomes unreliable, stop and report the blocker instead of switching to the Chrome plugin or CDP silently.
---
## CDP Script Mode (Fallback)
Use the script sections below only when the selected browser-control mode is unavailable, unreliable, or explicitly not requested. These scripts launch or reuse a real Chrome instance via CDP and keep the browser open for review.
Do not use CDP Script Mode when the user explicitly requires the Codex Chrome plugin or Chrome Computer Use unless the user approves the fallback after you explain the blocker.
---
## Post Type Selection
Unless the user explicitly specifies the post type:
@@ -107,6 +231,8 @@ ${BUN_X} {baseDir}/scripts/x-browser.ts "Hello!" --image ./photo.png
**Note**: Script opens browser with content filled in. User reviews and publishes manually.
**Codex mode note**: If the user explicitly requested the Codex Chrome plugin, use **Codex Chrome Plugin Mode**. Otherwise, if Chrome Computer Use is enabled, use **Chrome Computer Use Mode** instead of running `x-browser.ts`.
---
## Video Posts
@@ -126,6 +252,8 @@ ${BUN_X} {baseDir}/scripts/x-video.ts "Check this out!" --video ./clip.mp4
**Note**: Script opens browser with content filled in. User reviews and publishes manually.
**Codex mode note**: If the user explicitly requested the Codex Chrome plugin, use **Codex Chrome Plugin Mode**. Otherwise, if Chrome Computer Use is enabled, use **Chrome Computer Use Mode** instead of running `x-video.ts`.
**Limits**: Regular 140s max, Premium 60min. Processing: 30-60s.
---
@@ -147,6 +275,8 @@ ${BUN_X} {baseDir}/scripts/x-quote.ts https://x.com/user/status/123 "Great insig
**Note**: Script opens browser with content filled in. User reviews and publishes manually.
**Codex mode note**: If the user explicitly requested the Codex Chrome plugin, use **Codex Chrome Plugin Mode**. Otherwise, if Chrome Computer Use is enabled, use **Chrome Computer Use Mode** instead of running `x-quote.ts`.
---
## X Articles
@@ -167,7 +297,11 @@ ${BUN_X} {baseDir}/scripts/x-article.ts article.md --cover ./cover.jpg
**Frontmatter**: `title`, `cover_image` supported in YAML front matter.
**Note**: Script opens browser with article filled in. User reviews and publishes manually.
**Codex mode note**: If the user explicitly requested the Codex Chrome plugin, follow **Codex Chrome Plugin Mode** above. If the user explicitly requested Chrome Computer Use, follow **Chrome Computer Use Mode**. Otherwise, prefer Chrome Computer Use; for Markdown articles with local content images, use the toolbar `Insert` -> `Media` image-upload workflow before falling back to `x-article.ts` in **CDP Script Mode**.
**CDP fallback note**: The script opens browser with article filled in. User reviews and publishes manually unless `--submit` is used.
**Publish safety**: Do not use `--submit` or click `Publish` unless the user explicitly confirms the final public publish action.
**Post-Composition Check**: The script automatically verifies after all images are inserted:
- Remaining `XIMGPH_` placeholders in editor content
@@ -181,7 +315,7 @@ If the check fails (warnings in output), alert the user with the specific issues
### Chrome debug port not ready
If a script fails with `Chrome debug port not ready` or `Unable to connect`, kill existing Chrome CDP instances first, then retry:
CDP fallback only: if a script fails with `Chrome debug port not ready` or `Unable to connect`, kill existing Chrome CDP instances first, then retry:
```bash
pkill -f "Chrome.*remote-debugging-port" 2>/dev/null; pkill -f "Chromium.*remote-debugging-port" 2>/dev/null; sleep 2
@@ -192,7 +326,8 @@ pkill -f "Chrome.*remote-debugging-port" 2>/dev/null; pkill -f "Chromium.*remote
## Notes
- First run: manual login required (session persists)
- All scripts only fill content into the browser, user must review and publish manually
- In Codex Chrome Plugin Mode and Chrome Computer Use Mode, use the user's existing Chrome session and do not launch a separate CDP profile
- CDP scripts only fill content into the browser by default; user must review and publish manually unless `--submit` is explicitly used
- Cross-platform: macOS, Linux, Windows
## Extension Support
+139 -15
View File
@@ -2,6 +2,17 @@
Publish Markdown articles to X Articles editor with rich text formatting and images.
## Mode Selection
In Codex, choose the browser-control mode from the user's wording:
1. If the user says "Codex Chrome plugin", "Codex 自带的 Chrome 插件", `@chrome`, or Chrome Extension, use **Codex Chrome Plugin Workflow**. Do not try Computer Use first.
2. If the user explicitly asks for Chrome Computer Use, use **Computer Use Workflow**.
3. If the user explicitly asks for CDP/script mode, use **CDP Script Workflow**.
4. Otherwise, use Computer Use when available; if unavailable or blocked, use CDP Script Workflow.
Never use the in-app Browser for X Article publishing. Never switch away from an explicitly requested mode without explaining the blocker and getting approval.
## Prerequisites
- X Premium subscription (required for Articles)
@@ -10,6 +21,42 @@ Publish Markdown articles to X Articles editor with rich text formatting and ima
## Usage
### Codex Chrome Plugin (When Requested)
Use the `chrome:Chrome` skill and its Node REPL browser client. Verify the connection with a lightweight call such as `browser.user.openTabs()`. If it fails, wait 2 seconds and retry once, then follow the Chrome skill's health checks.
Prepare the article HTML and image map:
```bash
${BUN_X} {baseDir}/scripts/md-to-html.ts article.md --save-html /tmp/x-article-body.html > /tmp/x-article.json
```
Copy generated HTML as rich text:
```bash
${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts html --file /tmp/x-article-body.html
```
Use the Chrome plugin's tab, Playwright-wrapper, CUA, clipboard, and file chooser APIs for all X UI operations. If upload fails with `Not allowed`, stop and tell the user to enable file URL access for the Codex Chrome Extension in `chrome://extensions` → Details.
### Chrome Computer Use
Prepare the article HTML and image map:
```bash
${BUN_X} {baseDir}/scripts/md-to-html.ts article.md --save-html /tmp/x-article-body.html > /tmp/x-article.json
```
Copy the generated HTML as rich text:
```bash
${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts html --file /tmp/x-article-body.html
```
Then use Codex Computer Use against `Google Chrome` for all X UI operations.
### CDP Script Fallback
```bash
# Publish markdown article (preview mode)
${BUN_X} {baseDir}/scripts/x-article.ts article.md
@@ -21,6 +68,8 @@ ${BUN_X} {baseDir}/scripts/x-article.ts article.md --cover ./cover.jpg
${BUN_X} {baseDir}/scripts/x-article.ts article.md --submit
```
Do not use `--submit` unless the user has explicitly confirmed the final public publish action.
## Markdown Format
```markdown
@@ -118,7 +167,63 @@ JSON output:
| `1. item` | `<ol><li>` |
| `![](img)` | Image placeholder |
## Workflow
## Codex Chrome Plugin Workflow
1. **Load Chrome skill**: use `chrome:Chrome`, not Computer Use.
2. **Connect**: initialize the Chrome plugin browser client and verify with `browser.user.openTabs()`.
3. **Parse Markdown**: run `md-to-html.ts --save-html /tmp/x-article-body.html > /tmp/x-article.json`.
4. **Read the map**: use `/tmp/x-article.json` for `title`, `coverImage`, and `contentImages`.
5. **Open X Articles**: open or claim a Chrome tab for `https://x.com/compose/articles`.
6. **Create Draft**: click the create/write button if needed, or open the target draft.
7. **Upload Cover**: use the Chrome plugin file chooser flow. If file upload returns `Not allowed`, report the Chrome Extension file-access fix and stop.
8. **Fill Title**: fill the title field.
9. **Paste Content**:
- Run `copy-to-clipboard.ts html --file /tmp/x-article-body.html`.
- Click the article body.
- Press `Meta+V` on macOS or `Control+V` on Windows/Linux through the Chrome plugin.
- Verify the article body appeared and contains `XIMGPH_` placeholders. On macOS, use `pbpaste` to verify shell-written system clipboard contents if paste is suspicious; `tab.clipboard.readText()` may not reflect the system clipboard after shell writes.
10. **Insert Images**: for each `contentImages` item in placeholder order:
- Locate the exact visible placeholder text (`XIMGPH_N`) and click it to put the insertion point there.
- Open the editor toolbar dropdown `Insert` and choose `Media`.
- In the `Insert` modal, click the icon button with `aria-label="Add photos or video"`; do not click the "Choose a file or drag it here" text/dropzone or hidden file input.
- Use the Chrome plugin file chooser flow to upload that image's `localPath`.
- Wait until the image block appears. If `XIMGPH_N` remains above the image, select exactly that placeholder and press `Delete` first; use `Backspace` only if `Delete` fails and the selected text is confirmed to be exactly the placeholder.
- Verify that placeholder's count is `0` before continuing.
11. **Verify**:
- Inspect the editor for `XIMGPH_` residue.
- Confirm the expected number of image blocks is visible.
- Open Preview and verify title, cover, body, links, and images.
12. **Publish Safety**: ask the user for explicit final confirmation before clicking `Publish`.
If the Chrome plugin reports `native pipe is closed`, retry one lightweight browser call after 2 seconds, then run the Chrome skill health checks. If Chrome, the extension, and native host are healthy, ask the user before opening a new Chrome window and retrying.
## Computer Use Workflow
1. **Detect Computer Use**: call `get_app_state` for `Google Chrome`; use `tool_search` first if the tools are not visible.
2. **Parse Markdown**: run `md-to-html.ts --save-html /tmp/x-article-body.html > /tmp/x-article.json`.
3. **Read the map**: use `/tmp/x-article.json` for `title`, `coverImage`, and `contentImages`.
4. **Open X Articles**: use Chrome Computer Use to navigate to `https://x.com/compose/articles`.
5. **Create Draft**: click the create/write button if needed, or open the target draft.
6. **Upload Cover**: if `coverImage` exists, use Chrome's visible upload/file picker UI. If the file picker cannot be operated reliably, stop and ask for help rather than switching to CDP silently.
7. **Fill Title**: type the title into the title field.
8. **Paste Content**:
- Run `copy-to-clipboard.ts html --file /tmp/x-article-body.html`.
- Click the article body.
- Press `super+v` on macOS or `control+v` on Windows/Linux with Computer Use.
9. **Insert Images**: for each `contentImages` item in placeholder order:
- Locate the exact visible placeholder text (`XIMGPH_N`) and click it to put the insertion point there.
- Open the editor toolbar dropdown `Insert`, choose `Media`, then click the icon button with `aria-label="Add photos or video"` inside the modal.
- Use the native file picker to choose that image's `localPath`.
- Wait until the image block appears and upload activity is complete.
- If `XIMGPH_N` remains above the inserted image, reselect exactly that placeholder text and press `Delete` first; use `Backspace` only if `Delete` fails and the Computer Use state confirms the selected text is exactly the placeholder.
- Confirm that placeholder is gone before continuing.
10. **Verify**:
- Inspect the Computer Use state for `XIMGPH_` residue.
- Confirm the expected number of image blocks is visible.
- Open Preview and verify title, cover, body, links, and images.
11. **Publish Safety**: ask the user for explicit final confirmation before clicking `Publish`.
## CDP Script Workflow (Fallback)
1. **Parse Markdown**: Extract title, cover, content images, generate HTML
2. **Launch Chrome**: Real browser with CDP, persistent login
@@ -127,17 +232,18 @@ JSON output:
5. **Upload Cover**: Use file input for cover image
6. **Fill Title**: Type title into title field
7. **Paste Content**: Copy HTML to clipboard, paste into editor
8. **Insert Images**: For each placeholder (reverse order):
- Find placeholder text in editor
- Select the placeholder
- Copy image to clipboard
- Paste to replace selection
8. **Insert Images**: For each placeholder in placeholder order:
- Find and click the placeholder text in the editor
- Use `Insert` -> `Media`
- Click the modal's icon button labeled `Add photos or video`
- Upload the matching image file
- Delete the leftover placeholder text with `Delete` after the image appears
9. **Post-Composition Check** (automatic):
- Scan editor for remaining `XIMGPH_` placeholders
- Compare expected vs actual image count
- Warn if issues found
10. **Review**: Browser stays open for 60s preview
11. **Publish**: Only with `--submit` flag
11. **Publish**: Only with `--submit` flag and explicit user confirmation
## Example Session
@@ -145,23 +251,27 @@ JSON output:
User: /post-to-x article ./blog/my-post.md --cover ./thumbnail.png
Claude:
1. Parses markdown: title="My Post", 3 content images
2. Launches Chrome with CDP
3. Navigates to x.com/compose/articles
4. Clicks create button
1. Detects that the user requested the Codex Chrome plugin
2. Parses markdown: title="My Post", 3 content images
3. Saves `/tmp/x-article-body.html` and `/tmp/x-article.json`
4. Uses the Chrome plugin to open X Articles and create a draft
5. Uploads thumbnail.png as cover
6. Fills title "My Post"
7. Pastes HTML content
7. Pastes HTML content with a real Chrome paste
8. Inserts 3 images at placeholder positions
9. Reports: "Article composed. Review and use --submit to publish."
9. Opens Preview and asks before publishing
```
## Troubleshooting
- **No create button**: Ensure X Premium subscription is active
- **Cover upload fails**: Check file path and format (PNG, JPEG)
- **Images not inserting**: Verify placeholders exist in pasted content
- **Images not inserting**: Verify placeholders exist in pasted content; use `Insert` -> `Media` -> modal icon button `Add photos or video`, not image clipboard paste, the dropzone text, or the hidden file input.
- **Content not pasting**: Check HTML clipboard: `${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts html --file /tmp/test.html`
- **Chrome plugin `native pipe is closed`**: retry once after 2 seconds, then run Chrome skill checks; ask before opening a new Chrome window if checks pass.
- **Chrome plugin upload `Not allowed`**: enable file URL access for the Codex Chrome Extension in `chrome://extensions` → Details.
- **Computer Use unavailable**: Use the CDP fallback script, unless the user explicitly required Chrome Computer Use.
- **Placeholder remains after upload**: Select only the placeholder text and press `Delete` after upload completes. Use `Backspace` only if `Delete` fails and the selection is exactly the placeholder.
## How It Works
@@ -172,7 +282,21 @@ Claude:
- Downloads remote images locally
- Returns structured JSON
2. `x-article.ts` publishes via CDP:
2. The Codex Chrome plugin publishes through the user's real Chrome session when explicitly requested:
- Uses the user's active Chrome profile and logged-in X session
- Uses the Chrome Extension browser client rather than Computer Use or CDP
- Uses `copy-to-clipboard.ts` for rich HTML body paste
- Inserts body images through X's toolbar `Insert` -> `Media` modal and its `Add photos or video` icon button
- Keeps the final publish click under user confirmation
3. Chrome Computer Use publishes through the user's visible Chrome UI:
- Uses the user's active Chrome profile and logged-in X session
- Uses `copy-to-clipboard.ts` for rich HTML body paste
- Inserts body images through X's toolbar `Insert` -> `Media` modal and its `Add photos or video` icon button
- Uses real keystrokes (`super+v`/`control+v`) through Codex Computer Use
- Keeps the final publish click under user confirmation
4. `x-article.ts` publishes via CDP as a fallback:
- Launches real Chrome (bypasses detection)
- Uses persistent profile (saved login)
- Navigates and fills editor via DOM manipulation
@@ -6,6 +6,17 @@ Detailed documentation for posting text and images to X.
If you prefer step-by-step control:
### Step 0: Prefer Chrome Computer Use in Codex
When running inside Codex, first detect whether Chrome Computer Use is enabled:
1. If Computer Use tools are already visible, call `get_app_state` for `Google Chrome`.
2. If not, use `tool_search` for `computer-use get_app_state click press_key drag scroll Google Chrome`, then call `get_app_state`.
3. If `get_app_state` succeeds, use the user's real Chrome with Computer Use for all X UI actions.
4. Use CDP scripts only when Computer Use is unavailable or explicitly requested.
If the user explicitly asks for Chrome Computer Use, do not use Playwright, the in-app Browser, or CDP without approval.
### Step 1: Copy Image to Clipboard
```bash
@@ -25,27 +36,15 @@ ${BUN_X} {baseDir}/scripts/paste-from-clipboard.ts --app "Google Chrome" --retri
${BUN_X} {baseDir}/scripts/paste-from-clipboard.ts --delay 200
```
### Step 3: Use Playwright MCP (if Chrome session available)
### Step 3: Use Chrome Computer Use (Preferred)
```bash
# Navigate
mcp__playwright__browser_navigate url="https://x.com/compose/post"
# Get element refs
mcp__playwright__browser_snapshot
# Type text
mcp__playwright__browser_click element="editor" ref="<ref>"
mcp__playwright__browser_type element="editor" ref="<ref>" text="Your content"
# Paste image (after copying to clipboard)
mcp__playwright__browser_press_key key="Meta+v" # macOS
# or
mcp__playwright__browser_press_key key="Control+v" # Windows/Linux
# Screenshot to verify
mcp__playwright__browser_take_screenshot filename="preview.png"
```
1. Use `get_app_state` for `Google Chrome`.
2. Navigate Chrome to `https://x.com/compose/post` if needed.
3. Click the composer and type the post text.
4. Copy each image to the clipboard with `copy-to-clipboard.ts image <path>`.
5. Press `super+v` on macOS or `control+v` on Windows/Linux with Computer Use.
6. Wait until X finishes media upload.
7. Ask for explicit confirmation before clicking `Post`.
## Image Support
@@ -59,12 +58,12 @@ mcp__playwright__browser_take_screenshot filename="preview.png"
User: /post-to-x "Hello from Claude!" --image ./screenshot.png
Claude:
1. Runs: ${BUN_X} {baseDir}/scripts/x-browser.ts "Hello from Claude!" --image ./screenshot.png
2. Chrome opens with X compose page
3. Text is typed into editor
4. Image is copied to clipboard and pasted
5. Browser stays open 30s for preview
6. Reports: "Post composed. Use --submit to post."
1. Detects Chrome Computer Use
2. Opens X compose in the user's real Chrome
3. Types text into editor
4. Copies image to clipboard and pastes with Computer Use
5. Waits for upload and verifies the preview
6. Asks before clicking Post
```
## Troubleshooting
@@ -80,7 +79,13 @@ Claude:
## How It Works
The `x-browser.ts` script uses Chrome DevTools Protocol (CDP) to:
In Chrome Computer Use mode:
1. Codex controls the user's visible Google Chrome window
2. Text is typed through the real UI
3. Images are copied to the system clipboard and pasted with real keystrokes
4. The user confirms before the final public post
The `x-browser.ts` script is the CDP fallback. It uses Chrome DevTools Protocol (CDP) to:
1. Launch real Chrome (not Playwright) with `--disable-blink-features=AutomationControlled`
2. Use persistent profile directory for saved login sessions
3. Interact with X via CDP commands (Runtime.evaluate, Input.dispatchKeyEvent)
+35 -1
View File
@@ -164,7 +164,7 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
// Check if we're on the articles list page (has Write button)
console.log('[x-article] Looking for Write button...');
const writeButtonFound = await waitForElement('[data-testid="empty_state_button_text"]', 10_000);
const writeButtonFound = await waitForElement('[data-testid="empty_state_button_text"]', 30_000);
if (writeButtonFound) {
console.log('[x-article] Clicking Write button...');
@@ -172,6 +172,40 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
expression: `document.querySelector('[data-testid="empty_state_button_text"]')?.click()`,
}, { sessionId });
await sleep(2000);
} else {
console.log('[x-article] Write button not found, looking for create button...');
const createClicked = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
expression: `(() => {
const selectors = [
'button[aria-label="create"]',
'button[aria-label="Create"]',
'button[aria-label*="create" i]',
'button[aria-label*="write" i]',
'a[href="/compose/articles"]'
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el instanceof HTMLElement) {
el.click();
return true;
}
}
const buttons = [...document.querySelectorAll('button')];
const byText = buttons.find((el) => /^(create|write)$/i.test((el.textContent || '').trim()));
if (byText instanceof HTMLElement) {
byText.click();
return true;
}
return false;
})()`,
returnByValue: true,
}, { sessionId });
if (createClicked.result.value) {
console.log('[x-article] Create button clicked');
await sleep(2000);
} else {
console.log('[x-article] Create button not found');
}
}
// Wait for editor (title textarea)
@@ -218,6 +218,7 @@ async function launchChromeOnce(
try {
await waitForChromeDebugPort(port, 30_000, { includeLastError: true });
chrome.unref();
return { chrome, port };
} catch (error) {
killChrome(chrome);