Skip to main content

video-sharing

How Clips shares recordings — composes with the framework sharing skill and adds password, expiry, embed URLs, view-counting, and per-viewer "Viewed by" records. Use when wiring the share dialog, building embed links, adding a password, showing who viewed a clip and when, debugging who can see a recording, or when an agent receives a Clips link and needs its transcript or video frames.

Informations de source

Dépôt
BuilderIO/agent-native
Dernière activité de la source
9 octobre 2026 à 17:00
Langue détectée de SKILL.md
anglais
Étoiles
7 119
Forks
646

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
video-sharing
description
How Clips shares recordings — composes with the framework sharing skill and adds password, expiry, embed URLs, view-counting, and per-viewer "Viewed by" records. Use when wiring the share dialog, building embed links, adding a password, showing who viewed a clip and when, debugging who can see a recording, or when an agent receives a Clips link and needs its transcript or video frames.
# Video Sharing ## Rule Recording sharing uses the framework `sharing` system — not a custom share table. Recordings are registered via `registerShareableResource({ type: "recording", ... })` in `server/db/index.ts`. The `share-resource`, `unshare-resource`, `list-resource-shares`, and `set-resource-visibility` actions are auto-mounted and handle per-user grants, per-org grants, and the three visibility levels (`private` / `org` / `public`). Unlike the framework-wide private default, normal Clips recordings and uploaded videos default to **public** so their copied share links work immediately. Embedded bug-report recordings are the exception and default to organization visibility. Callers can still explicitly create a private or organization-only recording, and owners/admins can change visibility from the Share dialog. Organization admins can use `set-organization-branding` with `defaultVisibility=public|org|private` to choose the visibility applied when new recordings omit an explicit visibility. The default remains `public`, and an explicit visibility always wins — which is why bug-report recordings still land on `org`. Clips **adds two things** on top of the framework system: 1. **Password** — an optional bcrypt'd string on the `recordings` row. When set, all non-owner viewers must enter it to play the recording. 2. **`expiresAt`** — an optional ISO timestamp on the `recordings` row. After this time, all non-owner access is denied (even to principals with explicit grants). These are **additive** — they never grant access the framework denies, only tighten it. ## When to use Read this skill before: - Wiring the Share dialog on a recording page - Adding a password or expiry UI - Building embed URLs (`?t=`, `?autoplay=`, `?hideControls=`) - Building AI-readable public clip URLs or transcript/frame endpoints - Debugging "why can't Alice see this video?" - Touching `server/routes/video/[id].ts` or `server/routes/share/[id].ts` ## Data model touched - **`recordings.password`** (nullable text) — bcrypt hash. - **`recordings.expires_at`** (nullable ISO string). - **`recording_shares`** — framework-managed. Do not insert directly — use `share-resource`. - **`recordings.visibility`** — framework-managed column from `ownableColumns()`. - **`recording_viewers`** + **`recording_events`** — view counting. - **`recording_views`** — append-only per-view log (who viewed, when) backing the owner-facing "Viewed by" popover. See "View counting" below. - **`recording_agent_views`** — outside agents reading a clip through its public agent APIs, counted separately from humans. See "Agent views" below. ## Dropping in the share UI Clips' `app/components/player/share-dialog.tsx` is a **thin wrapper around the framework `ShareDialog`** from `@agent-native/core/client`. The framework component handles per-user / per-org grants, visibility, and tabbed copy-link / embed UI — Clips just composes it with recording-specific extras. ```tsx import { ShareDialog } from "@agent-native/core/client"; <ShareDialog resourceType="recording" resourceId={recording.id} resourceTitle={recording.title} shareUrl={`${origin}/share/${recording.id}`} embedUrl={`${origin}/embed/${recording.id}`} linkTabExtras={ <> {/* Password + expiry render in the Link tab, below the share URL. */} <PasswordField recordingId={recording.id} /> <ExpiryField recordingId={recording.id} /> </> } embedTabContent={<EmbedSnippetAndOptions recordingId={recording.id} />} />; ``` - `shareUrl` / `embedUrl` — the copy-link and embed URLs the framework renders in its tabs. - `linkTabExtras` — Clips-specific controls (password, expiry) shown beneath the link. - `embedTabContent` — full replacement for the Embed tab body (embed code, params like `?t=`, `?autoplay=`). The password and expiry fields call `update-recording --password=...` / `--expiresAt=...`. Keep Clips' share-dialog wrapper minimal — any new generic sharing feature belongs in the framework component, not here. ## Shared with me Use `list-recordings --view=shared` to list recordings the current user can access but does not own. The filter composes with `accessFilter`, so it includes direct user/org grants and organization-visible recordings while excluding public-link-only clips. The UI exposes the same collection at `/shared`; use `navigate --view=shared` to open it. `view-screen` returns the recordings currently visible in that collection. ## Discovery boundary for public clips Public recordings are unlisted-by-link for agent purposes: an agent may discover only recordings the current user owns or has already viewed. Do not use `list-recordings` or `search-recordings` to discover another user's public clips, to answer a time/date question about the clip already in context, or to recover from a failed direct lookup. If the user supplies another clip's share URL or id, use that explicit reference; otherwise stop and report the lookup failure. ## Access resolution The player and `/api/video/:id` route check access in this exact order: ```ts async function canAccess( recordingId: string, requester: Session | null, providedPassword?: string, ) { // 1. Framework check — owner, shared, or meets visibility. const access = await resolveAccess("recording", recordingId, requester); if (!access.allowed) return false; const rec = await getRecordingOrThrow(recordingId); // 2. Expiry — non-owner only. if (rec.expiresAt && requester?.email !== rec.ownerEmail) { if (new Date(rec.expiresAt) < new Date()) return false; } // 3. Password — non-owner only. if (rec.password && requester?.email !== rec.ownerEmail) { if (!providedPassword) return false; if (!(await bcrypt.compare(providedPassword, rec.password))) return false; } return true; } ``` Framework first, Clips additions second. Don't invert this — the framework owns the "is this row visible at all" question. ## Embed URLs Embeds live at `/embed/:shareId` (a share-scoped anonymous route). Supported query params: | Param | Meaning | | ----------------- | -------------------------------------------------- | | `?t=80` | Start playback at 80 seconds | | `?autoplay=1` | Autoplay (muted — browsers block unmuted autoplay) | | `?hideControls=1` | Hide the player chrome | | `?loop=1` | Loop playback | Build embed URLs via the `build-embed-url` action: ```ts const { url } = await callAction("build-embed-url", { id: recording.id, t: 80, autoplay: true, }); // -> /embed/<shareId>?t=80&autoplay=1 ``` ## Slack unfurls Clips can render Loom-style Slack previews through Slack App Unfurling. Configure the Slack app's `link_shared` event to call `/api/slack/unfurl`; the route verifies `SLACK_SIGNING_SECRET`, acknowledges Slack URL verification, and calls `chat.unfurl` with a Block Kit `video` block using the existing `/embed/:id` player URL. For installable workspaces, use the Clips Settings OAuth flow. `connect-slack` opens Slack OAuth, `/api/slack/oauth/callback` stores the bot token encrypted in `app_secrets`, and `slack_installations` stores only the Slack team/app metadata plus the secret ref. The unfurl webhook resolves the token by Slack `team_id` and `api_app_id`; only if no OAuth install exists should it fall back to the legacy `SLACK_BOT_TOKEN` path. The playable Slack embed is deliberately narrower than the share page: - Only `ready` recordings with `visibility === "public"` can produce a video block. - Password-protected, expired, archived, trashed, private, org-only, or still-processing clips must not produce a playable Slack block. - Slack thumbnails use the same-origin proxy for stored thumbnails and the branded social image while one is unavailable. The proxy redirects to that image if stored content cannot be served; normal share-page metadata remains the fallback when no Slack app is installed. - Do not put passwords, short-lived share tokens, raw provider URLs, or transcript text in Slack unfurl payloads. Required Slack app setup: - Bot scopes: `links:read`, `links:write`, `links.embed:write` - Event subscription: `link_shared` - App unfurl domains: the public Clips share domain, for example `clips.agent-native.com` - Request URL: `https://<clips-host>/api/slack/unfurl` - OAuth redirect URL: `https://<clips-host>/api/slack/oauth/callback` - Deploy secrets: `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, and `SLACK_SIGNING_SECRET` ## Agent-readable clips When a user supplies a Clips link, read the clip through its published agent context instead of stopping at the share page or transcript preview: 1. Use the exact `agentContextUrl` in the page's agent-readable instructions or `application/agent-native+json` discovery payload. Preserve its `id` and any `agent_access` query parameter on every related request. 2. Fetch `apis.transcript` for the complete transcript. Fetch each `recommendedFrames[].url` as an image and inspect the returned pixels; when more visual context is needed, request `apis.frame.urlTemplate` with an `atMs` timestamp. 3. On an error, inspect the JSON `failureKind`, `error`, and `nextStep`. If a context or transcript request has `failureKind: access`, explain that the link does not grant access. For a private clip, ask its owner to open the Clips Share menu, choose **Share with agents**, and send the generated link. If a frame request has `failureKind: media` while the transcript works, report that the stored media could not be retrieved; another link will not fix it. If it has `failureKind: processing`, follow `nextStep`: extraction, size-limit, and temporary storage errors do not prove that media is missing or that the link lacks access. If `failureKind: expired`, ask the owner to extend or remove the clip expiry in the Share menu, save, then create and send a new **Share with agents** link. Never claim to have seen frames that were not returned as images. The JSON discovery payload and HTTP endpoints work without browser tools. A WebMCP-capable browser is optional; its frame tool returns a URL, so the agent must still fetch that URL as an image. Recordings can expose URLs meant for external agents without handing over raw video bytes: | Endpoint | Meaning | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `/api/agent-context.json?id=<recordingId>` | Clip metadata, transcript summary, recommended frames, and API discovery links | | `/api/agent-transcript.json?id=<recordingId>` | Timestamped transcript segments with `startMs`, `endMs`, `timestamp`, `range`, `text`, and optional `source`; add `maxSegments` to page and continue with the returned `nextStartIndex` | | `/api/agent-frame.jpg?id=<recordingId>&atMs=<ms>` | JPEG frame extracted from the video at the requested original-video timestamp | These endpoints follow the same access model as `/api/public-recording`, plus a temporary agent-link path: - Non-public clips return not found to anonymous callers. - `create-recording-agent-link` resolves normal recording access, rejects archived or trashed recordings, then mints a two-hour `agent_access` URL for `/share/:recordingId`. The share page SSR advertises the agent context URL, and the JSON endpoints accept the same scoped token. - Expired clips return expired. - Password-protected clips require `password=<pw>` once; successful JSON responses include short-lived tokenized links so the plaintext password is not copied into downstream agent prompts, browser history, or logs. - If a discovery, context, or transcript payload reports `agentReadiness.state` as `"preparing"` (the clip is `"uploading"` or `"processing"`), wait 15 seconds and retry `agentContextUrl`. Do not open the share page, fetch frames, or draw conclusions until the recording status is `"ready"`. - If the context or transcript response reports `transcript.status` as `"pending"`, wait 15-30 seconds and retry the context/transcript URL a few times before falling back to frames or telling the user no transcript exists. Long recordings are the common case here. - If transcription failed because Builder transcription credits are exhausted, tell the user to upgrade or connect Builder.io credits, or configure a Groq key for backup speech-to-text. Generic OpenAI or Anthropic chat keys do not transcribe Clips recordings. - Frame extraction must use the checked recording media path and must not expose raw provider URLs. Public agent context also exposes the recording's redacted browser diagnostics: the bounded relative event sequence as `browserDiagnostics.timeline`, including navigation, click/input targets, console events, and request/response markers; the console stream (all levels) as `browserDiagnostics.consoleLogs` and the fetch/XHR stream as `browserDiagnostics.networkRequests` (method, sanitized URL with query values redacted, status, duration), plus `consoleIssues` and `failedNetworkRequests` highlights. All of it is bounded, and page URL, headers, bodies, and cookies stay omitted. Password-protected clips require the password once to mint a short-lived token, which is returned inside the agent-context links. Use the `@agent-native/core/server` and `@agent-native/core/shared` agent-access helpers for scoped token mint/verify and bot-visible URL construction. Keep Clips-specific visibility, password, transcript, frame, and player behavior in Clips. New URLs should use `agent_access`; existing agent API routes should keep accepting legacy `t` tokens so already-copied links do not break. The share popover's "Share with agents" field should copy an agent context URL or tokenized share page URL, not raw transcript text. Its "Copy agent prompt" field may wrap that URL with instructions to fetch transcripts, frames, and browser diagnostics, but it should still point agents at the context response so they can fetch only the visual context they need. HTTP access is the default browser-independent path: fetch the agent context URL and then use its advertised transcript and frame URLs. It works for URL-only clients without loading the share page. When a `/share/:id`, `/embed/:id`, or public `/r/:id` page is open in a WebMCP-capable browser, the page also registers these read-only tools: | WebMCP tool | Purpose | | --- | --- | | `clips-get-context` | Clip metadata, readiness, transcript status, and HTTP API URLs | | `clips-get-transcript` | Bounded timestamped segments, with optional time bounds and stable-index pagination; may omit fullText | | `clips-get-frame` | An existing authenticated JPEG frame URL for `atMs` | For any client, fetch the agent context URL and use its HTTP API URLs. For complete transcript text, use `apis.transcript`. If the page is already open in a WebMCP-capable browser, list its current page tools and use them for bounded inspection; `clips-get-transcript` may omit `fullText` or return a truncated result, so follow its `sourceUrl` for the complete HTTP transcript. The transcript tool returns `nextStartIndex` when another page exists; pass that value back as `startIndex` so overlapping transcript segments are not skipped. The URL endpoint accepts the same `startIndex`, `maxSegments`, `startMs`, and `endMs` parameters, and keeps `nextStartMs` for older clients. The frame tool returns an image URL and `mimeType: image/jpeg`; fetch that URL as an image rather than expecting WebMCP to carry binary bytes. WebMCP is optional progressive enhancement and page-local. HTTP access remains first-class and browser-independent: use the existing `agentContextUrl`, `apis.transcript`, and `apis.frame` URLs above. These URLs, password handling, scoped `agent_access` tokens, and legacy `t` token support remain the primary URL contract. ## View counting Clips counts **human views** and **agent views** separately. The two live in different tables and never mix — see "Agent views" below before touching either. ### Human views A view counts when **any** of these is true: - The viewer has watched **≥ 5 seconds** of total real playback time - The viewer has hit **≥ 75% completion** - The viewer has scrubbed to the very end The canonical predicate is `shouldCountView(totalWatchMs, completedPct, scrubbedToEnd)` from `server/lib/recordings.ts`. Always go through it — do not recompute inline. ```ts import { shouldCountView } from "../server/lib/recordings.js"; if (
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub