From 517e3a57fdcce99761b07cab36c96672b7bd8a2b Mon Sep 17 00:00:00 2001 From: 0xsline Date: Mon, 23 Mar 2026 23:59:08 +0800 Subject: [PATCH 1/2] feat(generate): implement registerCandidates to copy generated CLIs to user directory - Read candidates.json index from synthesize output directory - Copy matched YAML files to ~/.opencli/clis/ (or opts.userClis) - Support optional name filter to register only a specific candidate - Create user CLI directory if it does not exist - Warn and skip files that already exist to avoid overwriting - Remove TODO comment and stub implementation; return real count --- src/generate.ts | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/generate.ts b/src/generate.ts index 5e1968ad..154ba3e4 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -8,11 +8,13 @@ * automatically downgrades and retries. */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; import { exploreUrl } from './explore.js'; import type { IBrowserFactory } from './runtime.js'; import { synthesizeFromExplore, type SynthesizeCandidateSummary, type SynthesizeResult } from './synthesize.js'; -// TODO: implement real CLI registration (copy candidate YAML to user clis dir) interface RegisterCandidatesOptions { target: string; builtinClis?: string; @@ -59,8 +61,46 @@ export interface GenerateCliResult { register: RegisterCandidatesResult | null; } -function registerCandidates(_opts: RegisterCandidatesOptions): RegisterCandidatesResult { - return { ok: true, count: 0 }; +function registerCandidates(opts: RegisterCandidatesOptions): RegisterCandidatesResult { + const userClisDir = opts.userClis ?? path.join(os.homedir(), '.opencli', 'clis'); + + // Read candidate list from the synthesize output directory + const indexPath = path.join(opts.target, 'candidates.json'); + if (!fs.existsSync(indexPath)) { + return { ok: false, count: 0 }; + } + + let candidates: Array<{ name: string; path: string }>; + try { + const raw = fs.readFileSync(indexPath, 'utf-8'); + const index = JSON.parse(raw) as { candidates: Array<{ name: string; path: string }> }; + candidates = index.candidates ?? []; + } catch { + return { ok: false, count: 0 }; + } + + // Filter to a specific candidate if name is provided + if (opts.name) { + candidates = candidates.filter(c => c.name === opts.name); + } + + fs.mkdirSync(userClisDir, { recursive: true }); + + let count = 0; + for (const candidate of candidates) { + if (!fs.existsSync(candidate.path)) continue; + + const destPath = path.join(userClisDir, path.basename(candidate.path)); + if (fs.existsSync(destPath)) { + console.warn(`[opencli] Skipping ${path.basename(candidate.path)}: already exists in user CLI directory.`); + continue; + } + + fs.copyFileSync(candidate.path, destPath); + count++; + } + + return { ok: true, count }; } const CAPABILITY_ALIASES: Record = { From c3ac9e7c9a414fe82eff66eb0281525426a35ca8 Mon Sep 17 00:00:00 2001 From: sline Date: Tue, 24 Mar 2026 01:51:24 +0800 Subject: [PATCH 2/2] docs: add doubao-app desktop adapter documentation Fix dead link in docs/adapters/index.md that references /adapters/desktop/doubao-app but the page did not exist. This was causing vitepress docs-build to fail. --- docs/adapters/desktop/doubao-app.md | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/adapters/desktop/doubao-app.md diff --git a/docs/adapters/desktop/doubao-app.md b/docs/adapters/desktop/doubao-app.md new file mode 100644 index 00000000..ce8b5bc0 --- /dev/null +++ b/docs/adapters/desktop/doubao-app.md @@ -0,0 +1,40 @@ +# Doubao App + +Control the **Doubao AI desktop app** (豆包) directly from the terminal via Chrome DevTools Protocol (CDP). + +## Prerequisites + +1. Install the official Doubao desktop app. +2. Launch Doubao with the remote debugging port enabled: + +```bash +# macOS +/Applications/Doubao.app/Contents/MacOS/Doubao \ + --remote-debugging-port=9226 +``` + +3. Set the CDP endpoint: + +```bash +export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9226" +``` + +## Commands + +- `opencli doubao-app status`: Check if the Doubao app is running and accessible via CDP. +- `opencli doubao-app new`: Start a new conversation. +- `opencli doubao-app send "message"`: Send a message to the active conversation. +- `opencli doubao-app read`: Read chat messages from the current conversation. +- `opencli doubao-app ask "message"`: Send a prompt and wait for the assistant reply in one shot. +- `opencli doubao-app screenshot`: Capture a screenshot of the current Doubao window. +- `opencli doubao-app dump`: Dump the full conversation history from the current session. + +## How It Works + +Doubao Desktop is an Electron app. OpenCLI connects via the Chrome DevTools Protocol to the Electron renderer process and interacts with the chat UI using `data-testid` selectors exposed by the app. + +## Limitations + +- Requires Doubao to be launched with `--remote-debugging-port=9226` +- CDP endpoint must be reachable at the configured address +- `read` returns only the visible messages in the current conversation \ No newline at end of file