1Password 명령줄 도구의 설치와 인증을 확인하고 저장된 비밀번호와 API 키를 명령이나 설정에 전달합니다. 데스크톱 앱 연동과 서비스 계정도 지원합니다.
- 1Password 명령줄 도구 설치
- API 키를 명령에 전달
- CI에서 서비스 계정 인증 사용
동작 확인이나 PR 녹화에 자막, 확대, 재생 속도 조절을 적용해 설명이 담긴 MP4를 만듭니다. 원본 영상은 증거로 보존합니다.
Capture and annotate PR screenshots and videos with measured arrows, outlines, alignment guides, spacing brackets, captions, zoom, and speed cues.
동작 확인이나 PR(변경 제안) 녹화에 자막, 확대, 재생 속도 조절을 적용해 설명이 담긴 MP4를 만듭니다. Control UI 녹화 도우미로 조작과 설명 시점을 기록하거나, 기존 .mov 또는 .webm에 cues.json을 첨부해 가공합니다. 자막은 영상에 입혀지며 음성 내레이션은 생성하지 않습니다.
변경한 화면의 조작 방법을 검토자에게 보여 주거나 작은 버튼을 확대할 때 사용합니다. 시작과 대기 구간은 빠르게 재생하고, 확인이 필요한 조작은 정상 속도로 보여 줍니다.
저장소의 녹화 도우미와 렌더링 스크립트, 시스템의 ffmpeg와 ffprobe를 사용합니다. 자막 이미지 생성에는 Playwright Chromium이 필요합니다. 가변 프레임률 영상은 작업용 복사본을 변환합니다. 원본을 보존하고 공개 전에 사적인 내용과 자막 시점 오류를 확인합니다. 영상과 .artifacts/는 커밋하지 않습니다.
이 소개문은 공개된 SKILL.md를 바탕으로 AI(OpenAI Codex)가 작성했습니다. 정확한 사양은 아래 원문을 확인하세요.
AI가 SKILL.md의 내용만을 근거로 4가지 관점에서 판정했습니다. 스타 수나 인기도는 판정에 넣지 않았습니다.
총점은 각 항목을 A=25점, B=15점, C=5점으로 더한 값입니다. 2026년 10월 19일 기준으로 AI(OpenAI Codex)가 판정했습니다. 참고용이므로 설치 전에 원문도 확인해 주세요.
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Use this developer skill for captioned Control UI recordings and native captures.
Captions are burned into an MP4; they are visual narration, not synthesized audio.
Keep the raw video as evidence, attach the polished MP4, and never commit media or
.artifacts/ output. Inspect the entire capture for unrelated or private content
before publishing under the PR's existing authorization.
Choose marks that demonstrate the change. Use arrows to locate a control, outlines
to identify a region, alignment guides to compare edges or centers, and spacing
brackets to measure gaps. Measurements come from locator.boundingBox() after the
asserted layout settles; the renderer calculates the labels. Keep the reference
element first and use the same viewport, crop, scale, and edge before and after.
Keep marks sparse and labels in empty space. Preserve the raw capture.
For screenshot markup or timed video marks, read annotations. Both paths use the same SVG geometry renderer. Annotations are composited before zoom, while narration stays fixed in the viewport. Re-measure and replace marks after scrolling or layout changes; a cue describes a stationary capture, not a live element tracker. Clear marks during movement when they would become stale.
From the repository root, save this script as .artifacts/proof-video/record.mts
(create the parent directory first), then run
node --import tsx .artifacts/proof-video/record.mts. It starts an isolated mocked
Control UI, records five captions, a real toolbar zoom, fast-forwarded startup, and an eight-second pause
at 4× speed. Every invocation allocates a fresh evidence directory.
import { chromium } from "playwright";
import { createControlUiE2eArtifactDir } from "../../ui/src/test-helpers/control-ui-e2e-artifacts.ts";
import {
installMockGateway,
resolvePlaywrightChromiumExecutablePath,
startControlUiE2eServer,
} from "../../ui/src/test-helpers/control-ui-e2e.ts";
import { startProofRecording } from "../../ui/src/test-helpers/proof-video.ts";
const dir = createControlUiE2eArtifactDir("captioned-toolbar", ".artifacts/proof-video");
const server = await startControlUiE2eServer(undefined, { source: true });
const browser = await chromium.launch({
executablePath: resolvePlaywrightChromiumExecutablePath(chromium.executablePath()),
args: ["--force-device-scale-factor=2"],
});
try {
const recording = await startProofRecording(browser, {
dir,
contextOptions: { colorScheme: "dark", locale: "en-US", reducedMotion: "reduce" },
});
const { page } = recording;
const sessions = ["Main", "Weekly planning", "Reading list"].map((label, index) => ({
key: index === 0 ? "agent:main:main" : `agent:main:proof-${index}`,
label,
displayName: label,
kind: "direct",
status: "done",
hasActiveRun: false,
updatedAt: Date.parse("2026-09-01T12:00:00Z") - index * 60000,
contextTokens: 200000,
totalTokens: 0,
model: "gpt-5.6-luna",
modelProvider: "openai",
}));
const gateway = await installMockGateway(page, {
sessionKey: "agent:main:main",
sessionArchiveFiltering: true,
methodResponses: {
"sessions.list": {
sessions,
count: sessions.length,
totalCount: sessions.length,
hasMore: false,
offset: 0,
limitApplied: 50,
nextOffset: null,
path: "",
ts: sessions[0].updatedAt,
defaults: { contextTokens: 200000, model: "gpt-5.6-luna", modelProvider: "openai" },
},
},
});
recording.caption("Open the mocked Control UI.");
recording.speed(8);
await page.goto(`${server.baseUrl}chat`);
await gateway.waitForRequest("sessions.list");
await page.getByRole("link", { name: "Weekly planning", exact: true }).waitFor();
const toolbar = page.locator(".sidebar-session-toolbar");
await toolbar.getByText("Sessions", { exact: true }).waitFor();
recording.speed(1);
await page.waitForTimeout(1500);
recording.caption("Find your conversations in the session list.");
await page.waitForTimeout(3000);
await recording.zoom(toolbar);
recording.caption("Zoom in on the session filter controls.");
await toolbar.getByRole("button", { name: "Filter & sort" }).click();
await page.locator(".sidebar-session-sort-menu").waitFor({ state: "visible" });
await page.waitForTimeout(4000);
recording.zoomOut();
recording.caption("Fast-forward an eight-second pause.");
recording.speed(4);
await page.waitForTimeout(8000);
recording.speed(1);
recording.caption("Return to the full session list.");
await page.getByRole("menuitemradio", { name: "All", exact: true }).click();
await page.waitForFunction(() =>
document
.querySelector(".sidebar-session-sort")
?.classList.contains("sidebar-session-sort--filtered"),
);
await page.waitForTimeout(3000);
console.log(JSON.stringify(await recording.finish(), null, 2));
} finally {
await browser.close();
await server.close();
}
Use caption(text) to replace the current caption, clearCaption() to end it,
zoom(locatorOrRect, { factor: 2, padding: 24, ease: 0.5 }) to start or replace a
zoom, and zoomOut() to end it. Rectangles use viewport CSS pixels. Padding
expands and clamps the target rectangle; its center determines the zoom focus.
speed(factor) changes playback speed until the next call; speed(1) restores
normal playback. finish() closes open cues at the finalized video's end,
closes the recording context, rasterizes captions, writes cues.json, and returns
{ dir, videoPath, cuesPath }. The caller closes the browser and server.
The helper is test tooling at ui/src/test-helpers/proof-video.ts; production
code must not import it. contextOptions carries ordinary Playwright context
options, while the helper owns the viewport, recording size, and device scale.
Keep narration to one or two short lines; oversized text fails instead of
silently clipping. Caption PNGs are transparent viewport-width, 120-pixel-high
strips with a centered dark rounded card and 30-pixel system text.
Use the exact directory printed by the recording:
node --import tsx scripts/render-proof-video.mts --cues .artifacts/proof-video/<capture>/cues.json
The renderer prints the output path and duration. Optional --input <video>
overrides the raw input and --out <file.mp4> chooses the output. Existing output
files are refused, preserving raw evidence and earlier renders. It uses trusted
system ffmpeg and ffprobe, never Playwright's bundled encoder. Install a full
system build with brew install ffmpeg or apt install ffmpeg.
Speed segments split the raw timeline with trim/setpts/concat, then normalize it
to the input frame rate. Caption windows and zoom transitions are remapped to
output time. Zoom uses zoompan with d=1 and matching input FPS; a 100-frame,
4-second, 25-FPS synthetic capture retained exactly 100 frames and 4 seconds,
with a visibly and numerically different zoom hold. Dynamic crop width/height
expressions do not animate: ffmpeg evaluates them only at initialization.
Output is viewport-sized H.264 MP4, yuv420p, CRF 23, with faststart and no audio.
Save a hand-written cues.json beside an untouched native .mov or .webm.
Use scale for actual raw pixels per viewport CSS pixel, not display metadata.
Input dimensions must equal viewport × scale and frame rate must be constant.
All times below are seconds in the RAW video. Caption image paths are relative
to the sidecar directory. Zoom cues cannot overlap other zoom cues; speed cues
cannot overlap other speed cues. Adjacent cues are allowed.
{
"version": 1,
"video": "capture.mov",
"viewport": { "width": 1280, "height": 800 },
"scale": 2,
"captionHeight": 120,
"cues": [
{
"kind": "caption",
"start": 1,
"end": 4,
"text": "Inspect the session controls.",
"image": "captions/0.png"
},
{
"kind": "zoom",
"start": 1,
"end": 4,
"rect": { "x": 20, "y": 150, "width": 280, "height": 80 },
"factor": 2,
"ease": 0.5
},
{ "kind": "speed", "start": 5, "end": 9, "factor": 4 }
]
}
node --import tsx scripts/render-proof-video.mts --cues <dir>/cues.json --rasterize
--rasterize creates missing caption and annotation PNGs with fresh Playwright Chromium pages.
The exported rasterizeProofOverlays(browser, cues, dir) also supports programmatic
native-capture workflows. Neither path needs libass or drawtext: Chromium
rasterizes text and geometry. ffmpeg overlays annotations before zoom and captions
after zoom.
On macOS with Playwright 1.62.1 and Chromium 151.0.7922.34, context
deviceScaleFactor: 2 produced a 2560×1600 WebM containing only 1280×800 page
content plus gray padding. Launching with --force-device-scale-factor=2 and
leaving context emulation at 1 recorded true 2× content: a 100-CSS-pixel marker
measured 200×200 at raw coordinates (2360, 1400). The helper defaults to
deviceScaleFactor: 2 for recording size and sidecar scale; the launch flag in
the template is required. This parameter does not enable context emulation.
For verified 1× capture, launch without the flag and pass deviceScaleFactor: 1.
The renderer uses Lanczos pre-upscaling for 1× sources, but cannot restore lost
detail: a 2× zoom on those recordings is softer.
The cue clock starts at context creation using performance.now(). Playwright's
video clock can differ by roughly 100 ms, and startup/load can increase the skew;
inspect action/caption alignment in the finalized recording. Avoid clipped,
locator, or full-page screenshots during recording: Chromium can corrupt the
screencast surface. Caption rasterization happens only after recording closes,
in a fresh context. See the Control UI E2E skill's screenshot guidance.
GitHub's player ignores subtitle tracks, so captions must be burned in. The
renderer requires crop, scale, overlay, setpts, trim, concat, fps, and zoompan in
the system ffmpeg build. It does not use subtitles= or drawtext=. Variable
frame-rate native captures need conversion to a separate constant-frame-rate
working copy first; retain the original. Frame-rate quantization can shift
speed-segment boundaries by about one output frame. Check output duration and
extract caption, zoom-hold, and sped-segment frames before attaching the MP4.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
1Password 명령줄 도구의 설치와 인증을 확인하고 저장된 비밀번호와 API 키를 명령이나 설정에 전달합니다. 데스크톱 앱 연동과 서비스 계정도 지원합니다.
OpenClaw에 전달한 자연어 요청을 Claude Code 같은 외부 코딩 에이전트로 연결합니다. ACP나 acpx로 작업을 시작하거나 이어가며 대화 맥락을 유지합니다.
OpenClaw에 모델 제공업체를 추가하고 실제 응답을 확인합니다. 인증 정보는 환경 변수나 파일 참조로 다루며 설정 검증과 결과 보고도 진행합니다.
AI 에이전트의 작업 로그를 GitHub PR이나 이슈에 첨부할 기록으로 정리합니다. 관련 내용을 추리고 민감한 정보를 제거해 미리 보며, 별도 승인을 받아 게시합니다.
에이전트에서 macOS의 Apple Notes 메모를 생성, 검색, 편집, 삭제합니다. 폴더 간 이동과 HTML 또는 Markdown 내보내기도 지원합니다.
Mac에서 Apple Reminders의 기한이 있는 할 일을 확인하고 추가·편집합니다. 목록 관리와 완료·삭제를 통해 iPhone이나 iPad에서 보는 작업을 정리합니다.