按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。 browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
3.8 KiB
claude.ai — Export a Shared Conversation
URL pattern: https://claude.ai/share/<uuid>
Extracts a full Human/Assistant transcript from a Claude share link as JSON or Markdown.
Auth requirement
The share page renders the chat content only when the user is logged into claude.ai. Logged-out viewers see an app shell without the conversation DOM. Open the URL in a Chrome profile that's already signed in — bh attaches to the user's running Chrome, so this works automatically as long as their profile is logged in.
If the conversation body is empty after navigation, that's the signal you're hitting an unauthenticated session.
DOM map
Share pages render the conversation as a flat list of sibling <div> children inside a single container. There is no per-turn wrapper element that includes both messages — turns are alternating siblings.
Stable selectors:
[data-testid=user-message]— user message body (clean text, no prefix).font-claude-response— assistant message body[data-testid=page-header]— first line ofinnerTextis the conversation title[data-testid=action-bar-copy]— per-turn copy button (not needed for export)
Traps to avoid:
document.titlereturns"Claude"— useless for the conversation title; use[data-testid=page-header]document.querySelector("h1, h2")picks up sidebarStarredetc — not the chat title- Each assistant block contains an
H2.sr-onlyreading "Claude responded:" — screen-reader only, butinnerTextincludes it. Selecting.font-claude-responseskips it. - User blocks render a "You said: " heading separate from the body —
[data-testid=user-message]returns just the body, so use it directly. Don't use the wrapping turn div'sinnerTextor you'll get the preview duplicated. js()shares a global scope across calls in the samebh -cinvocation. Wrap extraction in an IIFE —const/letat top level will collide on re-run. (General bh gotcha, but bites here because you'll iterate selectors.)
Container-walk pattern
There's no semantic container ID, so find it by walking up from the first user message until you find an ancestor that also contains the last user message:
const userMsgs = [...document.querySelectorAll("[data-testid=user-message]")];
let p = userMsgs[0];
while (p && !p.contains(userMsgs[userMsgs.length-1])) p = p.parentElement;
const container = p;
// container.children is now the alternating list of turn divs
Then iterate container.children and check each child for either [data-testid=user-message] (user turn) or .font-claude-response (assistant turn). Children that match neither are headers/notices — skip.
Extraction
A working script lives next to this file: extract-share-transcript.py. Run it with:
CLAUDE_SHARE_URL=https://claude.ai/share/<uuid> \
OUTPUT_DIR=/path/to/transcripts \
bh -c "$(cat agent-workspace/domain-skills/claude-ai/extract-share-transcript.py)"
The script reads both via env vars (browser-harness's -c doesn't forward extra argv, so env vars are the cleanest passthrough).
It produces two files in the output dir, named from the conversation title slug:
<slug>.json—{title, source_url, turns: [{role, text}]}<slug>.md— LLM-friendly transcript with## Human/## Assistantheaders
What this skill does NOT cover
- Live conversations (
claude.ai/chat/<uuid>) — different DOM, requires the user's own session and may include streaming/in-progress messages - Artifacts inside the conversation — captured as plain text only; the artifact panel is a separate iframe-like surface not covered here
- Attachments — the page-header notice mentions "Shared snapshot may contain attachments and data not displayed here"; share exports are text-only by design