Skip to content

feat(claude): add Claude adapter#1252

Merged
jackwener merged 5 commits intojackwener:mainfrom
Benjamin-eecs:feat/claude-ask
May 3, 2026
Merged

feat(claude): add Claude adapter#1252
jackwener merged 5 commits intojackwener:mainfrom
Benjamin-eecs:feat/claude-ask

Conversation

@Benjamin-eecs
Copy link
Copy Markdown
Contributor

@Benjamin-eecs Benjamin-eecs commented May 2, 2026

Description

Adds a Claude adapter family for claude.ai, matching the surface of the existing AI chat adapters (ChatGPT, Gemini, DeepSeek, Doubao, Yuanbao). Modeled on the merged clis/deepseek/ pattern.

Closes #1251

Seven commands:

  • claude ask <prompt>: send a prompt and return the response
  • claude send <prompt>: fire-and-forget send (output shape aligned with doubao send: Status / SubmittedBy / InjectedText)
  • claude new: start a fresh conversation
  • claude status: login state and page availability
  • claude read: read the current conversation (use --live on the prior command or chain after claude detail <id> so the tab stays on the chat)
  • claude history: list recent conversations from /recents
  • claude detail <id>: open a conversation by ID and read its messages

ask args: prompt (positional, required), --timeout, --new, --model <sonnet|opus|haiku> (opus on free tier surfaces a CliError('ARGUMENT', ...) with upgrade hint), --think (Adaptive thinking), --file (image / PDF / text, up to ~1 MB raw because of the daemon body limit at src/daemon.ts:152).

Implementation notes worth flagging:

  • Composer is [data-testid="chat-input"] ProseMirror contenteditable; clearing uses Range + execCommand('delete') because .value / .textContent are ignored by ProseMirror
  • File upload uses page.setFileInput (CDP, no body) followed by manual __reactProps$*.onChange invocation because Chrome's CDP file write does not fire React's controlled-input onChange
  • Response container is .font-claude-response; Adaptive thinking and file-thumbnail widgets render duplicated label paragraphs (Thought process / View uploaded image) at the top of the response, stripped via paragraph split
  • Preview detection uses [data-testid="file-thumbnail"] and button[aria-label="Remove"]
  • getVisibleMessages queries user and assistant nodes in a single combined pass to preserve DOM order in multi-turn conversations

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🌐 New site adapter
  • 📝 Documentation
  • ♻️ Refactor
  • 🔧 CI / build / tooling

Checklist

  • I ran the checks relevant to this PR
  • I updated tests or docs if needed
  • I included output or screenshots when useful

Documentation (if adding/modifying an adapter)

  • Added doc page under docs/adapters/browser/claude.md
  • Updated docs/adapters/index.md table
  • Updated sidebar in docs/.vitepress/config.mts
  • Updated README.md / README.zh-CN.md
  • Used positional args for the command's primary subject (<prompt> for ask/send, <id> for detail)
  • Normalized expected adapter failures to CliError subclasses (CliError, CommandExecutionError)

Screenshots / Output

Vitest: npx vitest run clis/claude/ -> 22/22 pass.

End-to-end validated against current claude.ai DOM with a real free-tier Sonnet 4.6 account:

$ opencli claude ask "在 5 字内回答:你好吗" --new
我很好,谢谢!

$ opencli claude ask "in 5 words: which model are you?" --new --model haiku
I am Claude Haiku 4.5.

$ opencli claude ask "test" --new --model opus
ok: false
error:
  code: ARGUMENT
  message: opus model requires a paid Claude plan.
  help: Pick --model sonnet or --model haiku, or upgrade your account.

$ opencli claude ask "what is 2 plus 2 in 3 words" --new --think
Four is the answer.

$ opencli claude ask "describe in 5 words" --new --file ./cat.png
Tiny striped kitten, blue eyes.

$ opencli claude status
Status: Connected | Login: Yes | Url: https://claude.ai/new

$ opencli claude history --limit 5
(5 rows: Index / Id / Title / Url)

$ opencli claude detail <uuid>
Index 0 user "what is 2 plus 2 in 3 words"
Index 1 assistant "Four is the answer."

$ opencli claude new
New chat started

$ opencli claude send "hi" --new
Status: Success | SubmittedBy: send-button | InjectedText: hi

Backward compatibility: this PR adds files under clis/claude/ only; no shared utils, framework code, or existing adapter manifests were touched. chatgpt image --help / deepseek ask --help parse cleanly post-change (smoke check).

