Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Run `opencli list` for the live registry.
| **hf** | `top` | Public |
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | Browser |
| **jimeng** | `generate` `history` | Browser |
| **yollomi** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | Browser |
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | Public |
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | Public |
| **steam** | `top-sellers` | Public |
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ npm install -g @jackwener/opencli@latest
| **hf** | `top` | 公开 |
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | 浏览器 |
| **jimeng** | `generate` `history` | 浏览器 |
| **yollomi** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | 浏览器 |
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | 公开 |
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | 公开 |
| **steam** | `top-sellers` | 公开 |
Expand Down
10 changes: 9 additions & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: opencli
description: "OpenCLI — Make any website or Electron App your CLI. Zero risk, AI-powered, reuse Chrome login. 150+ commands across 30+ sites."
version: 1.1.0
author: jackwener
tags: [cli, browser, web, chrome-extension, cdp, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, AI, agent]
tags: [cli, browser, web, chrome-extension, cdp, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, yollomi, AI, agent]
---

# OpenCLI
Expand Down Expand Up @@ -220,6 +220,14 @@ opencli weread ranking --limit 10 # 排行榜
opencli jimeng generate --prompt "描述" # AI 生图
opencli jimeng history --limit 10 # 生成历史

# Yollomi yollomi.com (browser — 需在 Chrome 登录 yollomi.com,复用站点 session)
opencli yollomi models --type image # 列出图像模型与积分
opencli yollomi generate "提示词" --model z-image-turbo # 文生图
opencli yollomi video "提示词" --model kling-2-1 # 视频
opencli yollomi upload ./photo.jpg # 上传得 URL,供 img2img / 工具链使用
opencli yollomi remove-bg <image-url> # 去背景(免费)
opencli yollomi edit <image-url> "改成油画风格" # Qwen 图像编辑

