← Files Codex Browser RecorderARCHIVED FILE
skills/record-browser/scripts/doctor.mjs
5.9 KB · Sep 30, 2026 · 23:13 UTC
import { execFile } from "node:child_process";
import { constants } from "node:fs";
import { access, stat } from "node:fs/promises";
import { platform as hostPlatform } from "node:os";
import { delimiter, dirname, join } from "node:path";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
async function commandMatches(executable, arguments_, pattern) {
try {
const { stdout } = await execFileAsync(executable, arguments_, {
encoding: "utf8",
maxBuffer: 1024 * 1024,
timeout: 5000,
windowsHide: true,
});
return pattern.test(stdout);
} catch {
return false;
}
}
async function ffprobeSupportsJson(executable) {
try {
const { stdout } = await execFileAsync(
executable,
["-v", "error", "-show_program_version", "-of", "json"],
{
encoding: "utf8",
maxBuffer: 1024 * 1024,
timeout: 5000,
windowsHide: true,
},
);
const parsed = JSON.parse(stdout);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed) &&
parsed.program_version !== null &&
typeof parsed.program_version === "object" &&
!Array.isArray(parsed.program_version) &&
typeof parsed.program_version.version === "string" &&
parsed.program_version.version.length > 0
);
} catch {
return false;
}
}
async function inspectMediaCapabilities(ffmpegPath, ffprobePath) {
const [ffmpegH264Available, ffmpegMp4Available, ffprobeUsable] =
await Promise.all([
ffmpegPath === null
? false
: commandMatches(
ffmpegPath,
["-hide_banner", "-loglevel", "error", "-h", "encoder=libx264"],
/^Encoder libx264 \[/m,
),
ffmpegPath === null
? false
: commandMatches(
ffmpegPath,
["-hide_banner", "-loglevel", "error", "-h", "muxer=mp4"],
/^Muxer mp4 \[/m,
),
ffprobePath === null
? false
: ffprobeSupportsJson(ffprobePath),
]);
return { ffmpegH264Available, ffmpegMp4Available, ffprobeUsable };
}
async function resolveExecutableFromInheritedPath(name) {
try {
await execFileAsync(name, ["-version"], {
encoding: "utf8",
maxBuffer: 1024 * 1024,
timeout: 5000,
windowsHide: true,
});
return name;
} catch {
return null;
}
}
async function findExecutable(name, pathValue, resolveExecutableByName) {
if (typeof pathValue !== "string") {
try {
const resolved = await resolveExecutableByName(name);
return typeof resolved === "string" && resolved.length > 0
? resolved
: null;
} catch {
return null;
}
}
for (const directory of pathValue.split(delimiter).filter(Boolean)) {
const candidate = join(directory, name);
try {
const candidateStat = await stat(candidate);
if (candidateStat.isFile()) {
await access(candidate, constants.X_OK);
return candidate;
}
} catch {
// Continue through the configured search path.
}
}
return null;
}
async function outputDirectoryIsWritable(outputDirectory, { allowPlanned }) {
let candidate = outputDirectory;
while (typeof candidate === "string" && candidate.length > 0) {
try {
const candidateStat = await stat(candidate);
if (!candidateStat.isDirectory()) return false;
await access(candidate, constants.W_OK);
return true;
} catch (error) {
if (!allowPlanned || error?.code !== "ENOENT") return false;
const parent = dirname(candidate);
if (parent === candidate) return false;
candidate = parent;
}
}
return false;
}
async function inspectEnvironment({
allowPlannedOutputDirectory,
cdpAvailable,
includeCdp,
outputDirectory,
pathValue,
platform = hostPlatform(),
probeMediaCapabilities = inspectMediaCapabilities,
resolveExecutableByName = resolveExecutableFromInheritedPath,
}) {
const [ffmpegPath, ffprobePath] = await Promise.all([
findExecutable("ffmpeg", pathValue, resolveExecutableByName),
findExecutable("ffprobe", pathValue, resolveExecutableByName),
]);
const capabilities = await probeMediaCapabilities(ffmpegPath, ffprobePath);
const ffmpegH264Available =
ffmpegPath !== null && capabilities.ffmpegH264Available === true;
const ffmpegMp4Available =
ffmpegPath !== null && capabilities.ffmpegMp4Available === true;
const ffprobeUsable =
ffprobePath !== null && capabilities.ffprobeUsable === true;
const outputDirectoryWritable = await outputDirectoryIsWritable(
outputDirectory,
{ allowPlanned: allowPlannedOutputDirectory },
);
const blockingReasons = [];
if (platform !== "darwin") {
blockingReasons.push("unsupported_platform");
}
if (includeCdp && !cdpAvailable) {
blockingReasons.push("cdp_unavailable");
}
if (ffmpegPath === null) {
blockingReasons.push("ffmpeg_missing");
} else {
if (!ffmpegH264Available) {
blockingReasons.push("ffmpeg_h264_unavailable");
}
if (!ffmpegMp4Available) {
blockingReasons.push("ffmpeg_mp4_unavailable");
}
}
if (ffprobePath === null) {
blockingReasons.push("ffprobe_missing");
} else if (!ffprobeUsable) {
blockingReasons.push("ffprobe_unusable");
}
if (!outputDirectoryWritable) {
blockingReasons.push("output_directory_not_writable");
}
return {
blockingReasons,
...(includeCdp ? { cdpAvailable } : {}),
ffmpegPath,
ffmpegH264Available,
ffmpegMp4Available,
ffprobePath,
ffprobeUsable,
outputDirectoryWritable,
platform,
supported: blockingReasons.length === 0,
};
}
export function inspectLocalRecordingEnvironment(options) {
return inspectEnvironment({
...options,
allowPlannedOutputDirectory: true,
includeCdp: false,
});
}
export function doctor(options) {
return inspectEnvironment({
...options,
allowPlannedOutputDirectory: false,
includeCdp: true,
});
}
SHA-256: fd859f8a96a7bf7f891815a6ef387cb767d53c0ada00ee7e0c0dfb0db063dffd