@clipy/cli
Advanced tools
| import { existsSync, statSync } from "node:fs"; | ||
| import { resolve } from "node:path"; | ||
| import { resolveFf, runFf } from "./context/ffmpeg.js"; | ||
| const MAX_FRAMES = 50; | ||
| const MAX_TOTAL_SECONDS = 300; | ||
| const MAX_IMAGE_BYTES = 50 * 1024 * 1024; | ||
| const MAX_TOTAL_IMAGE_BYTES = 250 * 1024 * 1024; | ||
| const SUPPORTED_IMAGE_EXTENSIONS = /\.(?:png|jpe?g|webp)$/i; | ||
| function evenDimension(value, label) { | ||
| if (!Number.isInteger(value) || value < 320 || value > 3840) { | ||
| throw new Error(`${label} must be an integer between 320 and 3840`); | ||
| } | ||
| if (value % 2 !== 0) { | ||
| throw new Error(`${label} must be even for WebM encoding`); | ||
| } | ||
| return value; | ||
| } | ||
| function validateFrames(paths) { | ||
| if (paths.length === 0) | ||
| throw new Error("proof needs at least one --frame"); | ||
| if (paths.length > MAX_FRAMES) { | ||
| throw new Error(`proof accepts at most ${MAX_FRAMES} frames`); | ||
| } | ||
| let totalBytes = 0; | ||
| return paths.map((input) => { | ||
| const path = resolve(input); | ||
| if (!existsSync(path)) | ||
| throw new Error(`proof frame does not exist: ${path}`); | ||
| const stat = statSync(path); | ||
| if (!stat.isFile()) | ||
| throw new Error(`proof frame is not a file: ${path}`); | ||
| if (stat.size === 0) | ||
| throw new Error(`proof frame is empty: ${path}`); | ||
| if (stat.size > MAX_IMAGE_BYTES) { | ||
| throw new Error(`proof frame exceeds 50 MiB: ${path}`); | ||
| } | ||
| totalBytes += stat.size; | ||
| if (totalBytes > MAX_TOTAL_IMAGE_BYTES) { | ||
| throw new Error("proof frames exceed the 250 MiB total input limit"); | ||
| } | ||
| if (!SUPPORTED_IMAGE_EXTENSIONS.test(path)) { | ||
| throw new Error(`unsupported proof frame format (use PNG, JPEG, or WebP): ${path}`); | ||
| } | ||
| return path; | ||
| }); | ||
| } | ||
| /** | ||
| * Turns a bounded sequence of screenshots into a silent WebM. Each screenshot | ||
| * is held for the same duration; narration/captions remain separate so the | ||
| * server can expose them as timestamped agent evidence. | ||
| * | ||
| * Every argument goes directly to spawn(), never through a shell. Frame paths | ||
| * may therefore contain whitespace or shell metacharacters safely. | ||
| */ | ||
| export async function renderProofFrames(opts) { | ||
| const framePaths = validateFrames(opts.framePaths); | ||
| if (!Number.isFinite(opts.holdSeconds) || opts.holdSeconds < 0.25 || opts.holdSeconds > 30) { | ||
| throw new Error("--hold must be between 0.25 and 30 seconds"); | ||
| } | ||
| const durationSeconds = framePaths.length * opts.holdSeconds; | ||
| if (durationSeconds > MAX_TOTAL_SECONDS) { | ||
| throw new Error(`proof video is capped at ${MAX_TOTAL_SECONDS} seconds`); | ||
| } | ||
| const width = evenDimension(opts.width, "--width"); | ||
| const height = evenDimension(opts.height, "--height"); | ||
| const ffmpeg = await resolveFf("ffmpeg", opts.notify); | ||
| const args = ["-hide_banner", "-loglevel", "error"]; | ||
| for (const framePath of framePaths) { | ||
| args.push("-loop", "1", "-t", String(opts.holdSeconds), "-i", framePath); | ||
| } | ||
| const filters = framePaths.map((_frame, index) => `[${index}:v]scale=${width}:${height}:force_original_aspect_ratio=decrease,` + | ||
| `pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:color=black,` + | ||
| `setsar=1,fps=30,format=yuv420p[v${index}]`); | ||
| filters.push(`${framePaths.map((_frame, index) => `[v${index}]`).join("")}` + | ||
| `concat=n=${framePaths.length}:v=1:a=0[outv]`); | ||
| args.push("-filter_complex", filters.join(";"), "-map", "[outv]", "-c:v", "libvpx", "-deadline", "good", "-cpu-used", "4", "-crf", "28", "-b:v", "0", "-an", "-y", opts.outputPath); | ||
| const result = await runFf(ffmpeg, args, Math.max(120_000, durationSeconds * 10_000)); | ||
| if (result.code !== 0) { | ||
| const detail = result.stderr.trim().slice(-1_000); | ||
| throw new Error(`ffmpeg could not create the proof video${detail ? `: ${detail}` : ""}`); | ||
| } | ||
| return { videoPath: opts.outputPath, framePaths, durationSeconds }; | ||
| } |
@@ -493,2 +493,3 @@ /** | ||
| created: body.created !== false, | ||
| refreshed: body.refreshed === true, | ||
| folderName: typeof body.folderName === "string" ? body.folderName : null, | ||
@@ -601,3 +602,5 @@ classification: parseClassification(body.classification, compiled.manifest.durationMs), | ||
| const classification = synced.classification; | ||
| notify(`Synced ✓ — ${synced.publicId} (private${synced.folderName ? `, filed in ${synced.folderName}` : ""}).`); | ||
| notify(synced.refreshed | ||
| ? `Already in your Knowledge Base — refreshed ✓ — ${synced.publicId} (private${synced.folderName ? `, still filed in ${synced.folderName}` : ""}).` | ||
| : `Synced ✓ — ${synced.publicId} (private${synced.folderName ? `, filed in ${synced.folderName}` : ""}).`); | ||
| if (classification) { | ||
@@ -715,3 +718,7 @@ const wanted = classification.frameTimestampsMs.length; | ||
| gapCount: report?.gaps.length ?? 0, | ||
| ...(synced ? { synced: true, publicId: synced.publicId } : opts.sync ? { synced: false } : {}), | ||
| ...(synced | ||
| ? { synced: true, publicId: synced.publicId, refreshed: synced.refreshed } | ||
| : opts.sync | ||
| ? { synced: false } | ||
| : {}), | ||
| ...(synced?.folderName ? { folderName: synced.folderName } : {}), | ||
@@ -718,0 +725,0 @@ classification: synced?.classification ?? null, |
+151
-6
@@ -13,3 +13,3 @@ /** | ||
| name: clipy | ||
| description: Read and create Clipy screen recordings, and turn any video into agent-readable context. Use when the user shares a clipy.online/video/<id> URL (watch, summarize, or act on a recording, bug report, or walkthrough), shares a YouTube URL or local video file as context/reference for a task ("implement what this video shows", "give me the context of this video" — import it with clipy context import), OR asks you to record your own work — demo a feature you built, capture a UI fix, or show a bug reproduction — and share it as a link. | ||
| description: Read and create Clipy screen recordings, turn screenshots or tool-native video into proof, and turn any video into agent-readable context. Use when the user shares a clipy.online/video/<id> URL (watch, summarize, or act on a recording, bug report, or walkthrough), shares a YouTube URL or local video file as context/reference for a task ("implement what this video shows", "give me the context of this video" — import it with clipy context import), OR asks you to verify your own work and share proof through Clipy. | ||
| --- | ||
@@ -19,3 +19,3 @@ | ||
| Written for @clipy/cli + @clipy/mcp 0.9.0 (the two versions move in lockstep). If | ||
| Written for @clipy/cli + @clipy/mcp 0.11.0 (the two versions move in lockstep). If | ||
| \`clipy --version\` reports older, upgrade first: \`npm i -g @clipy/cli@latest\`. | ||
@@ -25,5 +25,6 @@ | ||
| recording has a share link, an AI transcript + summary, key moments, and a | ||
| machine-readable context document. With the CLI you can also CREATE recordings: | ||
| capture a running web app headlessly, or capture the real Mac screen through the | ||
| running Clipy app, narrate with timestamped marks, and hand back a watchable link. | ||
| machine-readable context document. With the CLI you can also CREATE proof: | ||
| combine screenshots from whatever tool you already use, upload a tool-native | ||
| WebM/MP4, capture a running web app headlessly, or capture the real Mac screen | ||
| through the running Clipy app, then hand back a watchable link. | ||
@@ -33,2 +34,8 @@ Commands below use \`clipy\`. If it is not on PATH, prefix with \`npx @clipy/cli\` | ||
| The canonical live operating contract is \`https://clipy.online/agents.md\`. Fetch | ||
| it when network access is available. It explains which surface to choose, auth and | ||
| scope boundaries, environment/profile preflight, proof, search, REST, safety, and | ||
| honest fallbacks. For the exact installed version, trust \`clipy guide --json\`; | ||
| for an MCP connection, trust its \`tools/list\` response. | ||
| ## Reading a recording (no auth needed for public links) | ||
@@ -53,2 +60,24 @@ | ||
| ## Search everything the user remembers | ||
| When the user refers to something they recorded, watched, imported, showed, or | ||
| discussed, search BOTH libraries first: | ||
| clipy memory search "authentication flow" --json | ||
| This is hybrid semantic + keyword search across Clipy recordings and imported/ | ||
| watched context. Use \`--kind recording\` or \`--kind context\` only when the user | ||
| clearly means one side. MCP equivalent: \`search_memory\`. | ||
| Read \`semantic.status\` before trusting an empty result. \`unavailable\` or | ||
| \`failed\` means the semantic index did not run and results are keyword-only: | ||
| say so and retry with literal phrasing rather than concluding the memory is absent. | ||
| For each hit, \`resolution=lexical|refined\` is an exact moment, \`window\` is a | ||
| span to inspect, and \`document\` is a whole-document match without an exact time. | ||
| Follow a recording hit with \`clipy transcript\` / \`clipy context\`; follow a | ||
| context hit with MCP \`read_context_document\`. | ||
| \`clipy search\` is the legacy recording-library search. Prefer | ||
| \`clipy memory search\` for new agent work. | ||
| ## Turning someone else's video into context (clipy context import) | ||
@@ -211,2 +240,114 @@ | ||
| ## Choose the proof path before recording | ||
| Do not start by launching a browser. First determine WHERE the agent is running, | ||
| WHAT must be verified, and WHETHER the target depends on an existing login. | ||
| 1. Read the repository's own \`AGENTS.md\` / \`CLAUDE.md\` / browser-testing | ||
| instructions. If they name a Chrome profile, test account, browser, port, or | ||
| authentication fixture, that project-specific choice wins. | ||
| 2. Inspect the change and make a coverage checklist: changed routes/pages, | ||
| important states and interactions, required identities/roles, and requested | ||
| viewports. A UI PR review should prove the built app on every material changed | ||
| surface, not merely record one convenient page. | ||
| 3. Run \`clipy doctor --json\`. Use its auth, Mac bridge, Playwright, and install | ||
| results to identify what this machine can actually do. | ||
| 4. Classify the environment and choose the narrowest truthful path: | ||
| - **Interactive Mac with an already-authenticated browser:** preserve that | ||
| real session. Prefer the current agent/browser tool's own WebM/MP4 or | ||
| screenshots and hand them to \`clipy proof\`. For continuous native proof, | ||
| use \`clipy sources --json\`, select the exact Chrome/app window, and record | ||
| it with \`--source mac-screen --window <exact-id>\`. | ||
| - **Interactive Windows/Linux desktop:** the Mac bridge is unavailable. Reuse | ||
| the existing browser/computer-use tool's video or screenshots with | ||
| \`clipy proof\`; otherwise use Playwright with an existing approved auth | ||
| state. Never fall back to whole-display capture silently. | ||
| - **SSH server / container / CI:** assume there is no usable desktop session. | ||
| For public routes, isolated headless Playwright is appropriate. For | ||
| authenticated routes, first reuse the repository's test login, | ||
| Playwright \`storageState\`, init script, or existing agent-owned browser | ||
| recording. If none exists, report the authentication blocker; do not type | ||
| personal credentials, copy cookies out of an unrelated browser, or record a | ||
| signed-out substitute and call it proof. | ||
| - **Public/local route with no login dependency:** a fresh isolated headless | ||
| browser is normally the cleanest option. | ||
| Before capture, visibly confirm the resolved target: expected URL, expected | ||
| signed-in/signed-out state, expected account/role when it is safe to display, and | ||
| the exact window/profile named by the repository or user. If any of those are | ||
| wrong, abort that take. | ||
| If another tool already owns the browser or its CDP debugger, do not attach Clipy | ||
| as a second debugger. Let that tool produce video/screenshots and use | ||
| \`clipy proof\`. Clipy-owned headless sessions are for cases where Clipy is the | ||
| browser owner; \`--source mac-screen\` is for an exact real Mac window. | ||
| ### UI PR / multi-page proof | ||
| For a request such as "review this UI PR and record every changed page": | ||
| - Derive the route/state checklist from the diff and acceptance criteria. | ||
| - Start the app build the user will actually run, then verify each checklist | ||
| item in that running artifact. | ||
| - Reuse one authenticated identity across the run when the routes share it. | ||
| Navigate within one recording and add a \`clipy chapter\` for every page or | ||
| major state, plus literal observed-value marks for the decisive result. | ||
| - Capture relevant responsive states when layout is part of the change. | ||
| - If the current tool records video, prefer one concise walkthrough and upload | ||
| it with \`clipy proof --video\`. If it only captures screenshots, use a focused | ||
| frame sequence; the frame limit is 50. | ||
| - If pages require different accounts, browser profiles, native apps, or privacy | ||
| boundaries, make separate proof recordings. Do not weaken authentication or | ||
| expose unrelated windows merely to force everything into one video. | ||
| - Return a short coverage list beside the watch and \`.md\` URLs so the reviewer | ||
| can see exactly which routes/states the recording proves. | ||
| ## Universal proof — use the tool the agent already has | ||
| When the user says "once you are done, verify it and send proof through Clipy", | ||
| finish the work and normal tests first. Then use the narrowest proof path the | ||
| current environment already supports. Do not install Open Browser Use, Browser | ||
| Use, Playwright, or another browser driver merely to make proof. | ||
| If the current browser/computer/simulator tool can save screenshots, capture a | ||
| short evidence sequence and let Clipy combine it: | ||
| clipy proof \\ | ||
| --frame /absolute/path/01-target.png \\ | ||
| --caption "Target: Settings page loaded; Save is disabled" \\ | ||
| --frame /absolute/path/02-result.png \\ | ||
| --caption "Result: Save is enabled after changing Time zone" \\ | ||
| --frame /absolute/path/03-persisted.png \\ | ||
| --caption "Persistence: Asia/Kolkata remains selected after reload" \\ | ||
| --hold 3 --title "Settings time-zone fix" --type bug --wait --json | ||
| - Use 2–4 frames when possible: identify the target, show the decisive action or | ||
| result, then show persistence/reload or a second viewport when relevant. | ||
| - \`--caption\` is optional; if used, repeat it exactly once per \`--frame\`. | ||
| Captions become timestamped agent narration. | ||
| - Frame mode accepts PNG, JPEG, and WebP, up to 50 images / 5 minutes, 50 MiB | ||
| per image and 250 MiB total. Output dimensions must be even integers between | ||
| 320 and 3840. It needs ffmpeg only to encode the supplied images; it does not | ||
| launch or control a browser. | ||
| - Captions are driver-attested evidence: record literal UI text, values, URL, | ||
| status, or dimensions you actually observed. They are not independent Clipy | ||
| assertions, so never phrase an inference as a verified fact. | ||
| If the current tool already recorded a video, hand the completed artifact to | ||
| Clipy without re-encoding: | ||
| clipy proof --video /absolute/path/verification.webm \\ | ||
| --title "Export flow verification" --type demo \\ | ||
| --note "0: Export page loaded" --note "6: Download completed" \\ | ||
| --wait --json | ||
| \`--video\` accepts WebM or MP4 and needs no browser automation dependency. | ||
| Playwright's \`recordVideo\`, a Browser Use export, a CI artifact, or any other | ||
| recorder is equally valid; Clipy is the proof sink, not the browser driver. | ||
| Capture only the relevant UI. Do not include secrets, private messages, customer | ||
| data, or unrelated windows. After upload, read the returned result, then run | ||
| \`clipy context <id>\` and confirm the narration matches the frames/video before | ||
| sharing both URLs. | ||
| ## Making a recording — headless web app | ||
@@ -561,2 +702,5 @@ | ||
| data) — the recording gets a shareable link. | ||
| - For \`clipy proof\`, screenshots/video must come from the verification you | ||
| actually performed. Captions and notes are agent attestations, so use literal | ||
| observed values and never imply Clipy independently checked them. | ||
| - ALWAYS verify before sharing: after upload run \`clipy wait <id> --for both\` | ||
@@ -584,3 +728,4 @@ then \`clipy context <id>\` and confirm the transcript matches what you meant to | ||
| \`npm i -g @clipy/cli@latest\`. \`clipy guide --json\` prints a machine-readable | ||
| manifest of every command, flag, env var, and exit code. | ||
| manifest of every command, flag, env var, and exit code. The site-wide surface | ||
| contract is \`https://clipy.online/agents.md\`. | ||
@@ -587,0 +732,0 @@ ## Deeper access |
+4
-2
| { | ||
| "name": "@clipy/cli", | ||
| "version": "0.9.3", | ||
| "version": "0.11.0", | ||
| "description": "Command-line interface for Clipy — list, search, and read your screen recordings' transcripts, AI summaries, and key moments from the terminal.", | ||
@@ -42,4 +42,6 @@ "license": "MIT", | ||
| "test:auth": "npm run build && node scripts/mock-auth-server.test.mjs && node scripts/doctor.test.mjs", | ||
| "test:proof": "npm run build && node scripts/proof-frames.test.mjs", | ||
| "test:memory": "npm run build && node scripts/memory-search.test.mjs", | ||
| "test:session": "npm run build && node scripts/auth-guard.test.mjs && node scripts/session-control.test.mjs", | ||
| "test": "npm run test:auth && npm run test:session && node scripts/context-sync.test.mjs && node scripts/context-frames.test.mjs && node scripts/context-youtube-transcript.test.mjs && node scripts/context-youtube-url-lang.test.mjs && node scripts/context-retry.test.mjs && node scripts/context-json-envelope.test.mjs && node scripts/context-ytdlp-update.test.mjs", | ||
| "test": "npm run test:auth && npm run test:session && node scripts/proof-frames.test.mjs && node scripts/memory-search.test.mjs && node scripts/context-sync.test.mjs && node scripts/context-frames.test.mjs && node scripts/context-youtube-transcript.test.mjs && node scripts/context-youtube-url-lang.test.mjs && node scripts/context-retry.test.mjs && node scripts/context-json-envelope.test.mjs && node scripts/context-ytdlp-update.test.mjs", | ||
| "prebuild": "node scripts/sync-context-core.mjs", | ||
@@ -46,0 +48,0 @@ "test:context": "npm run build && node scripts/context-sync.test.mjs && node scripts/context-frames.test.mjs && node scripts/context-youtube-transcript.test.mjs && node scripts/context-youtube-url-lang.test.mjs && node scripts/context-retry.test.mjs && node scripts/context-json-envelope.test.mjs && node scripts/context-ytdlp-update.test.mjs" |
+59
-3
@@ -16,6 +16,9 @@ # @clipy/cli | ||
| agents can act on what was recorded. This package is its terminal client. | ||
| The canonical agent operating contract is | ||
| [clipy.online/agents.md](https://clipy.online/agents.md). | ||
| The read commands are **read-only** with any key. The write commands — | ||
| [`record`](#record), the `session`/`mark` flow, and `transcript --replace` — create | ||
| recordings or replace a transcript, and work only with an `ingest`-scoped key. | ||
| [`proof`](#proof-from-any-agent-tool), [`record`](#record), the `session`/`mark` | ||
| flow, and `transcript --replace` — create recordings or replace a transcript, | ||
| and work only with an `ingest`-scoped key. | ||
@@ -61,2 +64,4 @@ ```bash | ||
| clipy list [-n 20] [--page 2] [--status ready,processing] [--json] | ||
| clipy memory search <query> [--kind recording|context] [--json] | ||
| # hybrid search across recordings + imported context | ||
| clipy search <query> # full-text search titles + descriptions | ||
@@ -73,2 +78,4 @@ clipy show <id|share-url> # metadata + share link | ||
| clipy wait <id> --for both # block until transcript/summary are ready | ||
| clipy proof --frame before.png --frame after.png # screenshots → proof video | ||
| clipy proof --video verification.webm # upload a tool-native recording | ||
| clipy record --url <app> [--for 15] # record a web app headlessly → a Clipy recording | ||
@@ -89,2 +96,19 @@ clipy session start --url <app> # start recording in the background while you work | ||
| ## Search all Clipy memory | ||
| `clipy memory search` uses the same hybrid semantic + keyword search as MCP | ||
| `search_memory`, across the owner's recordings and imported/watched context: | ||
| ```bash | ||
| clipy memory search "authentication flow" --json | ||
| clipy memory search "checkout error" --kind recording --limit 10 | ||
| ``` | ||
| Each result identifies its `kind`, title, timestamp or span, `resolution`, plain-text | ||
| snippet, and deep link. Inspect `semantic.status` before treating an empty result as | ||
| conclusive: `unavailable` or `failed` means the semantic index did not run and results | ||
| are keyword-only. `lexical`/`refined` are exact moments, `window` is a span to inspect, | ||
| and `document` has no exact timestamp. `clipy search` remains the legacy | ||
| recording-library search. | ||
| ## Import any video as context | ||
@@ -117,2 +141,34 @@ | ||
| ## Proof from any agent tool | ||
| `clipy proof` is the dependency-light handoff for coding agents. The agent keeps | ||
| using whatever browser, computer-use, simulator, or test tool it already has; | ||
| Clipy only assembles and uploads the evidence. | ||
| Turn saved screenshots into one short proof video: | ||
| ```bash | ||
| clipy proof \ | ||
| --frame /tmp/01-before.png --caption "Before: Save is disabled" \ | ||
| --frame /tmp/02-after.png --caption "After: Save is enabled" \ | ||
| --frame /tmp/03-reload.png --caption "Reload: the value persists" \ | ||
| --hold 3 --title "Settings fix proof" --type bug --wait --json | ||
| ``` | ||
| Or upload an existing recording without re-encoding: | ||
| ```bash | ||
| clipy proof --video /tmp/verification.webm \ | ||
| --note "0: opened settings" --note "6: saved successfully" \ | ||
| --title "Settings fix proof" --type bug --wait --json | ||
| ``` | ||
| Frame mode accepts PNG, JPEG, or WebP (up to 50 frames, 5 minutes, 50 MiB per | ||
| image, and 250 MiB total), and requires ffmpeg to create the silent WebM. | ||
| `--width`/`--height` must be even integers from 320–3840. Video mode accepts | ||
| WebM or MP4 and does not require Playwright, Browser Use, Open Browser Use, or | ||
| any other browser automation dependency. Captions and notes become timestamped | ||
| agent narration; they are attestations from the driving agent, not claims | ||
| independently verified by Clipy. | ||
| ## Record | ||
@@ -537,3 +593,3 @@ | ||
| Every command has machine-readable output. `--json` is supported on **`list`, `search`, | ||
| Every command has machine-readable output. `--json` is supported on **`list`, `memory search`, `search`, | ||
| `show`, `transcript`, `summary`, `moments`, `wait`, `record`, `session start/stop/status`, | ||
@@ -540,0 +596,0 @@ `mark`, `chapter`, `doctor`, and `playwright-path`** — stdout is the JSON payload, stderr is progress |
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
503509
5.89%23
4.55%9192
5.08%644
9.52%52
1.96%