# Grok (default + explicit web)
opencli grok ask --prompt "问题" # 提问 Grok(兼容默认路径)
opencli grok ask --prompt "问题" --web # 显式 grok.com consumer web UI 路径
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default defineConfig({
{ text: 'SMZDM', link: '/adapters/browser/smzdm' },
{ text: 'Jike', link: '/adapters/browser/jike' },
{ text: 'Jimeng', link: '/adapters/browser/jimeng' },
{ text: 'Yollomi', link: '/adapters/browser/yollomi' },
{ text: 'LINUX DO', link: '/adapters/browser/linux-do' },
{ text: 'Chaoxing', link: '/adapters/browser/chaoxing' },
{ text: 'Grok', link: '/adapters/browser/grok' },
Expand Down
69 changes: 69 additions & 0 deletions docs/adapters/browser/yollomi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Yollomi

**Mode**: 🔐 Browser · **Domain**: `yollomi.com`

AI image/video generation and editing on [yollomi.com](https://yollomi.com). Uses the same `/api/ai/*` routes as the web app; authentication is your **logged-in Chrome session** (NextAuth cookies).

## Commands

| Command | Description |
|---------|-------------|
| `opencli yollomi generate` | Text-to-image / image-to-image |
| `opencli yollomi video` | Text-to-video / image-to-video |
| `opencli yollomi edit` | Qwen image edit (prompt + image) |
| `opencli yollomi upload` | Upload a local file → public URL for other commands |
| `opencli yollomi models` | List image / video / tool models and credit costs |
| `opencli yollomi remove-bg` | Remove background (free) |
| `opencli yollomi upscale` | Image upscaling |
| `opencli yollomi face-swap` | Face swap between two images |
| `opencli yollomi restore` | Photo restoration |
| `opencli yollomi try-on` | Virtual try-on |
| `opencli yollomi background` | AI background for product/object images |
| `opencli yollomi object-remover` | Remove objects (image + mask URLs) |

## Usage Examples

```bash
# List models
opencli yollomi models --type image

# Text-to-image (default model: z-image-turbo)
opencli yollomi generate "a red apple on a wooden table"

# Choose model and aspect ratio
opencli yollomi generate "sunset" --model flux-schnell --ratio 16:9

# Image-to-image: upload first, then pass URL
opencli yollomi upload ./photo.png
opencli yollomi generate "oil painting style" --model flux-2-pro --image "https://..."

# Video
opencli yollomi video "waves on a beach" --model kling-2-1

# Tools
opencli yollomi remove-bg https://example.com/image.png
opencli yollomi upscale https://example.com/image.png --scale 4
opencli yollomi edit https://example.com/in.png "make it vintage"
```

### Common options

| Option | Applies to | Description |
|--------|------------|-------------|
| `--model` | `generate`, `video` | Model id (see `yollomi models`) |
| `--ratio` | `generate`, `video` | Aspect ratio, e.g. `1:1`, `16:9` |
| `--image` | `generate`, `video` | Image URL for img2img / i2v |
| `--output` | Most | Output directory (default `./yollomi-output`) |
| `--no-download` | Several | Print URLs only, skip saving files |

## Prerequisites

- Chrome running and **logged into** [yollomi.com](https://yollomi.com) (Google OAuth)
- [Browser Bridge extension](/guide/browser-bridge) installed; daemon connects on first command

The CLI ensures the automation tab is on `yollomi.com` before calling APIs (same-origin `fetch` with session cookies).

## Notes

- **Credits**: Each model consumes account credits; insufficient credits returns HTTP 402.
- **Upload**: Local paths for tools are not accepted directly — use `yollomi upload` to get a URL, or pass an existing HTTPS image URL.
1 change: 1 addition & 0 deletions docs/adapters/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Run `opencli list` for the live registry.
| **[smzdm](/adapters/browser/smzdm)** | `search` | 🔐 Browser |
| **[jike](/adapters/browser/jike)** | `feed` `search` `post` `topic` `user` `create` `comment` `like` `repost` `notifications` | 🔐 Browser |
| **[jimeng](/adapters/browser/jimeng)** | `generate` `history` | 🔐 Browser |
| **[yollomi](/adapters/browser/yollomi)** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | 🔐 Browser |
| **[linux-do](/adapters/browser/linux-do)** | `hot` `latest` `categories` `category` `search` `topic` | 🔐 Browser |
| **[chaoxing](/adapters/browser/chaoxing)** | `assignments` `exams` | 🔐 Browser |
| **[grok](/adapters/browser/grok)** | `ask` | 🔐 Browser |
Expand Down
48 changes: 48 additions & 0 deletions src/clis/yollomi/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Yollomi AI background generator — POST /api/ai/ai-background-generator
*/

import * as path from 'node:path';
import chalk from 'chalk';
import { cli, Strategy } from '../../registry.js';
import { CliError } from '../../errors.js';
import { YOLLOMI_DOMAIN, yollomiPost, downloadOutput, fmtBytes } from './utils.js';

cli({
site: 'yollomi',
name: 'background',
description: 'Generate AI background for a product/object image (5 credits)',
domain: YOLLOMI_DOMAIN,
strategy: Strategy.COOKIE,
args: [
{ name: 'image', positional: true, required: true, help: 'Image URL (upload via "opencli yollomi upload" first)' },
{ name: 'prompt', default: '', help: 'Background description (optional)' },
{ name: 'output', default: './yollomi-output', help: 'Output directory' },
{ name: 'no-download', type: 'boolean', default: false, help: 'Only show URL' },
],
columns: ['status', 'file', 'size', 'url'],
func: async (page, kwargs) => {
const imageUrl = kwargs.image as string;
const prompt = kwargs.prompt as string;

process.stderr.write(chalk.dim('Generating background...\n'));
const data = await yollomiPost(page, '/api/ai/ai-background-generator', {
images: [imageUrl],
prompt: prompt || undefined,
aspect_ratio: '1:1',
});

const url = data.image || (data.images?.[0]);
if (!url) throw new CliError('EMPTY_RESPONSE', 'No result', 'Try a different image');

if (kwargs['no-download']) return [{ status: 'generated', file: '-', size: '-', url }];

try {
const filename = `yollomi_bg_${Date.now()}.png`;
const { path: fp, size } = await downloadOutput(url, kwargs.output as string, filename);
return [{ status: 'saved', file: path.relative('.', fp), size: fmtBytes(size), url }];
} catch {
return [{ status: 'download-failed', file: '-', size: '-', url }];
}
},
});
58 changes: 58 additions & 0 deletions src/clis/yollomi/edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Yollomi image editing — POST /api/ai/qwen-image-edit
* Matches frontend workspace-generator.tsx for qwen-image-edit model.
*/

import * as path from 'node:path';
import chalk from 'chalk';
import { cli, Strategy } from '../../registry.js';
import { CliError } from '../../errors.js';
import { YOLLOMI_DOMAIN, yollomiPost, resolveImageInput, downloadOutput, fmtBytes } from './utils.js';

cli({
site: 'yollomi',
name: 'edit',
description: 'Edit images with AI text prompts (Qwen image edit)',
domain: YOLLOMI_DOMAIN,
strategy: Strategy.COOKIE,
args: [
{ name: 'image', positional: true, required: true, help: 'Input image URL (upload via "opencli yollomi upload" first)' },
{ name: 'prompt', positional: true, required: true, help: 'Editing instruction (e.g. "Make it look vintage")' },
{ name: 'model', default: 'qwen-image-edit', choices: ['qwen-image-edit', 'qwen-image-edit-plus'], help: 'Edit model' },
{ name: 'output', default: './yollomi-output', help: 'Output directory' },
{ name: 'no-download', type: 'boolean', default: false, help: 'Only show URL' },
],
columns: ['status', 'file', 'size', 'credits', 'url'],
func: async (page, kwargs) => {
const imageInput = kwargs.image as string;
const prompt = kwargs.prompt as string;
const modelId = kwargs.model as string;

let body: Record<string, unknown>;
if (modelId === 'qwen-image-edit-plus') {
body = { prompt, images: [imageInput] };
} else {
body = { image: imageInput, prompt, go_fast: true, output_format: 'png' };
}

const apiPath = modelId === 'qwen-image-edit-plus' ? '/api/ai/qwen-image-edit-plus' : '/api/ai/qwen-image-edit';
process.stderr.write(chalk.dim(`Editing with ${modelId}...\n`));
const data = await yollomiPost(page, apiPath, body);

const images: string[] = data.images || (data.image ? [data.image] : []);
if (!images.length) throw new CliError('EMPTY_RESPONSE', 'No result', 'Try a different prompt');

const credits = data.remainingCredits;
const url = images[0];
if (kwargs['no-download']) return [{ status: 'edited', file: '-', size: '-', credits: credits ?? '-', url }];

try {
const filename = `yollomi_edit_${Date.now()}.png`;
const { path: fp, size } = await downloadOutput(url, kwargs.output as string, filename);
if (credits !== undefined) process.stderr.write(chalk.dim(`Credits remaining: ${credits}\n`));
return [{ status: 'saved', file: path.relative('.', fp), size: fmtBytes(size), credits: credits ?? '-', url }];
} catch {
return [{ status: 'download-failed', file: '-', size: '-', credits: credits ?? '-', url }];
}
},
});
45 changes: 45 additions & 0 deletions src/clis/yollomi/face-swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Yollomi face swap — POST /api/ai/face-swap
* Uses swap_image / input_image field names matching the frontend.
*/

import * as path from 'node:path';
import chalk from 'chalk';
import { cli, Strategy } from '../../registry.js';
import { CliError } from '../../errors.js';
import { YOLLOMI_DOMAIN, yollomiPost, downloadOutput, fmtBytes } from './utils.js';

cli({
site: 'yollomi',
name: 'face-swap',
description: 'Swap faces between two photos (3 credits)',
domain: YOLLOMI_DOMAIN,
strategy: Strategy.COOKIE,
args: [
{ name: 'source', required: true, help: 'Source face image URL' },
{ name: 'target', required: true, help: 'Target photo URL' },
{ name: 'output', default: './yollomi-output', help: 'Output directory' },
{ name: 'no-download', type: 'boolean', default: false, help: 'Only show URL' },
],
columns: ['status', 'file', 'size', 'url'],
func: async (page, kwargs) => {
process.stderr.write(chalk.dim('Swapping faces...\n'));
const data = await yollomiPost(page, '/api/ai/face-swap', {
swap_image: kwargs.source as string,
input_image: kwargs.target as string,
});

const url = data.image || (data.images?.[0]);
if (!url) throw new CliError('EMPTY_RESPONSE', 'No result', 'Make sure both images contain clear faces');

if (kwargs['no-download']) return [{ status: 'swapped', file: '-', size: '-', url }];

try {
const filename = `yollomi_faceswap_${Date.now()}.jpg`;
const { path: fp, size } = await downloadOutput(url, kwargs.output as string, filename);
return [{ status: 'saved', file: path.relative('.', fp), size: fmtBytes(size), url }];
} catch {
return [{ status: 'download-failed', file: '-', size: '-', url }];
}
},
});
94 changes: 94 additions & 0 deletions src/clis/yollomi/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Yollomi text-to-image / image-to-image generation.
*
* Uses per-model routes exactly like the frontend:
* POST /api/ai/z-image-turbo { prompt, width, height, ... }
* POST /api/ai/nano-banana { prompt, aspect_ratio, ... }
* POST /api/ai/flux-2-pro { prompt, aspectRatio, imageUrl?, ... }
*/

import * as path from 'node:path';
import chalk from 'chalk';
import { cli, Strategy } from '../../registry.js';
import { CliError } from '../../errors.js';
import { YOLLOMI_DOMAIN, yollomiPost, resolveImageInput, downloadOutput, fmtBytes, MODEL_ROUTES } from './utils.js';

function getDimensions(ratio: string): { width: number; height: number } {
const map: Record<string, [number, number]> = {
'1:1': [1024, 1024], '16:9': [1344, 768], '9:16': [768, 1344],
'4:3': [1152, 896], '3:4': [896, 1152],
};
const [w, h] = map[ratio] || [1024, 1024];
return { width: w, height: h };
}

cli({
site: 'yollomi',
name: 'generate',
description: 'Generate images with AI (text-to-image or image-to-image)',
domain: YOLLOMI_DOMAIN,
strategy: Strategy.COOKIE,
args: [
{ name: 'prompt', positional: true, required: true, help: 'Text prompt describing the image' },
{ name: 'model', default: 'z-image-turbo', help: 'Model ID (z-image-turbo, flux-schnell, nano-banana, flux-2-pro, ...)' },
{ name: 'ratio', default: '1:1', choices: ['1:1', '16:9', '9:16', '4:3', '3:4'], help: 'Aspect ratio' },
{ name: 'image', help: 'Input image URL for image-to-image (upload via "opencli yollomi upload" first)' },
{ name: 'output', default: './yollomi-output', help: 'Output directory' },
{ name: 'no-download', type: 'boolean', default: false, help: 'Only show URLs, skip download' },
],
columns: ['index', 'status', 'file', 'size', 'url'],
func: async (page, kwargs) => {
const prompt = kwargs.prompt as string;
const modelId = kwargs.model as string;
const ratio = kwargs.ratio as string;

const apiPath = MODEL_ROUTES[modelId];
if (!apiPath) throw new CliError('INVALID_MODEL', `Unknown model: ${modelId}`, 'Run "opencli yollomi models --type image" to see available models');

let body: Record<string, unknown>;

if (modelId === 'z-image-turbo') {
const { width, height } = getDimensions(ratio);
body = { prompt, width, height, output_format: 'jpg', output_quality: 85, guidance_scale: 0, num_inference_steps: 8 };
} else if (modelId === 'flux-2-pro') {
body = { prompt, aspectRatio: ratio, outputNumber: 1 };
if (kwargs.image) body.imageUrl = kwargs.image as string;
} else if (modelId === 'flux-kontext-pro') {
body = { prompt, output_format: 'jpg' };
if (kwargs.image) body.imageUrl = kwargs.image as string;
if (ratio !== '1:1') body.aspect_ratio = ratio;
} else {
body = { prompt, aspect_ratio: ratio };
if (kwargs.image) body.imageUrl = kwargs.image as string;
}

process.stderr.write(chalk.dim(`Generating with ${modelId}...\n`));
const data = await yollomiPost(page, apiPath, body);

const images: string[] = data.images || (data.image ? [data.image] : []);
if (!images.length) throw new CliError('EMPTY_RESPONSE', 'No images returned', 'Try a different prompt or model');

const noDownload = kwargs['no-download'] as boolean;
const outputDir = kwargs.output as string;
const results: any[] = [];

for (let i = 0; i < images.length; i++) {
const url = images[i];
if (noDownload) {
results.push({ index: i + 1, status: 'generated', file: '-', size: '-', url });
continue;
}
try {
const ext = url.includes('.png') ? '.png' : '.jpg';
const filename = `yollomi_${modelId}_${Date.now()}_${i + 1}${ext}`;
const { path: fp, size } = await downloadOutput(url, outputDir, filename);
results.push({ index: i + 1, status: 'saved', file: path.relative('.', fp), size: fmtBytes(size), url });
} catch {
results.push({ index: i + 1, status: 'download-failed', file: '-', size: '-', url });
}
}

if (data.remainingCredits !== undefined) process.stderr.write(chalk.dim(`Credits remaining: ${data.remainingCredits}\n`));
return results;
},
});
Loading