mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 13:59:47 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dea9322d0d | |||
| 86b1be83b8 | |||
| a1b935b2d8 | |||
| 9968d79a26 | |||
| 6f75fb17e4 | |||
| adba24281b | |||
| 61342ecfea | |||
| 5f753dd584 | |||
| 076192d58e | |||
| d6d434e714 | |||
| 6f3600d8e5 | |||
| 045fe5e57e | |||
| 0b3b7d13b5 | |||
| aa1a967a9f | |||
| d643bad53c | |||
| 0d977787b5 | |||
| 516803feb4 | |||
| 4af0506fa3 | |||
| 80a1c2970a | |||
| cdfa0dbff9 |
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||
"version": "1.113.0"
|
||||
"version": "1.115.4"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: release-skills
|
||||
description: Universal release workflow. Auto-detects version files and changelogs. Supports Node.js, Python, Rust, Claude Plugin, and generic projects. Use when user says "release", "发布", "new version", "bump version", "push", "推送".
|
||||
description: Universal release workflow. Auto-detects version files and changelogs. Supports Node.js, Python, Rust, Claude Plugin, GitHub Releases, annotated tags, historical release backfill, and generic projects. Use when user says "release", "发布", "new version", "bump version", "push", "推送", "release notes", "GitHub Release", or "回填 Release".
|
||||
---
|
||||
|
||||
# Release Skills
|
||||
@@ -39,6 +39,7 @@ Just run `/release-skills` - auto-detects your project configuration.
|
||||
| `--major` | Force major version bump |
|
||||
| `--minor` | Force minor version bump |
|
||||
| `--patch` | Force patch version bump |
|
||||
| `--backfill-releases` | Create missing GitHub Releases for existing tags from changelog sections |
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -57,7 +58,11 @@ Just run `/release-skills` - auto-detects your project configuration.
|
||||
- `HISTORY*.md`
|
||||
- `CHANGES*.md`
|
||||
4. Identify language of each changelog by filename suffix
|
||||
5. Display detected configuration
|
||||
5. Detect GitHub release support:
|
||||
- Check whether `origin` points to GitHub
|
||||
- Check whether `gh` is installed and authenticated
|
||||
- Check existing releases with `gh release list --limit 5` when available
|
||||
6. Display detected configuration
|
||||
|
||||
**Project Hook Contract**:
|
||||
|
||||
@@ -310,6 +315,14 @@ git commit -m "docs(project): update architecture documentation"
|
||||
- Read version file (JSON/TOML/text)
|
||||
- Update version number
|
||||
- Write back (preserve formatting)
|
||||
3. **Create release notes file**:
|
||||
- Prefer the new version section from `CHANGELOG.md`
|
||||
- If no English/default changelog exists, use the first detected changelog
|
||||
- Extract only the exact `## {VERSION} - {YYYY-MM-DD}` section through the next `##`
|
||||
- Match both plain version and tag-prefixed headings when needed, e.g. `1.2.3` and `v1.2.3`
|
||||
- Keep breaking changes near the top; if needed, add a short highlight before other sections
|
||||
- Write notes to a UTF-8 temp file and reuse it for annotated tag messages, GitHub Releases, and `publish_artifact`
|
||||
- In normal mode, stop rather than creating an empty tag or GitHub Release when notes cannot be found
|
||||
|
||||
**Version Paths by File Type**:
|
||||
|
||||
@@ -325,7 +338,7 @@ git commit -m "docs(project): update architecture documentation"
|
||||
|
||||
Before creating the release commit, ask user to confirm:
|
||||
|
||||
**Use AskUserQuestion with two questions**:
|
||||
**Use AskUserQuestion with three questions**:
|
||||
|
||||
1. **Version bump** (single select):
|
||||
- Show recommended version based on Step 3 analysis
|
||||
@@ -335,6 +348,11 @@ Before creating the release commit, ask user to confirm:
|
||||
2. **Push to remote** (single select):
|
||||
- Options: "Yes, push after commit", "No, keep local only"
|
||||
|
||||
3. **Publish GitHub Release** (single select):
|
||||
- Offer this only when GitHub release support is available
|
||||
- Default to "Yes, publish after tag push" when the user also chose push
|
||||
- If the user keeps the release local, do not create or edit a GitHub Release
|
||||
|
||||
**Example Output Before Confirmation**:
|
||||
```
|
||||
Commits created:
|
||||
@@ -349,10 +367,11 @@ Changelog preview (en):
|
||||
### Fixes
|
||||
- Improve panel layout for long dialogues in comic
|
||||
|
||||
Ready to create release commit and tag.
|
||||
Release notes source: CHANGELOG.md#1.3.0
|
||||
Ready to create release commit, annotated tag, and GitHub Release.
|
||||
```
|
||||
|
||||
### Step 9: Create Release Commit and Tag
|
||||
### Step 9: Create Release Commit and Annotated Tag
|
||||
|
||||
After user confirmation:
|
||||
|
||||
@@ -367,10 +386,11 @@ After user confirmation:
|
||||
git commit -m "chore: release v{VERSION}"
|
||||
```
|
||||
|
||||
3. **Create tag**:
|
||||
3. **Create annotated tag**:
|
||||
```bash
|
||||
git tag v{VERSION}
|
||||
git tag -a v{VERSION} -F <release-notes-file>
|
||||
```
|
||||
If `.releaserc.yml` sets `tag.sign: true`, use `git tag -s` with the same notes file.
|
||||
|
||||
4. **Push if user confirmed** (Step 8):
|
||||
```bash
|
||||
@@ -380,6 +400,28 @@ After user confirmation:
|
||||
|
||||
**Note**: Do NOT add Co-Authored-By line. This is a release commit, not a code contribution.
|
||||
|
||||
### Step 10: Publish Release Artifacts and GitHub Release
|
||||
|
||||
Project artifact publishing and GitHub Releases are separate outputs:
|
||||
|
||||
1. **Project artifacts**:
|
||||
- If `release.hooks.publish_artifact` exists, run it once per prepared target
|
||||
- Pass the same `{release_notes_file}` used for the tag and GitHub Release
|
||||
- In dry-run mode, pass `{dry_run}=true` and report what would be published
|
||||
|
||||
2. **GitHub Release**:
|
||||
- Run only if the user confirmed remote publishing and GitHub support is available
|
||||
- Ensure the tag exists on the remote before creating the release
|
||||
- Create or update using the extracted notes:
|
||||
```bash
|
||||
if gh release view v{VERSION} >/dev/null 2>&1; then
|
||||
gh release edit v{VERSION} --title "v{VERSION}" --notes-file <release-notes-file>
|
||||
else
|
||||
gh release create v{VERSION} --title "v{VERSION}" --notes-file <release-notes-file> --verify-tag
|
||||
fi
|
||||
```
|
||||
- Never inline multiline release notes into shell commands
|
||||
|
||||
**Post-Release Output**:
|
||||
```
|
||||
Release v1.3.0 created.
|
||||
@@ -391,9 +433,32 @@ Commits:
|
||||
4. chore: release v1.3.0
|
||||
|
||||
Tag: v1.3.0
|
||||
Tag type: annotated
|
||||
GitHub Release: published # or "skipped/local only"
|
||||
Status: Pushed to origin # or "Local only - run git push when ready"
|
||||
```
|
||||
|
||||
## Backfill Existing GitHub Releases
|
||||
|
||||
Use this mode when the user asks to backfill historical releases or passes `--backfill-releases`.
|
||||
|
||||
1. Do not bump versions, edit changelogs, or create release commits.
|
||||
2. List existing tags in version order and detect missing releases:
|
||||
```bash
|
||||
git tag --sort=v:refname
|
||||
gh release view <tag>
|
||||
```
|
||||
3. For each tag without a GitHub Release:
|
||||
- Normalize the changelog lookup by stripping the configured tag prefix, e.g. `v1.2.3` -> `1.2.3`
|
||||
- Extract the matching section from `CHANGELOG.md`; fall back to the first matching changelog file
|
||||
- Skip or ask before publishing if no matching changelog section exists
|
||||
- Create the release with:
|
||||
```bash
|
||||
gh release create <tag> --title "<tag>" --notes-file <release-notes-file> --verify-tag
|
||||
```
|
||||
4. Detect lightweight tags with `git cat-file -t <tag>` (`commit` means lightweight, `tag` means annotated).
|
||||
5. Do not rewrite public lightweight tags by default. Converting an existing remote tag to an annotated tag requires explicit user confirmation because it rewrites a published reference.
|
||||
|
||||
## Configuration (.releaserc.yml)
|
||||
|
||||
Optional config file in project root to override defaults:
|
||||
@@ -498,6 +563,7 @@ No changes made. Run without --dry-run to execute.
|
||||
/release-skills --minor # Force minor bump
|
||||
/release-skills --patch # Force patch bump
|
||||
/release-skills --major # Force major bump (with confirmation)
|
||||
/release-skills --backfill-releases # Create missing GitHub Releases for existing tags
|
||||
```
|
||||
|
||||
## When to Use
|
||||
@@ -506,6 +572,7 @@ Trigger this skill when user requests:
|
||||
- "release", "发布", "create release", "new version", "新版本"
|
||||
- "bump version", "update version", "更新版本"
|
||||
- "prepare release"
|
||||
- "release notes", "GitHub Release", "回填 Release"
|
||||
- "push to remote" (with uncommitted changes)
|
||||
|
||||
**Important**: If user says "just push" or "直接 push" with uncommitted changes, STILL follow all steps above first.
|
||||
|
||||
@@ -173,3 +173,5 @@ diagram/
|
||||
.worktrees/
|
||||
youtube-transcript/
|
||||
.omx/
|
||||
.codex-tmp/
|
||||
outputs/
|
||||
|
||||
+42
-1
@@ -2,6 +2,47 @@
|
||||
|
||||
English | [中文](./CHANGELOG.zh.md)
|
||||
|
||||
## 1.115.4 - 2026-05-11
|
||||
|
||||
### Documentation
|
||||
- Image generation backend selection: emphasize Codex `imagegen` as the priority runtime-native tool (invoke via the `Skill` tool with `skill: "imagegen"`) and forbid SVG/HTML/canvas substitution when no raster backend can be resolved — fall through to asking the user instead of silently emitting code-based art. Updated in `docs/image-generation-tools.md` and inlined into `baoyu-article-illustrator`, `baoyu-comic`, `baoyu-cover-image`, `baoyu-image-cards`, `baoyu-infographic`, `baoyu-slide-deck`, and `baoyu-xhs-images`.
|
||||
|
||||
## 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
|
||||
- `baoyu-danger-gemini-web`: restore generated-image extraction for current Gemini Web responses where image URLs appear as `https://lh3.googleusercontent.com/gg-dl/` without the legacy generated-image markers. Adds regression coverage for the fallback response shape. (by @evilstar2016)
|
||||
|
||||
## 1.114.0 - 2026-05-05
|
||||
|
||||
### Features
|
||||
- `baoyu-infographic`: add `retro-popup-pop` style — retro pixel popup × pop-art collage. Renders content as a stack of 80/90s desktop popup windows (title bars, close buttons, ERROR / ALERT dialogs, file windows like `PROBLEMS.EXE`, progress bars, OK / CANCEL / FIX IT buttons) with thick black outlines, flat color fills, and bright cyan (#12B8DE) or vintage cream (#F5F0E6) backgrounds. Pairs especially well with the `dense-modules` layout; promoted as a recommended style for the `高密度信息大图` keyword shortcut and the `Product/Buying Guide` content type. Style Gallery count goes from 21 to 22.
|
||||
Credit to AJ@WaytoAGI.
|
||||
|
||||
### Documentation
|
||||
- `release-skills`: document GitHub Release publishing in the release workflow, including release-notes extraction from changelog sections, annotated tag creation, `gh release create/edit`, and historical release backfill for existing tags.
|
||||
|
||||
## 1.113.0 - 2026-04-25
|
||||
|
||||
### Features
|
||||
@@ -682,7 +723,7 @@ English | [中文](./CHANGELOG.zh.md)
|
||||
- `baoyu-format-markdown`: add reader-perspective content analysis phase — analyzes highlights, structure, and formatting issues before applying formatting
|
||||
- `baoyu-format-markdown`: restructure workflow from 8 steps to 7 with explicit do/don't formatting principles and completion report
|
||||
- `baoyu-translate`: extract Step 2 workflow mechanics to separate reference file for cleaner SKILL.md
|
||||
- `baoyu-translate`: expand trigger keywords (改成中文, 快翻, 本地化, etc.) for better skill activation
|
||||
- `baoyu-translate`: expand trigger keywords (改成中文,快翻,本地化,etc.) for better skill activation
|
||||
- `baoyu-translate`: add proactive warning for long content in quick mode
|
||||
- `baoyu-translate`: save frontmatter to `chunks/frontmatter.md` during chunking
|
||||
|
||||
|
||||
+45
-4
@@ -2,6 +2,47 @@
|
||||
|
||||
[English](./CHANGELOG.md) | 中文
|
||||
|
||||
## 1.115.4 - 2026-05-11
|
||||
|
||||
### 文档
|
||||
- 图片生成后端选择规则强化:明确将 Codex `imagegen` 作为运行时原生工具的优先项(通过 `Skill` 工具调用,`skill: "imagegen"`),并禁止在无可用光栅后端时降级为 SVG/HTML/canvas 等代码渲染 —— 应退回到询问用户,而非静默输出代码绘图。规则同步更新到 `docs/image-generation-tools.md`,并按自包含规则内联到 `baoyu-article-illustrator`、`baoyu-comic`、`baoyu-cover-image`、`baoyu-image-cards`、`baoyu-infographic`、`baoyu-slide-deck`、`baoyu-xhs-images`。
|
||||
|
||||
## 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
|
||||
|
||||
### 修复
|
||||
- `baoyu-danger-gemini-web`:修复当前 Gemini Web 响应中生成图 URL 以 `https://lh3.googleusercontent.com/gg-dl/` 形式出现、但不再包含旧版生成图 marker 时的图片提取失败问题。补充该响应形态的回归测试。 (by @evilstar2016)
|
||||
|
||||
## 1.114.0 - 2026-05-05
|
||||
|
||||
### 新功能
|
||||
- `baoyu-infographic`:新增 `retro-popup-pop` 风格 —— 复古像素弹窗 × 波普信息图。画面由多个 80/90 年代桌面弹窗叠加而成(标题栏、关闭按钮、ERROR / ALERT 报错对话框、`PROBLEMS.EXE` 等复古文件窗、进度条、OK / CANCEL / FIX IT 按钮),统一粗黑描边、平涂色块,背景使用亮青蓝(#12B8DE)或复古奶油色(#F5F0E6)。与 `dense-modules` 布局尤其契合;同时升级为 `高密度信息大图` 关键词快捷方式与 `Product/Buying Guide` 内容类型的推荐风格。风格库从 21 个扩展至 22 个。
|
||||
Credit to AJ@WaytoAGI.
|
||||
|
||||
### 文档
|
||||
- `release-skills`:补充 GitHub Release 发布流程,包括从 changelog 段落提取 release notes、创建 annotated tag、执行 `gh release create/edit`,以及为已有 tag 回填历史 GitHub Releases。
|
||||
|
||||
## 1.113.0 - 2026-04-25
|
||||
|
||||
### 新功能
|
||||
@@ -29,7 +70,7 @@
|
||||
|
||||
### 重构
|
||||
- 统一所有图片生成类技能(`baoyu-infographic`、`baoyu-comic`、`baoyu-cover-image`、`baoyu-image-cards`、`baoyu-article-illustrator`、`baoyu-slide-deck`、`baoyu-xhs-images`)的后端选择规则:新增单一 `preferred_image_backend` 偏好字段(`auto | ask | <backend-id>`),用 4 步解析规则(当前请求覆盖 → 已保存偏好 → 自动选择 → 询问用户)替换原有的无状态询问规则。默认优先使用运行时原生工具(如 Codex `imagegen`、Hermes `image_generate`);未设置该字段的现有 `EXTEND.md` 文件视为 `auto`,无需升级 schema 版本。
|
||||
- 在每个图片技能中新增顶级 `## Changing Preferences` 章节,作为固定后端和修改常用偏好的一级入口。
|
||||
- 在每个图片技能中新增顶级 `## Changing Preferences` 章节,作为固定后端和修改常用偏好的一级入口。
|
||||
|
||||
## 1.110.0 - 2026-04-21
|
||||
|
||||
@@ -638,7 +679,7 @@
|
||||
## 1.52.0 - 2026-03-06
|
||||
|
||||
### 新功能
|
||||
- `baoyu-post-to-weibo`:新增 `--video` 视频上传支持(图片+视频最多 18 个文件)
|
||||
- `baoyu-post-to-weibo`:新增 `--video` 视频上传支持(图片 + 视频最多 18 个文件)
|
||||
- `baoyu-post-to-weibo`:上传方式从剪贴板粘贴改为 `DOM.setFileInputFiles`,提升上传可靠性
|
||||
|
||||
### 修复
|
||||
@@ -1101,7 +1142,7 @@
|
||||
## 1.20.0 - 2026-01-24
|
||||
|
||||
### 新功能
|
||||
- `baoyu-cover-image`:从类型 × 风格二维系统升级为**四维系统**——新增 `--text` 维度(none 无文字、title-only 仅标题、title-subtitle 标题+副标题、text-rich 丰富文字)控制文字密度,新增 `--mood` 维度(subtle 低调、balanced 平衡、bold 醒目)控制情感强度。新增 `--quick` 标志跳过确认,直接使用自动选择。
|
||||
- `baoyu-cover-image`:从类型 × 风格二维系统升级为**四维系统**——新增 `--text` 维度(none 无文字、title-only 仅标题、title-subtitle 标题 + 副标题、text-rich 丰富文字)控制文字密度,新增 `--mood` 维度(subtle 低调、balanced 平衡、bold 醒目)控制情感强度。新增 `--quick` 标志跳过确认,直接使用自动选择。
|
||||
|
||||
### 文档
|
||||
- `baoyu-cover-image`:新增维度参考文件——`references/dimensions/text.md`(文字密度级别)和 `references/dimensions/mood.md`(氛围强度级别)。
|
||||
@@ -1121,7 +1162,7 @@
|
||||
- `baoyu-image-gen`:代码模块化——类型定义提取至 `types.ts`,provider 实现提取至 `providers/google.ts` 和 `providers/openai.ts`。
|
||||
|
||||
### 文档
|
||||
- `baoyu-comic`:改进 ohmsha 预设文档,明确默认哆啦A梦角色定义和视觉描述。
|
||||
- `baoyu-comic`:改进 ohmsha 预设文档,明确默认哆啦 A 梦角色定义和视觉描述。
|
||||
|
||||
## 1.18.3 - 2026-01-23
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -9,11 +9,14 @@ When a skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if the skill's `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends.
|
||||
|
||||
## The Preference Field
|
||||
|
||||
@@ -28,11 +28,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
@@ -345,7 +345,14 @@ Prompt Files:
|
||||
|
||||
### 5.2 Select Generation Skill
|
||||
|
||||
Check available skills. If multiple, ask user.
|
||||
Follow the `## Image Generation Tools` rule at the top of `SKILL.md`. Concretely:
|
||||
|
||||
- If `imagegen` is in your available-skills list (Codex), use it — invoke via the `Skill` tool with `skill: "imagegen"`.
|
||||
- Else if the EXTEND.md pin is available, use it.
|
||||
- Else if exactly one non-native backend is installed, use it.
|
||||
- Else, ask the user.
|
||||
|
||||
**Do not generate SVG, HTML, or any code-based vector as a substitute for the raster image.** If no raster backend can be resolved, ask the user how to proceed.
|
||||
|
||||
### 5.3 Process References ⚠️ REQUIRED if references saved in Step 1.0
|
||||
|
||||
|
||||
@@ -32,11 +32,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
@@ -28,11 +28,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { collect_generated_image_urls_from_response_parts } from "./client.ts";
|
||||
|
||||
test("response part fallback finds generated images when legacy generated markers are absent", () => {
|
||||
const generatedUrl = "https://lh3.googleusercontent.com/gg-dl/example-generated-image";
|
||||
const initialCandidate = ["rcid-1", ["image generated successfully"]];
|
||||
const imageCandidate = [
|
||||
"rcid-1",
|
||||
["image generated successfully"],
|
||||
{ nestedPayload: [{ media: generatedUrl }] },
|
||||
];
|
||||
const responseJson = [
|
||||
["wrb.fr", null, JSON.stringify([null, [], null, null, [initialCandidate]])],
|
||||
["wrb.fr", null, JSON.stringify([null, [], null, null, [imageCandidate]])],
|
||||
];
|
||||
|
||||
assert.equal(initialCandidate[12], undefined);
|
||||
assert.equal(
|
||||
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(String(initialCandidate[1]?.[0])),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(collect_generated_image_urls_from_response_parts(responseJson, 0, 0), [generatedUrl]);
|
||||
});
|
||||
@@ -37,6 +37,8 @@ type InitOptions = {
|
||||
|
||||
type RequestKwargs = RequestInit & { timeout_ms?: number };
|
||||
|
||||
const GENERATED_IMAGE_URL_PREFIX = 'https://lh3.googleusercontent.com/gg-dl/';
|
||||
|
||||
function normalize_headers(h?: HeadersInit): Record<string, string> {
|
||||
if (!h) return {};
|
||||
if (Array.isArray(h)) return Object.fromEntries(h.map(([k, v]) => [k, v]));
|
||||
@@ -78,6 +80,59 @@ function collect_strings(root: unknown, accept: (s: string) => boolean, limit: n
|
||||
return out;
|
||||
}
|
||||
|
||||
function collect_generated_image_urls(root: unknown, limit: number = 4): string[] {
|
||||
return collect_strings(root, (s) => s.startsWith(GENERATED_IMAGE_URL_PREFIX), limit);
|
||||
}
|
||||
|
||||
function parse_response_part_body(part: unknown): unknown[] | null {
|
||||
if (!Array.isArray(part)) return null;
|
||||
const part_body = get_nested_value<string | null>(part, [2], null);
|
||||
if (!part_body) return null;
|
||||
try {
|
||||
const part_json = JSON.parse(part_body) as unknown;
|
||||
return Array.isArray(part_json) ? part_json : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function find_generated_image_part(
|
||||
response_json: unknown[],
|
||||
body_index: number,
|
||||
candidate_index: number,
|
||||
limit: number = 4,
|
||||
): { body: unknown[]; urls: string[] } | null {
|
||||
for (let part_index = body_index; part_index < response_json.length; part_index++) {
|
||||
const part_json = parse_response_part_body(response_json[part_index]);
|
||||
if (!part_json) continue;
|
||||
const cand = get_nested_value<unknown>(part_json, [4, candidate_index], null);
|
||||
if (!cand) continue;
|
||||
const urls = collect_generated_image_urls(cand, limit);
|
||||
if (urls.length > 0) return { body: part_json, urls };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function collect_generated_image_urls_from_response_parts(
|
||||
response_json: unknown[],
|
||||
body_index: number,
|
||||
candidate_index: number,
|
||||
limit: number = 4,
|
||||
): string[] {
|
||||
return find_generated_image_part(response_json, body_index, candidate_index, limit)?.urls ?? [];
|
||||
}
|
||||
|
||||
function push_generated_images(
|
||||
generated_images: GeneratedImage[],
|
||||
urls: string[],
|
||||
proxy: string | null,
|
||||
cookies: Record<string, string>,
|
||||
): void {
|
||||
for (const url of urls) {
|
||||
generated_images.push(new GeneratedImage(url, '[Generated Image]', '', proxy, cookies));
|
||||
}
|
||||
}
|
||||
|
||||
export class GeminiClient extends GemMixin {
|
||||
public cookies: Record<string, string> = {};
|
||||
public proxy: string | null = null;
|
||||
@@ -404,24 +459,8 @@ export class GeminiClient extends GemMixin {
|
||||
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(text);
|
||||
|
||||
if (wants_generated) {
|
||||
let img_body: unknown[] | null = null;
|
||||
for (let part_index = body_index; part_index < (response_json as unknown[]).length; part_index++) {
|
||||
const part = (response_json as unknown[])[part_index];
|
||||
if (!Array.isArray(part)) continue;
|
||||
const part_body = get_nested_value<string | null>(part, [2], null);
|
||||
if (!part_body) continue;
|
||||
try {
|
||||
const part_json = JSON.parse(part_body) as unknown[];
|
||||
const cand = get_nested_value<unknown>(part_json, [4, candidate_index], null);
|
||||
if (!cand) continue;
|
||||
|
||||
const urls = collect_strings(cand, (s) => s.startsWith('https://lh3.googleusercontent.com/gg-dl/'), 1);
|
||||
if (urls.length > 0) {
|
||||
img_body = part_json;
|
||||
break;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
const image_part = find_generated_image_part(response_json as unknown[], body_index, candidate_index, 1);
|
||||
const img_body = image_part?.body ?? null;
|
||||
|
||||
if (!img_body) {
|
||||
throw new ImageGenerationError(
|
||||
@@ -452,13 +491,22 @@ export class GeminiClient extends GemMixin {
|
||||
}
|
||||
|
||||
if (generated_images.length === 0) {
|
||||
const urls = collect_strings(img_candidate, (s) => s.startsWith('https://lh3.googleusercontent.com/gg-dl/'), 4);
|
||||
for (const url of urls) {
|
||||
generated_images.push(new GeneratedImage(url, '[Generated Image]', '', this.proxy, this.cookies));
|
||||
}
|
||||
push_generated_images(generated_images, collect_generated_image_urls(img_candidate), this.proxy, this.cookies);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: unconditionally scan all response parts for generated image URLs.
|
||||
// The `wants_generated` detection above relies on `candidate[12][7][0]` and an old
|
||||
// `googleusercontent.com/image_generation_content/` URL pattern, both of which no
|
||||
// longer appear in the current Gemini Web API response format.
|
||||
// When Gemini does generate images, their URLs now start with
|
||||
// `https://lh3.googleusercontent.com/gg-dl/` and are present somewhere in the
|
||||
// response parts — this fallback finds them so images are not silently dropped.
|
||||
if (generated_images.length === 0) {
|
||||
const urls = collect_generated_image_urls_from_response_parts(response_json as unknown[], body_index, candidate_index);
|
||||
push_generated_images(generated_images, urls, this.proxy, this.cookies);
|
||||
}
|
||||
|
||||
out.push(new Candidate({ rcid, text, thoughts, web_images, generated_images }));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: baoyu-infographic
|
||||
description: Generate professional infographics with 21 layout types and 21 visual styles. Analyzes content, recommends layout×style combinations, and generates publication-ready infographics. Use when user asks to create "infographic", "信息图", "visual summary", "可视化", or "高密度信息大图".
|
||||
version: 1.57.1
|
||||
description: Generate professional infographics with 21 layout types and 22 visual styles. Analyzes content, recommends layout×style combinations, and generates publication-ready infographics. Use when user asks to create "infographic", "信息图", "visual summary", "可视化", or "高密度信息大图".
|
||||
version: 1.58.0
|
||||
metadata:
|
||||
openclaw:
|
||||
homepage: https://github.com/JimLiu/baoyu-skills#baoyu-infographic
|
||||
@@ -28,11 +28,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
@@ -84,7 +87,7 @@ Default behavior: **confirm before generation**.
|
||||
| Option | Values |
|
||||
|--------|--------|
|
||||
| `--layout` | 21 options (see Layout Gallery), default: bento-grid |
|
||||
| `--style` | 21 options (see Style Gallery), default: craft-handmade |
|
||||
| `--style` | 22 options (see Style Gallery), default: craft-handmade |
|
||||
| `--aspect` | Named: landscape (16:9), portrait (9:16), square (1:1). Custom: any W:H ratio (e.g., 3:4, 4:3, 2.35:1) |
|
||||
| `--lang` | en, zh, ja, etc. |
|
||||
| `--no-confirm` | Skip Step 4 only when the user explicitly requests direct generation without confirmation |
|
||||
@@ -118,7 +121,7 @@ Default behavior: **confirm before generation**.
|
||||
|
||||
Full definitions live at `references/layouts/<layout>.md`.
|
||||
|
||||
## Style Gallery (21)
|
||||
## Style Gallery (22)
|
||||
|
||||
| Style | Description |
|
||||
|-------|-------------|
|
||||
@@ -143,6 +146,7 @@ Full definitions live at `references/layouts/<layout>.md`.
|
||||
| `morandi-journal` | Hand-drawn doodle, warm Morandi tones |
|
||||
| `retro-pop-grid` | 1970s retro pop art, Swiss grid, thick outlines |
|
||||
| `hand-drawn-edu` | Macaron pastels, hand-drawn wobble, stick figures |
|
||||
| `retro-popup-pop` | Retro popup collage, vintage UI, thick outlines, flat pop colors |
|
||||
|
||||
Full definitions live at `references/styles/<style>.md`.
|
||||
|
||||
@@ -165,6 +169,7 @@ Full definitions live at `references/styles/<style>.md`.
|
||||
| Product Guide | `dense-modules` + `morandi-journal` |
|
||||
| Technical Guide | `dense-modules` + `pop-laboratory` |
|
||||
| Trendy Guide | `dense-modules` + `retro-pop-grid` |
|
||||
| Retro Pop Guide | `dense-modules` + `retro-popup-pop` |
|
||||
| Educational Diagram | `hub-spoke` + `hand-drawn-edu` |
|
||||
| Process Tutorial | `linear-progression` + `hand-drawn-edu` |
|
||||
|
||||
@@ -176,7 +181,7 @@ When the user's input contains these keywords, use the mapped layout as the lead
|
||||
|
||||
| User Keyword | Layout | Recommended Styles | Default Aspect | Prompt Notes |
|
||||
|--------------|--------|--------------------|----------------|--------------|
|
||||
| 高密度信息大图 / high-density-info | `dense-modules` | `morandi-journal`, `pop-laboratory`, `retro-pop-grid` | portrait | — |
|
||||
| 高密度信息大图 / high-density-info | `dense-modules` | `morandi-journal`, `pop-laboratory`, `retro-pop-grid`, `retro-popup-pop` | portrait | — |
|
||||
| 信息图 / infographic | `bento-grid` | `craft-handmade` | landscape | Minimalist: clean canvas, ample whitespace, no complex background textures. Simple cartoon elements and icons only. |
|
||||
|
||||
## Output Structure
|
||||
|
||||
@@ -39,7 +39,7 @@ Approach content analysis as a **world-class instructional designer**:
|
||||
| **System/Structure** | Components, architecture, anatomy | structural-breakdown, bento-grid | technical-schematic, ikea-manual |
|
||||
| **Journey/Narrative** | Stories, user flows, milestones | winding-roadmap, story-mountain | storybook-watercolor, comic-strip |
|
||||
| **Overview/Summary** | Multiple topics, feature highlights | bento-grid, periodic-table, dense-modules | chalkboard, bold-graphic |
|
||||
| **Product/Buying Guide** | Multi-dimension comparisons, specs, pitfalls | dense-modules | morandi-journal, pop-laboratory, retro-pop-grid |
|
||||
| **Product/Buying Guide** | Multi-dimension comparisons, specs, pitfalls | dense-modules | morandi-journal, pop-laboratory, retro-pop-grid, retro-popup-pop |
|
||||
|
||||
### 2. Learning Objective Identification
|
||||
|
||||
|
||||
@@ -68,5 +68,6 @@ High-density modular layout with 6-7 typed information modules packed with concr
|
||||
- `pop-laboratory`: Technical precision with coordinate markers and blueprint grid
|
||||
- `morandi-journal`: Hand-drawn warmth with doodle illustrations and organic frames
|
||||
- `retro-pop-grid`: 1970s pop art with strict grid cells and bold contrast
|
||||
- `retro-popup-pop`: Vintage desktop popups with chunky pixel UI for retro-tech dense guides
|
||||
- `corporate-memphis`: Clean business feel for product comparisons
|
||||
- `technical-schematic`: Engineering precision for technical product guides
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# retro-popup-pop
|
||||
|
||||
Retro pixel popup × pop-art collage — content rendered as a stack of 80/90s desktop dialog windows with thick black outlines, flat color fills, and bright cyan or vintage cream backgrounds.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- Background: Bright cyan (#12B8DE) primary canvas, vintage cream (#F5F0E6) alternate
|
||||
- Window fills: Vintage cream (#F5F0E6) and pure white (#FFFFFF)
|
||||
- Primary text and outlines: Pure black (#000000)
|
||||
- Reverse text: Pure white on solid black title bars
|
||||
- Accent fills (small areas only): Salmon pink, sky blue, mustard yellow, mint green — muted retro tones
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- 80s/90s desktop popup windows with title bars, close buttons (×), and chunky borders
|
||||
- Multiple windows tiled or lightly overlapping with offset solid-black drop shadows for depth
|
||||
- ERROR / ALERT / WARNING dialogs to highlight misconceptions, common pitfalls, risks
|
||||
- File-window vignettes labeled with retro filenames (PROBLEMS.EXE, METHOD.PNG, BURNOUT.PSD, FOCUS_SCAN, SYSTEM_ALERT, IDEAS_MISSING)
|
||||
- Progress bars for completion, difficulty, conversion rate, or trend
|
||||
- Pixel-style chunky icons: folders, magnifiers, gears, exclamation marks, floppy disks, hourglasses
|
||||
- Action buttons with thick black borders and short pop-style copy: OK, CANCEL, FIX IT, SCAN, PLAY, RETRY, SAVE
|
||||
- Comparison data rendered as windowed lists or small tabular dialogs
|
||||
- Uniform thick black outlines on every window, button, icon, and divider
|
||||
- Pure 2D flat color fills throughout — no gradients, no glow, no glass
|
||||
|
||||
## Typography
|
||||
|
||||
- Headers: Pixel / dot-matrix / chunky bitmap display fonts, large and high-contrast
|
||||
- Body: Retro monospace or system-style sans-serif, high legibility
|
||||
- Title bars: Reverse white on solid black, all-caps preferred
|
||||
- Decorative all-caps English allowed for filenames, status strings, button labels (PROBLEMS.EXE, OK, CANCEL); body content text remains in the confirmed output language
|
||||
|
||||
## Style Enforcement
|
||||
|
||||
- Every information chunk must live inside a window, dialog, button, or progress bar — no floating elements
|
||||
- Uniform thick black outlines everywhere; offset solid-black drop shadows for stacked-window depth
|
||||
- Limit accent palette to ~4 colors per composition; let cyan or cream dominate the canvas
|
||||
- Maintain low-fi pixel/popup feel; humorous popup copy welcome, polished modern UI prohibited
|
||||
|
||||
## Avoid
|
||||
|
||||
- Modern UI styles: glassmorphism, neumorphism, soft shadows, blurs, gradients
|
||||
- 3D rendering, photorealism, ray-traced shadows
|
||||
- Hand-drawn wobble, watercolor washes, or sketch textures
|
||||
- Smooth anti-aliased curves on icons (prefer chunky pixel/stair-step edges)
|
||||
- Pure black backgrounds — keep cyan or cream as the canvas
|
||||
|
||||
## Best For
|
||||
|
||||
High-density "干货" knowledge guides, common-pitfall and debunking posts, knowledge-pop content for design-savvy or developer audiences, Xiaohongshu-style retro-tech posts, dense-modules portrait infographics.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>` |
|
||||
| `` | 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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -32,11 +32,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-slide-[slug].md`) BEFORE invoking any backend. The file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
@@ -29,11 +29,14 @@ When this skill needs to render an image, resolve the backend in this order:
|
||||
1. **Current-request override** — if the user names a specific backend in the current message, use it.
|
||||
2. **Saved preference** — if `EXTEND.md` sets `preferred_image_backend` to a backend available right now, use it.
|
||||
3. **Auto-select** (when the preference is `auto`, unset, or the pinned backend isn't available):
|
||||
- If the current runtime exposes a native image tool (e.g., Codex `imagegen`, Hermes `image_generate`), use it. Runtime-native tools are preferred by default — agents that know their own tool inventory should surface the native one here.
|
||||
- **Codex (`imagegen`)** — first, inspect your available-skills / tool inventory. If a skill named `imagegen` is listed, you are running inside Codex and MUST use it: invoke via the `Skill` tool with `skill: "imagegen"`, passing the saved prompt file's content (plus output path and aspect ratio per Codex `imagegen`'s own args). Codex `imagegen` is the official raster backend in that runtime and outranks any non-native skill (e.g., `baoyu-imagine`) unless the user has explicitly pinned a different `preferred_image_backend`.
|
||||
- **Other runtime-native tools** — if the runtime exposes a different native image tool (e.g., Hermes `image_generate`), use it the same way.
|
||||
- Otherwise, if exactly one non-native backend is installed (e.g., `baoyu-imagine`), use it.
|
||||
- Otherwise (multiple non-native backends with no runtime-native tool), ask the user once — batch with any other initial questions.
|
||||
4. **If none are available**, tell the user and ask how to proceed.
|
||||
|
||||
**⛔ Never substitute SVG, HTML, canvas, or other code-based rendering for raster image generation.** Codex `imagegen`'s own description says it should be used "when the output should be a bitmap asset rather than repo-native code or vector." If you cannot resolve a raster backend via step 3, fall through to step 4 and ask the user — do **not** silently emit SVG, write inline `<svg>` markup, or produce HTML/CSS art as a substitute. This applies even if the article/section seems "diagram-like": the consumer skill calling this rule has already decided that a raster image is what it needs.
|
||||
|
||||
Setting `preferred_image_backend: ask` forces the step-3 prompt every run regardless of available backends. Users change the pinned backend via the `## Changing Preferences` section below.
|
||||
|
||||
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||
|
||||
Reference in New Issue
Block a user