Agent Skills › liaohch3/claude-tap

liaohch3/claude-tap

GitHub

用于在PR中执行Codex E2E端到端验证,通过claude-tap连接真实Codex CLI,捕获OpenAI Responses API追踪并生成截图证据。适用于涉及捕获、代理、渲染或会话逻辑变更的PR。

11 skills 2,570

Install All Skills

npx skills add liaohch3/claude-tap --all -g -y
More Options

List skills in collection

npx skills add liaohch3/claude-tap --list

Skills in Collection (11)

用于在PR中执行Codex E2E端到端验证,通过claude-tap连接真实Codex CLI,捕获OpenAI Responses API追踪并生成截图证据。适用于涉及捕获、代理、渲染或会话逻辑变更的PR。
PR修改了捕获、代理、查看器渲染或会话行为 PR涉及客户端启动逻辑、追踪顺序、内容块、工具或Token使用变更
.agents/skills/codex-e2e-test/SKILL.md
npx skills add liaohch3/claude-tap --skill codex-e2e-test -g -y
SKILL.md
Frontmatter
{
    "name": "codex-e2e-test",
    "tags": "testing, e2e, codex, responses-api",
    "description": "Run PR-grade real Codex E2E validation through claude-tap, including resume turns, multiple tool calls, optional image input, viewer verification, and screenshot evidence."
}

Codex E2E Test Skill

Run real end-to-end validation that starts claude-tap from local source, connects to the real Codex CLI via OAuth, captures OpenAI Responses API traces, and produces viewer screenshots suitable for PR evidence.

Use this skill for every PR that changes capture, proxying, viewer rendering, session/dashboard behavior, client launch logic, trace ordering, content blocks, tools, token usage, or screenshot/demo assets. If a PR cannot run this flow, state why in the PR and cover the same risk with another real client trace.

Prerequisites

  • codex CLI installed (npm install -g @openai/codex) and authenticated via OAuth
  • Python dev dependencies: uv sync --extra dev
  • Playwright installed: uv run playwright install chromium

Verify OAuth works:

codex exec "say hello" --dangerously-bypass-approvals-and-sandbox

If it fails with token errors, re-authenticate:

codex auth login

Key Difference from Claude E2E

Codex uses the OpenAI Responses API (/v1/responses) instead of Anthropic Messages API. With OAuth authentication, the upstream is https://chatgpt.com/backend-api/codex, not https://api.openai.com.

The proxy must be told the correct target with --tap-target.

Run a Real Codex E2E Trace

Prefer the resume + multimodal flow below for PR evidence. The simple commands are only smoke tests for checking local setup.

Simple (single tool call)

claude-tap --tap-client codex \
  --tap-target https://chatgpt.com/backend-api/codex \
  --tap-output-dir /tmp/codex-e2e \
  --tap-no-open \
  -- exec "say hello" \
  --dangerously-bypass-approvals-and-sandbox

Multi-call (triggers multiple API requests)

Use a task that requires shell tool use — this forces the agent to make multiple Responses API calls (models lookup + actual responses):

claude-tap --tap-client codex \
  --tap-target https://chatgpt.com/backend-api/codex \
  --tap-output-dir /tmp/codex-e2e \
  --tap-no-open \
  -- exec "Read pyproject.toml and tell me the project name and version" \
  --dangerously-bypass-approvals-and-sandbox

Expected: 4+ API calls (2x GET /v1/models + 2x POST /v1/responses).

Resume + Multimodal Content-Block Trace

Use this flow for viewer changes that affect message rendering, content block boundaries, tool call ordering, images, or copy/select behavior. It creates a real Codex session, resumes it at least once, forces multiple shell tool calls per user turn, and attaches an actual image so the trace includes multimodal content. This is the default PR evidence flow.

1. Prepare an isolated workspace

mkdir -p /tmp/claude-tap-real-codex-workspace
printf 'project = "claude-tap-real-codex-e2e"\n' \
  > /tmp/claude-tap-real-codex-workspace/project.toml

Create or copy a small valid PNG into the workspace. If you need a deterministic local image, generate it with Python's standard library:

python3 - <<'PY'
from pathlib import Path
import struct
import zlib

def chunk(kind: bytes, data: bytes) -> bytes:
    return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)

width = height = 8
rows = b"".join(b"\x00" + (b"\x2f\x80\xed\xff" * width) for _ in range(height))
png = (
    b"\x89PNG\r\n\x1a\n"
    + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0))
    + chunk(b"IDAT", zlib.compress(rows))
    + chunk(b"IEND", b"")
)
Path("/tmp/claude-tap-real-codex-workspace/input.png").write_bytes(png)
PY

2. Start a real Codex session through claude-tap

Run from the repository, but point Codex at the isolated workspace with -C. The prompt should explicitly require several tool calls so the viewer has enough messages, tool calls, and response blocks to inspect.

