New:Socket for Asana Is Now Available.Learn more
Get Started

@clipy/cli

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@clipy/cli - npm Package Compare versions

Comparing version
0.9.3
to
0.11.0
+83
dist/proofFrames.js
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 };
}
+9
-2

@@ -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,

@@ -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"

@@ -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