agents/openai.yaml
interface: display_name: 'Vercel Monitor' short_description: 'Monitor Vercel previews until ready' default_prompt: 'Use $vercel to monitor the remotion Vercel deployment and tell me when the preview is ready.'
remotion-dev/remotion · GitHub
Set up a Codex monitor for Vercel deployments and preview URLs. Use when the user invokes /vercel or $vercel, asks Codex to watch or monitor a Vercel deployment, waits for a Vercel preview or PR preview to become ready, or wants to be notified with both the deployment URL and preview URL once Vercel is READY.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add remotion-dev/remotion --skill vercel설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
agents/openai.yamlinterface: display_name: 'Vercel Monitor' short_description: 'Monitor Vercel previews until ready' default_prompt: 'Use $vercel to monitor the remotion Vercel deployment and tell me when the preview is ready.'
scripts/check-deployment.py#!/usr/bin/env python3
"""Return the authoritative state of one immutable Vercel deployment."""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from urllib.parse import urlparse
IN_PROGRESS_STATES = {"BUILDING", "QUEUED", "INITIALIZING"}
FAILURE_STATES = {"ERROR", "CANCELED", "CANCELLED"}
def emit(payload: dict[str, object], exit_code: int = 0) -> int:
print(json.dumps(payload, indent=2, sort_keys=True))
return exit_code
def normalize_reference(reference: str, scope: str, project: str) -> tuple[str, str | None]:
parsed = urlparse(reference if "://" in reference else f"https://{reference}")
if parsed.hostname == "vercel.com" or parsed.hostname == "www.vercel.com":
parts = [part for part in parsed.path.split("/") if part]
if len(parts) != 3:
raise ValueError(
"Expected a Vercel deployment dashboard URL with /<scope>/<project>/<deployment>."
)
url_scope, url_project, suffix = parts
if url_scope != scope or url_project != project:
raise ValueError(
f"Dashboard URL is for {url_scope}/{url_project}, expected {scope}/{project}."
)
deployment_id = suffix if suffix.startswith("dpl_") else f"dpl_{suffix}"
return deployment_id, reference
if reference.startswith("dpl_"):
return reference, None
if parsed.hostname and parsed.hostname.endswith(".vercel.app"):
return parsed.hostname, None
raise ValueError("Expected a dpl_ deployment ID, dashboard URL, or vercel.app URL.")
def main() -> int:
parser = argparse.ArgumentParser(
description="Read Vercel readyState for one pinned deployment.",
)
parser.add_argument("deployment", help="Deployment ID, dashboard URL, or immutable URL")
parser.add_argument("--scope", default="remotion", help="Vercel team scope")
parser.add_argument("--project", default="remotion", help="Expected Vercel project")
args = parser.parse_args()
try:
reference, supplied_dashboard_url = normalize_reference(
args.deployment,
args.scope,
args.project,
)
except ValueError as error:
return emit({"state": "UNKNOWN", "error": str(error)}, 2)
result = subprocess.run(
[
"vercel",
"inspect",
reference,
"--scope",
args.scope,
"--format=json",
],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return emit(
{
"state": "UNKNOWN",
"error": "vercel inspect failed",
"diagnostic": result.stderr.strip() or result.stdout.strip(),
},
2,
)
try:
deployment = json.loads(result.stdout)
except json.JSONDecodeError as error:
return emit(
{
"state": "UNKNOWN",
"error": f"vercel inspect returned invalid JSON: {error}",
},
2,
)
name = deployment.get("name")
context_name = deployment.get("contextName")
if name != args.project or (context_name is not None and context_name != args.scope):
return emit(
{
"state": "UNKNOWN",
"error": "Deployment belongs to a different Vercel project or scope.",
"actual_project": name,
"actual_scope": context_name,
},
2,
)
canonical_hostname = deployment.get("url")
parsed_input = urlparse(
args.deployment if "://" in args.deployment else f"https://{args.deployment}"
)
if (
parsed_input.hostname
and parsed_input.hostname.endswith(".vercel.app")
and parsed_input.hostname != canonical_hostname
):
return emit(
{
"state": "UNKNOWN",
"error": "Refusing to monitor a moving Vercel alias.",
"alias": parsed_input.hostname,
"currently_resolves_to": canonical_hostname,
"deployment_id": deployment.get("id"),
},
2,
)
state = str(deployment.get("readyState") or "UNKNOWN").upper()
if state == "READY":
terminal = True
outcome = "success"
elif state in FAILURE_STATES:
terminal = True
outcome = "failure"
elif state in IN_PROGRESS_STATES:
terminal = False
outcome = "in_progress"
else:
terminal = False
outcome = "unknown"
deployment_id = deployment.get("id")
dashboard_url = supplied_dashboard_url
if dashboard_url is None and isinstance(deployment_id, str):
dashboard_url = (
f"https://vercel.com/{args.scope}/{args.project}/"
f"{deployment_id.removeprefix('dpl_')}"
)
return emit(
{
"state": state,
"terminal": terminal,
"outcome": outcome,
"deployment_id": deployment_id,
"deployment_url": (
f"https://{canonical_hostname}" if canonical_hostname else None
),
"dashboard_url": dashboard_url,
"preview_aliases": [f"https://{alias}" for alias in deployment.get("aliases", [])],
"project": name,
"scope": context_name,
"created_at": deployment.get("createdAt"),
}
)
if __name__ == "__main__":
raise SystemExit(main())
SKILL.md--- name: vercel description: Set up a Codex monitor for Vercel deployments and preview URLs. Use when the user invokes /vercel or $vercel, asks Codex to watch or monitor a Vercel deployment, waits for a Vercel preview or PR preview to become ready, or wants to be notified with both the deployment URL and preview URL once Vercel is READY. --- # Vercel Monitor Monitor one immutable Vercel deployment for the `remotion` project and notify the current task when that exact deployment becomes ready or fails. ## Non-negotiable rules - Never infer deployment state from an HTTP response. A branch preview alias can return 200 from the previous deployment while the new deployment is building. - Never monitor a branch preview alias. It is movable and can resolve to an older or newer deployment. - Pin the monitor to a Vercel deployment ID (`dpl_...`) or the immutable automatic deployment hostname (`<project>-<random>-<scope>.vercel.app`). - Read the machine-readable Vercel deployment state. Do not parse human CLI output. - Default to the `remotion` project and the `remotion` Vercel scope. Ignore the `bugs` project unless the user explicitly asks for it. ## Resolve the exact deployment Prefer these sources, in order: 1. A Vercel deployment/dashboard URL supplied by the user. 2. The `Vercel – remotion` check on the active GitHub PR. Its dashboard URL has the form `https://vercel.com/remotion/remotion/<deployment-id-suffix>`. 3. The `remotion` row in the Vercel bot's PR comment. 4. `vercel list remotion --scope remotion --format=json`, matched to the exact PR, commit SHA, or branch in `.deployments[].meta`. The final path segment of a dashboard URL becomes the deployment ID by prefixing it with `dpl_`. For example: ```text https://vercel.com/remotion/remotion/AbCd1234 -> dpl_AbCd1234 ``` The `Preview` link in a Vercel PR comment is normally a branch alias. Preserve it for the final notification, but do not use it for state checks. When using `vercel list`, select the newest deployment that matches all available identity fields: - `.name == "remotion"` - `.meta.githubPrId == <PR number>`, when a PR is known - `.meta.githubCommitSha == <full commit SHA>`, when a commit is known Do not silently fall back to a different commit. If no exact deployment can be identified, ask for the Vercel dashboard URL, PR number, or commit SHA. ## Read state Run the bundled checker from the repository root: ```bash python3 .agents/skills/vercel/scripts/check-deployment.py <deployment-id-or-url> ``` The checker calls: ```bash vercel inspect <deployment-id-or-immutable-url> \ --scope remotion \ --format=json ``` It validates the project, rejects moving aliases, and emits normalized JSON. Use its `state` field: - `READY`: success. - `ERROR`, `CANCELED`, or `CANCELLED`: failure. - `BUILDING`, `QUEUED`, or `INITIALIZING`: still in progress. - `UNKNOWN`: not terminal. Report the diagnostic only if it persists or prevents creation of a trustworthy monitor. HTTP probing may be used after `READY` as an optional reachability check. It must never promote a non-ready or unknown deployment to `READY`. ## Create the monitor Use the Codex automation tool to create a one-minute heartbeat with a bounded count, normally 30 attempts. The heartbeat prompt must be self-contained and include: - The pinned deployment ID or immutable deployment hostname. - The dashboard URL. - The branch preview alias, if known, for the final notification only. - The project, PR, branch, and commit context, if known. - The exact checker command. - The terminal-state rules above. Use this prompt shape: ```text Monitor this exact Vercel deployment until it reaches a terminal state. Pinned deployment: <dpl_id_or_immutable_hostname> Dashboard: <dashboard_url> Preview alias (reporting only; never use for state): <preview_url_or_unknown> Context: <project/pr/branch/commit> From the repository root, run: python3 .agents/skills/vercel/scripts/check-deployment.py <pinned_deployment> Only the JSON `state` is authoritative. - READY: reply "Vercel deployment is ready" and include Dashboard and Preview. - ERROR, CANCELED, or CANCELLED: reply with the failure state and include both links. - BUILDING, QUEUED, INITIALIZING, or UNKNOWN: stay quiet and check again next time. Never curl the preview URL to determine readiness. After reporting a terminal state, delete or pause this heartbeat if its automation ID is available. ``` Before creating a heartbeat, run the checker once. If the deployment is already terminal, report immediately instead. Otherwise, tell the user which exact deployment is being watched and the cadence.