uv run claude-tap --tap-client codex \
  --tap-target https://chatgpt.com/backend-api/codex \
  --tap-output-dir /tmp/claude-tap-real-codex-traces \
  --tap-no-open \
  -- exec -C /tmp/claude-tap-real-codex-workspace \
  --image /tmp/claude-tap-real-codex-workspace/input.png \
  --dangerously-bypass-approvals-and-sandbox \
  "Inspect this workspace and the attached image. Use shell tools to run pwd, list files, inspect project.toml, inspect input.png, then write codex_e2e_report.txt with your findings. Keep all writes inside this workspace."

3. Resume the same Codex session with another real turn

Use the session id printed by the first Codex run when possible. Avoid relying on --last on busy maintainer machines because it can resume an unrelated recent Codex session.

uv run claude-tap --tap-client codex \
  --tap-target https://chatgpt.com/backend-api/codex \
  --tap-output-dir /tmp/claude-tap-real-codex-traces \
  --tap-no-open \
  -- exec resume <SESSION_ID_FROM_FIRST_RUN> \
  --image /tmp/claude-tap-real-codex-workspace/input.png \
  --dangerously-bypass-approvals-and-sandbox \
  "Continue the same investigation in /tmp/claude-tap-real-codex-workspace. Use shell tools to read /tmp/claude-tap-real-codex-workspace/codex_e2e_report.txt, compute the byte size of /tmp/claude-tap-real-codex-workspace/input.png, and write /tmp/claude-tap-real-codex-workspace/codex_e2e_followup.txt. Then summarize what changed since the previous turn."

4. Capture multi-position viewer screenshots

Take screenshots at multiple scroll positions, including a deeper position in the same detail pane. Store them under .agents/evidence/pr/<topic>/ and use raw.githubusercontent.com links in the PR body.

mkdir -p .agents/evidence/pr/codex-real-e2e

uv run python - <<'PY'
from pathlib import Path
from playwright.sync_api import sync_playwright

html_files = sorted(Path("/tmp/claude-tap-real-codex-traces").rglob("trace_*.html"))
if not html_files:
    raise SystemExit("No viewer HTML found in /tmp/claude-tap-real-codex-traces")
html = html_files[-1]
out_dir = Path(".agents/evidence/pr/codex-real-e2e")

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page(viewport={"width": 1760, "height": 1100}, device_scale_factor=1)
    page.goto(f"file://{html}", wait_until="domcontentloaded", timeout=10000)
    page.wait_for_selector(".sidebar-item", timeout=5000)
    page.evaluate("document.documentElement.setAttribute('data-theme', 'light')")

    # Select the last Responses call so resume context is visible.
    page.evaluate(
        """() => {
          const items = Array.from(document.querySelectorAll('.sidebar-item'));
          const responses = items.filter(item => item.textContent.includes('/v1/responses'));
          (responses.at(-1) || items.at(-1))?.click();
        }"""
    )
    page.wait_for_timeout(300)

    page.evaluate(
        """() => {
          for (const section of document.querySelectorAll('#detail .section')) {
            const title = section.querySelector('.title')?.textContent || '';
            const body = section.querySelector('.section-body');
            const header = section.querySelector('.section-header');
            if (!body || !header) continue;
            const shouldOpen = ['System Prompt', 'Messages', 'Response'].includes(title);
            const isOpen = body.classList.contains('open');
            if (shouldOpen !== isOpen) header.click();
          }
        }"""
    )
    page.wait_for_timeout(200)

    def shot(name: str, scroll_top: int) -> None:
        page.evaluate("y => { const d = document.querySelector('#detail'); if (d) d.scrollTop = y; }", scroll_top)
        page.wait_for_timeout(200)
        page.screenshot(path=str(out_dir / name), full_page=False)

    shot("codex-real-top.png", 0)
    shot("codex-real-mid.png", 700)
    shot("codex-real-deep.png", 1400)

    image_count = page.evaluate(
        """() => {
          const img = document.querySelector('#detail img');
          if (!img) return 0;
          img.scrollIntoView({ block: 'center', inline: 'nearest' });
          return document.querySelectorAll('#detail img').length;
        }"""
    )
    if image_count:
        page.wait_for_timeout(200)
        page.screenshot(path=str(out_dir / "codex-real-image.png"), full_page=False)

    browser.close()
PY

Validate screenshots:

uv run python scripts/check_screenshots.py .agents/evidence/pr/codex-real-e2e

5. Verify the trace

  • The output directory contains at least two real .jsonl traces and generated .html viewers from claude-tap.
  • The trace includes multiple POST /v1/responses entries with status 200 and non-zero token usage.
  • At least one request contains image content from the --image attachment.
  • The viewer shows Messages and Response sections with multiple content blocks separated cleanly.
  • Tool calls and tool results stay interleaved in the order they happened.
  • Copy buttons still copy the complete logical message/section text, not only one visual content block.
  • Text selection across adjacent content blocks remains possible in the browser.
  • Screenshot evidence includes at least two scroll depths for the same resumed trace, not only the top of the page.
  • If the trace contains image input, screenshot evidence includes the rendered image block or records why the client did not send image content into the API request body.

Taking Viewer Screenshots with Playwright

from playwright.sync_api import sync_playwright
import time, glob