Known limitation: --file is bounded by the daemon HTTP body limit (1 MB at src/daemon.ts:152); files up to ~700 KB raw work, larger images may fail with ECONNRESET. Same constraint applies to other adapters using page.setFileInput; not introduced by this PR.

Adds a Claude (claude.ai) browser adapter family with seven commands
modeled on the existing clis/deepseek/ pattern: ask, send, new, status,
read, history, detail.

Closes jackwener#1251
Copilot AI review requested due to automatic review settings May 2, 2026 14:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new claude browser adapter family for claude.ai, aligning with existing chat adapters (e.g., DeepSeek) and wiring it into docs + CLI manifest.

Changes:

  • Introduces clis/claude/* commands (ask, send, new, status, read, history, detail) with Claude-specific DOM automation utilities.
  • Adds Vitest coverage for the new Claude utilities and ask command flows.
  • Updates documentation/navigation and registers commands in cli-manifest.json + READMEs.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
docs/adapters/index.md Adds Claude to the adapters index table.
docs/adapters/browser/claude.md New Claude adapter documentation page (commands/options/caveats).
docs/.vitepress/config.mts Adds Claude to the docs sidebar navigation.
clis/claude/utils.test.js Unit tests for Claude utilities (bool flag parsing, file send, model select).
clis/claude/utils.js Core Claude browser automation helpers (navigation, message readback, model/think toggles, file upload, retry).
clis/claude/status.js Implements opencli claude status.
clis/claude/send.js Implements opencli claude send.
clis/claude/read.js Implements opencli claude read.
clis/claude/new.js Implements opencli claude new.
clis/claude/history.js Implements opencli claude history via /recents.
clis/claude/detail.js Implements opencli claude detail <id>.
clis/claude/ask.test.js Tests for ask flow covering --new, --model, --think, --file behaviors.
clis/claude/ask.js Implements opencli claude ask including model selection, adaptive thinking, file attach, and response wait.
cli-manifest.json Registers all new Claude commands in the CLI manifest.
README.zh-CN.md Adds Claude to the Chinese README adapter list.
README.md Adds Claude to the README adapter list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread clis/claude/utils.js
Comment on lines +44 to +65
export async function getVisibleMessages(page) {
const result = await page.evaluate(`(() => {
var rows = [];
document.querySelectorAll('[data-testid="user-message"]').forEach(function(el) {
var text = (el.innerText || '').trim();
if (text) rows.push({ index: rows.length, role: 'user', text: text });
});
document.querySelectorAll('${MESSAGE_SELECTOR}').forEach(function(el) {
var raw = (el.innerText || '').trim();
// Strip leading widget label paragraphs (Adaptive thinking, file thumbnail).
var parts = raw.split(/\\n\\n+/);
while (parts.length > 1 && /^(Thought|View)\\b/i.test(parts[0])) parts.shift();
var text = parts.join('\\n\\n').trim();
if (text) rows.push({ index: rows.length, role: 'assistant', text: text });
});
return rows;
})()`);
if (!Array.isArray(result)) return [];
// Stable order: user / assistant alternating by DOM order is preserved per-bucket above,
// but interleave them so the conversation reads top-to-bottom.
return result.map(function(r, i) { return { Index: i, Role: r.role, Text: r.text }; });
}
Comment thread clis/claude/utils.js
uploaded = true;
} catch (err) {
const msg = String(err?.message || err);
if (!msg.includes('Unknown action') && !msg.includes('not supported') && !msg.includes('Not allowed')) {
Comment thread clis/claude/utils.js
Comment on lines +287 to +294
const ready = await page.evaluate(`(() => {
// Claude renders attachments as data-testid="file-thumbnail" cards with
// a sibling Remove button. Either signal indicates the file took.
if (document.querySelector('[data-testid="file-thumbnail"]')) return true;
var removeBtn = Array.from(document.querySelectorAll('button'))
.find(function(b) { return (b.getAttribute('aria-label') || '') === 'Remove'; });
return !!removeBtn;
})()`);
Benjamin-eecs and others added 4 commits May 2, 2026 23:55
Match the established Status / SubmittedBy / InjectedText shape used by
doubao send so agent loops can rely on a consistent fire-and-forget
output across AI chat adapters.
The previous implementation queried user-message and assistant-message
nodes in two passes, which serialized as [u1, u2, u3, a1, a2, a3] for
multi-turn chats instead of the correct conversation order. Single
combined query preserves DOM order so claude read / detail return
turns in the order the user reads them on the page.
@jackwener jackwener merged commit 284c961 into jackwener:main May 3, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add Claude adapter

3 participants