html = glob.glob("/tmp/codex-e2e/trace_*.html")[-1]

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1440, "height": 1000})
    page.goto(f"file://{html}")
    page.wait_for_load_state("networkidle")
    time.sleep(1)

    # Select a Responses call (data-idx matches trace line index)
    page.click('.sidebar-item[data-idx="1"]')
    time.sleep(0.5)

    # Collapse System Prompt, keep Messages open
    page.evaluate("""() => {
        const h = document.querySelectorAll('.section-header')[1];
        const next = h.nextElementSibling;
        if (next && getComputedStyle(next).display !== 'none') h.click();
    }""")

    # Scroll to Messages section
    page.evaluate("""() => {
        document.querySelectorAll('.section-header')[2]
          .scrollIntoView({behavior: 'instant', block: 'start'});
    }""")
    time.sleep(0.3)
    page.screenshot(path="/tmp/codex-e2e/messages.png")

    browser.close()

Verification Checklist

  • Trace .jsonl has ≥2 POST /v1/responses entries
  • Response status is 200 (not 401/502)
  • Token counts are non-zero in Responses calls
  • HTML viewer is generated (trace_*.html)
  • Sidebar shows multiple calls with model name and token counts
  • Messages section shows user message text (verifies #41 fix)
  • Response section shows assistant reply (verifies #40 fix)

Troubleshooting

Symptom Cause Fix
WebSocket 502 then HTTP 401 Default target api.openai.com rejects ChatGPT OAuth tokens Use --tap-target https://chatgpt.com/backend-api/codex
Missing scopes: api.responses.write API key lacks Responses API access Use OAuth (codex auth login) instead of OPENAI_API_KEY
Only 1 API call Simple prompt completed in one round Use a task requiring tool use (file reads, shell commands)
OPENAI_BASE_URL is deprecated warning Codex v0.115+ prefers config.toml Harmless — proxy still works via env var

Notes

  • Codex with OAuth uses WebSocket first, then falls back to HTTP/SSE when proxied. The fallback is transparent — traces capture the HTTP/SSE path correctly.
  • Each codex exec session also calls GET /v1/models for model discovery.
  • The --dangerously-bypass-approvals-and-sandbox flag is required for non-interactive exec.
该技能用于生成演示视频资产。通过 tmux 和 asciinema 录制终端 E2E 会话,转换为 GIF 和 MP4;同时利用 Playwright CDP 连接浏览器捕获 HTML 查看器截图,支持可靠 UI 交互与滚动处理。
需要生成终端操作演示视频 需要将 E2E 测试过程录制成 GIF 或 MP4 需要截取 HTML 查看器的运行状态图片
.agents/skills/demo-video/SKILL.md
npx skills add liaohch3/claude-tap --skill demo-video -g -y
SKILL.md
Frontmatter
{
    "name": "demo-video",
    "description": "Generate demo assets (GIF\/MP4) from real tmux E2E runs and viewer screenshots using asciinema and Playwright",
    "user_invocable": true
}

Skill: Demo Video Generation

Generate demo assets from a real tmux E2E run and viewer screenshots.

Proven Workflow

1) Record terminal session in tmux with asciinema

tmux new-session -d -s demo -x 160 -y 46
tmux send-keys -t demo "asciinema rec /tmp/claude-tap-recordings/demo.cast" Enter
tmux send-keys -t demo "cd /path/to/claude-tap && scripts/run_real_e2e_tmux.sh" Enter
# ... wait until run finishes ...
tmux send-keys -t demo "exit" Enter

Notes:

  • Enter is the submit key for Claude Code TUI in tmux.
  • Use tool-triggering first prompt text so trace includes tool_use.

2) Convert .cast to GIF with agg

agg /tmp/claude-tap-recordings/demo.cast /tmp/claude-tap-recordings/demo.gif

3) Convert GIF to MP4 with ffmpeg

ffmpeg -y -i /tmp/claude-tap-recordings/demo.gif -movflags +faststart -pix_fmt yuv420p .agents/recordings/demo.mp4

Browser Screenshots (HTML Viewer)

Use Playwright CDP to attach to a running Chrome/Chromium instance on port 9222.

browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")
page = browser.contexts[0].pages[0]

Reliable UI interactions

  • Click entries by visible text content, for example:
page.query_selector('text="轮次 22"').click()
  • Open diff view by clicking button text:
page.query_selector('text="对比上次"').click()
  • For SPA/overflow layouts, scroll actual scrollable containers (not window):
page.evaluate("""
() => {
  for (const el of document.querySelectorAll('*')) {
    if (el.scrollHeight > el.clientHeight) {
      el.scrollTop += 300;
    }
  }
}
""")

Output Targets

  • docs/demo.gif
  • .agents/recordings/demo.mp4
  • Optional localized variants (docs/demo_zh.gif, .agents/recordings/demo_zh.mp4)

Avoid

  • Do not reference non-existent scripts such as cast_to_gif_ultra.py or make_final_demo_v2.py.
  • Do not hardcode selectors like .detail unless verified in the current viewer build.
用于测试嵌入HTML中的JS逻辑的两层策略:第一层用Python复现算法进行快速单元测试,第二层用Playwright启动真实浏览器验证DOM状态和用户交互。
需要测试嵌入在HTML文件中的JavaScript逻辑 验证纯计算算法或状态决策 验证按钮禁用、键盘事件等DOM交互
.agents/skills/js-in-html-testing/SKILL.md
npx skills add liaohch3/claude-tap --skill js-in-html-testing -g -y
SKILL.md
Frontmatter
{
    "name": "js-in-html-testing",
    "description": "Test JS logic embedded in HTML using two-layer strategy - Python unit tests + Playwright browser integration tests",
    "user_invocable": false
}

JS-in-HTML Two-Layer Testing Strategy

For JavaScript logic embedded in HTML files (e.g., diff navigation in viewer.html), use a two-layer testing approach.

Layer 1: Python Unit Tests (fast algorithm verification)

Replicate core JS algorithms in Python and verify correctness via pytest.

Best for: pure computation logic, state decisions, matching algorithms — anything that doesn't depend on the DOM.

Example: tests/test_diff_matching.py

# Replicate JS findPrevSameModel / findNextSameModel
def find_diff_parent_by_prefix(entries, idx):
    ...

def find_next_by_prefix(entries, idx):
    ...

# Replicate JS updateNavButtons state computation
def compute_nav_button_states(entries, cur_idx):
    prev_idx = find_diff_parent_by_prefix(entries, cur_idx)
    ...
    return (prev_enabled, next_enabled)

Advantages: fast (0.02s), no browser dependency, integrates with existing pytest setup.

Layer 2: Playwright Browser Integration Tests (DOM interaction verification)

Generate HTML with test data embedded, open it in real Chromium via Playwright, and verify DOM state and user interactions.

Best for: button disabled states, overlay show/hide, keyboard events, click navigation, etc.

Example: tests/test_nav_browser.py

Building test HTML

def _build_test_html():
    from claude_tap.viewer import VIEWER_SCRIPT_ANCHOR, _read_viewer_template

    template = _read_viewer_template()
    records = [json.dumps(e) for e in TEST_ENTRIES]
    data_js = "const EMBEDDED_TRACE_DATA = [\n" + ",\n".join(records) + "\n];\n"
    # Inject data into template
    return template.replace(
        VIEWER_SCRIPT_ANCHOR,
        f"<script>\n{data_js}</script>\n{VIEWER_SCRIPT_ANCHOR}",
        1,
    )

Playwright fixture

@pytest.fixture(scope="module")
def browser_page(html_file):
    from playwright.sync_api import sync_playwright
    pw = sync_playwright().start()
    browser = pw.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(f"file://{html_file}")
    page.wait_for_selector(".sidebar-item", timeout=5000)
    yield page
    browser.close()
    pw.stop()

Verifying DOM state

def _get_nav_state(page):
    return page.evaluate("""() => {
        const overlay = document.querySelector('.diff-overlay');
        if (!overlay) return { overlayExists: false };
        return {
            overlayExists: true,
            prevDisabled: overlay.querySelector('.diff-nav-prev')?.disabled,
            nextDisabled: overlay.querySelector('.diff-nav-next')?.disabled,
            title: overlay.querySelector('.diff-title')?.textContent,
        };
    }""")

Simulating user interaction

# Click buttons
page.click(".diff-nav-next")
# Keyboard navigation
page.keyboard.press("ArrowRight")
# Call internal JS functions
page.evaluate("selectEntry(2)")
page.evaluate("showDiff()")

Notes

  • Trace data may contain </script> text (e.g., when Claude discusses code), which breaks <script> block parsing. Escape it: line.replace("</script>", '</scr" + "ipt>')
  • Test data must match the viewer's expected JSONL format (including turn, duration_ms, request_id, request.path, etc.)
  • Playwright requires uv pip install playwright

Running

# Fast unit tests
uv run pytest tests/test_diff_matching.py -v

# Browser integration tests
uv run pytest tests/test_nav_browser.py -v

# All (excluding slow e2e)
uv run pytest tests/ --ignore=tests/test_e2e.py -v
用于验证维护者文档的结构合规性、标准时效性及计划状态。修改相关文档后运行,可提前捕获元数据过时、路径断裂及状态漂移问题,避免CI失败。
修改 .agents/docs/standards/ 下的文件 修改 .agents/docs/plans/ 下的文件 修改 .agents/docs/architecture/ 下的文件 修改 AGENTS.md
.agents/skills/legibility-check/SKILL.md
npx skills add liaohch3/claude-tap --skill legibility-check -g -y
SKILL.md
Frontmatter
{
    "name": "legibility-check",
    "description": "Validate maintainer docs structure, standards freshness, manifest paths, and plan state. Run this after modifying any file under .agents\/docs\/standards\/, .agents\/docs\/plans\/, .agents\/docs\/architecture\/, or AGENTS.md — it catches stale metadata, broken manifest paths, and plan state drift before CI does.",
    "user_invocable": true
}

Legibility Check

Run deterministic checks for maintainer docs that mirror what CI enforces via .github/workflows/legibility.yml. Catching these locally saves a round-trip to CI.

What it checks

  1. Standards freshness — every .agents/docs/standards/*.md must have frontmatter with owner, last_reviewed (ISO date), and source_of_truth. Files reviewed more than 60 days ago produce a warning.
  2. Architecture manifest — every path listed in .agents/docs/architecture/manifest.yaml under expected_paths: must exist in the repo.
  3. Plan state drift — every .agents/docs/plans/**/*.md must have a status frontmatter field (active, completed, or cancelled). Completed plans must not contain unchecked - [ ] checkboxes (outside fenced code blocks).

Run

uv run python scripts/check_legibility.py

Options:

  • --freshness-days N — change the staleness threshold (default: 60)
  • --strict-freshness — promote stale warnings to failures
  • --repo-root PATH — override repo root (default: cwd)

Fixing common failures

Failure Fix
missing frontmatter key 'X' Add the missing key to the YAML frontmatter block at the top of the file
last_reviewed must be ISO date Use YYYY-MM-DD format
last_reviewed ... is stale Update last_reviewed to today's date after reviewing the content
expected path missing: X Either create the file or remove the stale entry from manifest.yaml
status must be one of [...] Add status: active (or completed/cancelled) to plan frontmatter
completed plan still contains unchecked TODO Check off remaining items or change status back to active

After fixing

Re-run the check to confirm all issues are resolved before committing:

uv run python scripts/check_legibility.py && echo "All clear"
利用Playwright录制无头浏览器操作的.webm视频,用于PR审查、Bug修复验证及回归测试证据。支持添加暂停以增强可读性,结合终端日志实现双重验证,建议使用真实数据生成更具说服力的演示。
需要录制浏览器操作视频 进行PR代码审查 验证Bug修复效果
.agents/skills/playwright-screen-recording/SKILL.md
npx skills add liaohch3/claude-tap --skill playwright-screen-recording -g -y
SKILL.md
Frontmatter
{
    "name": "playwright-screen-recording",
    "description": "Record browser test videos with Playwright for PR review and bug fix verification",
    "user_invocable": false
}

Playwright Screen Recording for Test Verification

Use Playwright's video recording to capture headless browser operations as .webm videos for PR review or bug fix verification.

Core Usage

from playwright.sync_api import sync_playwright
import tempfile
from pathlib import Path

video_dir = Path(tempfile.mkdtemp())

with sync_playwright() as pw:
    browser = pw.chromium.launch(headless=True)
    context = browser.new_context(
        viewport={"width": 1400, "height": 900},
        record_video_dir=str(video_dir),
        record_video_size={"width": 1400, "height": 900},
    )
    page = context.new_page()
    page.goto(f"file:///path/to/test.html")

    # ... perform test actions, add pauses for readability ...
    page.wait_for_timeout(800)  # pause so viewers can see the current state

    page.close()
    context.close()  # video is finalized after context.close()
    browser.close()

# Retrieve the recorded video
videos = list(video_dir.glob("*.webm"))
if videos:
    videos[0].rename("demo.webm")

Use Cases

  • Bug fix verification: record before/after comparisons showing button state changes, UI behavior differences
  • PR Review: attach .webm video so reviewers can visually understand the change
  • Regression test evidence: record critical interaction paths as visual proof of passing tests

Recording Tips

Add pauses between actions

page.click(".some-button")
page.wait_for_timeout(800)   # let viewers see the click effect

page.keyboard.press("ArrowRight")
page.wait_for_timeout(600)   # let viewers see the navigation result

Combine assertions with terminal logging

state = get_nav_state(page)
print(f"[1] Title: {state['title']}")
print(f"    prev disabled: {state['prevDisabled']}  (expected: True)")
assert state["prevDisabled"] is True
print("    PASS")

Terminal output paired with the recorded video provides dual verification.

Prefer real data

Use existing real trace data from the project rather than synthetic data for more convincing demos:

# Build test HTML from a real trace file
records = []
with open(".traces/trace_xxx.jsonl") as f:
    for line in f:
        # Escape </script> to prevent breaking the HTML script block
        records.append(line.strip().replace("</script>", '</scr" + "ipt>'))

Notes

  • Video format is .webm (VP8 codec), supported by most players and browsers
  • Each page produces a separate video file
  • record_video_size controls video resolution — keep it consistent with viewport
  • Recording works in headless mode, no display required
  • Video files are typically a few hundred KB, suitable for attaching to PRs or chat
执行PR合并就绪检查,验证本地代码规范、CI状态及PR元数据。支持审查现有PR或仅运行本地门禁,提供明确的可合并判定与失败原因,确保提交前符合团队规范。
准备提交Pull Request前 需要审查现有PR的合并就绪状态
.agents/skills/pr-preflight/SKILL.md
npx skills add liaohch3/claude-tap --skill pr-preflight -g -y
SKILL.md
Frontmatter
{
    "name": "pr-preflight",
    "description": "Full pre-PR merge-readiness check. Run this before opening or merging a pull request — it validates local gates (lint, format, tests), CI status, screenshot evidence, and PR metadata in one pass. Also useful for reviewing an existing PR's readiness.",
    "user_invocable": true
}

PR Preflight

One-command merge-readiness check that combines local gates, CI status, and PR policy checks. Mirrors what reviewers look for so issues are caught before review, not during.

Check an existing PR

scripts/check_pr.sh <pr_number>

This runs:

  1. PR metadata — fetches title, state, draft status, merge state, branch info
  2. CI checks — counts pass/fail/pending GitHub Actions checks
  3. Local gates — runs lint, format, and tests locally:
    • uv run ruff check .
    • uv run ruff format --check .
    • uv run pytest tests/ -x --timeout=60
  4. PR body policy — validates required sections, evidence links, and blocked artifacts
  5. VerdictREADY or NOT_READY with specific reasons

Options

Flag Purpose
--repo OWNER/REPO Override repository (default: auto-detect via gh)
--no-tests Skip local test gates (useful when you just want CI + metadata check)

Exit codes

Code Meaning
0 All checks passed — ready to merge
1 Script error (missing tool, network failure)
2 Not ready — at least one check failed

Run local gates only (no PR needed)

If you haven't opened a PR yet and just want to validate locally:

uv run ruff check . && uv run ruff format --check . && uv run pytest tests/ -x --timeout=60

Or use the pre-commit hook (auto-runs lint on commit):

git config core.hooksPath .githooks

What blocks merge

The script reports NOT_READY if any of these are true:

  • PR is not in OPEN state
  • PR is still a draft
  • Merge state is not CLEAN or HAS_HOOKS
  • Any CI check is failing
  • Any CI check is still pending
  • Local gates (lint/format/tests) fail
  • PR body policy fails, such as missing evidence for runtime/viewer/client changes

Typical workflow

# 1. Make sure local gates pass
uv run ruff check . && uv run ruff format --check . && uv run pytest tests/ -x --timeout=60

# 2. Push and open PR
git push origin my-branch
gh pr create --title "feat: ..." --body "..."

# 3. Wait for CI, then run full preflight
scripts/check_pr.sh <pr_number>
将代码推送到GitHub并触发PyPI发布。若提交含功能变更则自动升级版本号,否则跳过。确保版本同步并通过CI完成测试与发布。
用户希望发布新版本到PyPI 推送包含特性或修复的代码更改
.agents/skills/push-release/SKILL.md
npx skills add liaohch3/claude-tap --skill push-release -g -y
SKILL.md
Frontmatter
{
    "name": "push-release",
    "description": "Push to GitHub and optionally bump version to trigger PyPI release",
    "user_invocable": true
}

Push & Release

Push code to GitHub. If the pending commits contain feature changes, bump the version number so CI auto-publishes to PyPI.

Workflow

  1. Check working tree: Ensure no uncommitted changes (prompt user to commit first if dirty).

  2. Determine whether a version bump is needed:

    • Read current version from pyproject.toml
    • Run git log origin/main..HEAD --oneline to inspect pending commits
    • If commits include feature changes (feat/fix/refactor, not purely docs/chore/test), a bump is needed
    • If only docs, tests, or CI changes, skip the bump
  3. If bump is needed:

    • Choose bump level based on change type:
      • patch (0.1.4 → 0.1.5): bug fixes, minor improvements
      • minor (0.1.4 → 0.2.0): new features
      • major (0.1.4 → 1.0.0): breaking changes
    • Update the version field in pyproject.toml
    • Update __version__ in claude_tap/__init__.py
    • git commit --amend to fold the version bump into the last commit (avoids extra commits)
  4. Push:

    git push origin main
    
  5. Confirm CI status:

Important: Version Bump = PyPI Release

The CI pipeline works as follows: push to main → auto-tag (only if version changed) → PyPI publish (triggered by new tag).

A version bump is the ONLY way to trigger a new PyPI release. If you push without bumping the version, CI will skip tagging and nothing gets published. So whenever commits include meaningful code changes (features, fixes, improvements), you MUST bump the version before pushing.

  • Version numbers in pyproject.toml and claude_tap/__init__.py must stay in sync
  • Only skip the bump for pure docs/test/CI changes that don't affect the published package
用于运行针对 Claude CLI 的真实端到端测试,支持 pytest 和 tmux 两种模式。通过启动本地 claude-tap 代理连接真实 CLI,验证 trace 输出、工具调用及 API 交互,确保功能正确性。
需要验证 Claude CLI 真实交互行为 执行端到端集成测试 检查 trace 日志或 HTML 报告生成
.agents/skills/real-e2e-test/SKILL.md
npx skills add liaohch3/claude-tap --skill real-e2e-test -g -y
SKILL.md
Frontmatter
{
    "name": "real-e2e-test",
    "tags": "testing, e2e, integration, tmux",
    "description": "Run real E2E tests against Claude CLI in pytest and tmux modes"
}

Real E2E Test Skill

Run real end-to-end tests that start claude-tap from local source, connect to the real Claude CLI, and verify trace output.

Prerequisites

  • claude CLI installed and authenticated
  • Python dev dependencies installed: uv sync --extra dev
  • tmux installed for interactive mode (brew install tmux)

Mode 1: Pytest Real E2E (7 test cases)

Run all real E2E tests

uv run pytest tests/e2e/ --run-real-e2e --timeout=300 -v

Run a single test

uv run pytest tests/e2e/test_real_proxy.py::TestRealProxy::test_single_turn --run-real-e2e --timeout=180 -v -s

Run with debug output

uv run pytest tests/e2e/ --run-real-e2e --timeout=300 -v -s --tb=long

Mode 2: tmux Interactive Real E2E

Use this when you need to validate non--p interactive behavior in Claude Code TUI.

scripts/run_real_e2e_tmux.sh

Optional overrides:

PROMPT_ONE="Use the shell tool to run command ls in the current directory, then reply with any 5 filenames only." \
PROMPT_TWO="Thank you." \
SUBMIT_KEY="Enter" \
PERMISSION_MODE="bypassPermissions" \
scripts/run_real_e2e_tmux.sh

Important tmux interaction notes:

  • Submit key is Enter for Claude Code TUI in tmux (confirmed working).
  • PROMPT_ONE should intentionally trigger tool use.
  • For portability, use grep -F instead of rg in shell assertions (rg may be unavailable).

Verification Checklist (for both modes)

  • Latest trace .jsonl contains both prompts (PROMPT_ONE, PROMPT_TWO)
  • At least 2 requests hit /v1/messages
  • At least one response content block has "type": "tool_use"
  • HTML viewer file is generated (trace_*.html)

Notes

  • Real E2E tests are skipped by default; --run-real-e2e is required.
  • Each pytest case starts a fresh proxy server and trace directory.
  • Timeouts are intentionally generous because real API calls are involved.
  • tmux mode includes retry logic for prompt submission and post-run JSONL assertions.

Pytest Test Cases

Test Timeout What It Tests
test_single_turn 180s Basic prompt/response trace capture
test_multi_turn 300s Conversation memory with -c flag
test_tool_use 180s Tool use generates multiple trace records
test_html_viewer_generated 180s HTML viewer generated with embedded trace data
test_api_key_redaction 180s API keys redacted from trace output
test_streaming_sse_capture 180s SSE events captured in streaming mode
test_trace_summary 180s CLI stdout includes trace summary and API call count
验证PR证据截图和Viewer HTML质量,确保符合CI标准。检查图片分辨率、大小及空白检测,利用Playwright验证HTML渲染正确性,防止无效或误导性提交。
修改 .agents/evidence/pr/ 或 .agents/recordings/ 下的图片文件 生成新的 viewer HTML 文件
.agents/skills/screenshot-validation/SKILL.md
npx skills add liaohch3/claude-tap --skill screenshot-validation -g -y
SKILL.md
Frontmatter
{
    "name": "screenshot-validation",
    "description": "Validate screenshot and viewer HTML quality for PR evidence. Run this after adding or modifying images under .agents\/evidence\/pr\/ or .agents\/recordings\/, or after generating a new viewer HTML file. Combines image quality checks (resolution, blankness, file size) with Playwright-based viewer rendering verification.",
    "user_invocable": true
}

Screenshot Validation

Validate that evidence images and viewer HTML files meet quality standards before committing. This catches issues that would otherwise fail CI or produce misleading PR evidence.

Image quality check

Checks PNG/JPG/GIF/WEBP files for:

  • Minimum dimensions: 400x400 pixels (hard fail)
  • Desktop viewport width: >= 1280px (warning if narrower)
  • File size: <= 5MB (warning if larger)
  • Blankness detection (PNG only): fails if > 90% of pixels are white/transparent

Run on specific files or directories

uv run python scripts/check_screenshots.py .agents/evidence/pr/
uv run python scripts/check_screenshots.py .agents/recordings/
uv run python scripts/check_screenshots.py path/to/specific-image.png

Run on git-staged images

scripts/check_screenshots.sh

This shell wrapper automatically finds staged PNG/JPG files and runs the quality check on them — useful as a pre-commit sanity check.

Viewer HTML rendering verification

Uses Playwright (headless Chromium) to verify that generated viewer HTML files actually render correctly — not just raw JSON or Python errors.

Checks:

  • No JavaScript errors on page load
  • Normal traces render a sidebar with entries and a detail panel
  • Empty embedded traces render the explicit "No API calls captured" state
  • Body text doesn't contain raw JSON dumps or Python tracebacks

Run

uv run python scripts/verify_screenshots.py .traces/trace_*.html

Requires Playwright to be installed (uv pip install playwright && playwright install chromium).

Typical workflow

After generating new evidence for a PR:

# 1. Check image quality
uv run python scripts/check_screenshots.py .agents/evidence/pr/

# 2. If you generated new viewer HTML, verify it renders
uv run python scripts/verify_screenshots.py .traces/trace_*.html

# 3. If all passes, stage and commit
git add .agents/evidence/pr/

Fixing common failures

Failure Fix
very small image (WxH; minimum is 400x400) Retake screenshot at a larger viewport or higher resolution
narrow desktop viewport (Wpx < 1280px) Resize browser window to >= 1280px wide before capturing
mostly blank/white image Ensure the screenshot captures actual content, not an empty page
No sidebar — viewer not rendered Viewer HTML is broken; regenerate from trace JSONL
JS errors Check viewer.html for syntax errors in embedded data
自动补全 viewer_i18n.json 中缺失的多语言翻译。基于英中源文本,利用 OpenRouter 生成日、韩、法、阿、德、俄译文。支持干跑预览、自定义模型及文件路径,确保多语言 UI 字符串完整性。
添加或修改了 viewer_i18n.json 中的英文或中文 UI 字符串 需要为其他六种目标语言自动填充缺失的翻译键值
.agents/skills/translate-i18n/SKILL.md
npx skills add liaohch3/claude-tap --skill translate-i18n -g -y
SKILL.md
Frontmatter
{
    "name": "translate-i18n",
    "description": "Fill missing i18n translations in the viewer source JSON. Run this after adding or modifying English or Chinese UI strings in claude_tap\/viewer_i18n.json — it auto-translates to ja, ko, fr, ar, de, ru via OpenRouter.",
    "user_invocable": true
}

Translate i18n

Automatically fill missing translations for the viewer's claude_tap/viewer_i18n.json source file. The script uses English and Chinese as source languages and translates to Japanese, Korean, French, Arabic, German, and Russian.

Prerequisites

  • OPENROUTER_API_KEY must be set in the environment (it is in the user's .zshrc)
  • Default model: google/gemini-2.5-flash

Workflow

1. Check what's missing (dry run)

Always preview first to confirm which keys need translation:

uv run python scripts/translate_i18n.py --dry-run

This parses claude_tap/viewer_i18n.json, finds keys present in both en and zh-CN but missing in other languages, and lists them without modifying the file.

2. Run the translation

uv run python scripts/translate_i18n.py

The script calls OpenRouter once per target language, then writes the translations back into viewer_i18n.json in-place.

3. Verify the result

After translation, run the formatter and tests to make sure nothing broke:

uv run python -m json.tool claude_tap/viewer_i18n.json >/dev/null
uv run pytest tests/test_translate_i18n.py -v

Options

Flag Purpose
--dry-run Show missing keys only, no file changes
--model MODEL Override the OpenRouter model (default: google/gemini-2.5-flash)
--target {viewer,cli} Translation target preset (default: viewer)
--file PATH Override target file path
--object-name NAME Override the legacy JS/Python i18n object name

How it works

The script:

  1. Loads the viewer i18n JSON source file
  2. Validates that every language block is a string-to-string map
  3. Identifies keys present in en + zh-CN but missing in target languages
  4. Sends a structured prompt to OpenRouter with existing translations for consistency
  5. Normalizes fullwidth punctuation for CJK languages (matching zh-CN style)
  6. Inserts new entries after the existing keys in each target language

Common scenarios

Added a new UI string: Add the key to both en and zh-CN blocks in claude_tap/viewer_i18n.json, then run this skill. The other 6 languages will be filled automatically.

Changed an existing string: The script only fills missing keys. To re-translate an existing key, first delete it from the target language blocks, then run the script.

用于在修改 claude-tap 核心逻辑后运行端到端测试。支持执行完整或单条 pytest 用例,并根据测试结果(如代理、清理、启动更新等)提供具体的故障排查指引。
修改了 claude-tap 核心逻辑 需要验证代理处理、SSE 重组装、TraceWriter 或 HTML 视图生成等功能 进行端到端回归测试
.agents/skills/e2e-test/SKILL.md
npx skills add liaohch3/claude-tap --skill e2e-test -g -y
SKILL.md
Frontmatter
{
    "name": "e2e-test",
    "description": "Run claude-tap end-to-end tests with pytest",
    "user_invocable": true
}

claude-tap E2E Test

Run this skill after modifying core logic in claude-tap, especially:

  • Proxy handler / SSE reassembly (__init__.py)
  • TraceWriter (JSONL writing, flush behavior)
  • HTML viewer generation (viewer.html, _generate_html_viewer)
  • LiveViewerServer (SSE streaming)
  • Signal handling / graceful shutdown
  • Manual update command / trace cleanup

Steps

  1. Run the full test suite:
uv run pytest tests/test_e2e.py -v --timeout=120

Or run a single test:

uv run pytest tests/test_e2e.py::test_e2e -v           # Full E2E pipeline
uv run pytest tests/test_e2e.py::test_trace_cleanup -v  # Trace cleanup
uv run pytest tests/test_e2e.py::test_startup_does_not_contact_pypi -v  # No startup update network
  1. Read the output. Each test prints PASSED or FAILED.

  2. If tests fail, check:

    • test_e2e fails: Core proxy pipeline issue. Check proxy_handler, _handle_streaming, TraceWriter.write.
    • test_trace_cleanup fails: Manifest logic issue. Check _load_manifest, _cleanup_traces, _register_trace.
    • test_startup_does_not_contact_pypi fails: Startup made an unexpected update-related network request.
    • test_live_viewer_ fails*: Viewer HTML issues. Check viewer.html for preserveDetail chain, updateNavButtons.
    • Timeout: May be a network/port issue, not a claude-tap bug.

ホーム - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-23 00:52
浙ICP备14020137号-1 $お客様$