@tandryukha/aidemo 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/package.json +1 -1
- package/src/browser.ts +76 -0
- package/src/distribute.ts +19 -1
- package/src/mcp/server.ts +11 -1
package/README.md
CHANGED
|
@@ -164,7 +164,9 @@ Voice/captions need `OPENAI_API_KEY` in `.env` — **or** `AIDEMO_TTS_PROVIDER=l
|
|
|
164
164
|
(no key, offline), **or** `OPENAI_BASE_URL` at a local server. See
|
|
165
165
|
[docs/LOCAL_MODELS.md](docs/LOCAL_MODELS.md). No Playwright browser download is
|
|
166
166
|
needed — the engine uses your system Chrome (`channel: "chrome"`; see
|
|
167
|
-
[Setup](#setup) to use a different browser).
|
|
167
|
+
[Setup](#setup) to use a different browser). The one Playwright download is
|
|
168
|
+
its ~2 MB ffmpeg helper, which records the take: `aidemo doctor` checks for it
|
|
169
|
+
and prints the exact install command (`npx playwright install ffmpeg`).
|
|
168
170
|
|
|
169
171
|
[](https://github.com/tandryukha/aidemo/releases/download/v0.3.0/quickstart-demo.mp4)
|
|
170
172
|
|
|
@@ -251,7 +253,9 @@ Codex).
|
|
|
251
253
|
|
|
252
254
|
## Setup
|
|
253
255
|
|
|
254
|
-
Prereqs: **Node 20+**, **Google Chrome**, **ffmpeg + ffprobe** on `PATH
|
|
256
|
+
Prereqs: **Node 20+**, **Google Chrome**, **ffmpeg + ffprobe** on `PATH`, and
|
|
257
|
+
Playwright's ~2 MB ffmpeg helper that records the take (one per Playwright
|
|
258
|
+
version; `aidemo doctor` prints the exact install command).
|
|
255
259
|
Developed and tested on macOS; Linux works for the default (Playwright) capture
|
|
256
260
|
and `--capture obs`. Run `aidemo doctor` to check your setup.
|
|
257
261
|
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -8,10 +8,72 @@
|
|
|
8
8
|
* the options are exactly the engine's historical `{ channel: "chrome", ... }`.
|
|
9
9
|
*/
|
|
10
10
|
import { chromium, type Browser, type BrowserContext, type LaunchOptions } from "playwright";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
11
15
|
import { BrowserSetupError, chromeLaunchOptions, type ChromeLaunchTarget } from "./config.js";
|
|
12
16
|
|
|
13
17
|
type PersistentOptions = NonNullable<Parameters<typeof chromium.launchPersistentContext>[1]>;
|
|
14
18
|
|
|
19
|
+
/** Executable name of Playwright's ffmpeg helper, per platform. */
|
|
20
|
+
const FFMPEG_HELPER_EXE: Partial<Record<NodeJS.Platform, string>> = {
|
|
21
|
+
linux: "ffmpeg-linux",
|
|
22
|
+
darwin: "ffmpeg-mac",
|
|
23
|
+
win32: "ffmpeg-win64.exe",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Playwright's own ffmpeg helper: the encoder recordVideo writes every take
|
|
28
|
+
* with. It is neither the system ffmpeg (compose) nor a browser — a ~2 MB
|
|
29
|
+
* download per Playwright ffmpeg *revision*, and no npm install fetches it
|
|
30
|
+
* (playwright has no install script). So a fresh npx install, or a Playwright
|
|
31
|
+
* minor that bumps the revision (1.64: 1011 → 1013, with VP9 encoding), leaves
|
|
32
|
+
* it missing and the take fails at launch. Mirrors playwright-core's
|
|
33
|
+
* documented lookup (PLAYWRIGHT_BROWSERS_PATH, else the per-OS
|
|
34
|
+
* ms-playwright cache dir). `install` is the exact command for THIS engine's
|
|
35
|
+
* Playwright. null when the location can't be determined.
|
|
36
|
+
*/
|
|
37
|
+
export function recorderFfmpeg(): { path: string; revision: string; install: string } | null {
|
|
38
|
+
try {
|
|
39
|
+
const exe = FFMPEG_HELPER_EXE[process.platform];
|
|
40
|
+
if (!exe) return null;
|
|
41
|
+
const req = createRequire(import.meta.url);
|
|
42
|
+
const playwrightPkg = req.resolve("playwright/package.json");
|
|
43
|
+
// playwright-core is playwright's dependency: resolve it from there.
|
|
44
|
+
const corePkg = createRequire(playwrightPkg).resolve("playwright-core/package.json");
|
|
45
|
+
const coreDir = dirname(corePkg);
|
|
46
|
+
const browsers = JSON.parse(readFileSync(join(coreDir, "browsers.json"), "utf8")) as {
|
|
47
|
+
browsers: Array<{ name: string; revision: string }>;
|
|
48
|
+
};
|
|
49
|
+
const revision = browsers.browsers.find((b) => b.name === "ffmpeg")?.revision;
|
|
50
|
+
if (!revision) return null;
|
|
51
|
+
const env = process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
52
|
+
let registry =
|
|
53
|
+
env === "0"
|
|
54
|
+
? join(coreDir, ".local-browsers")
|
|
55
|
+
: env || join(defaultPlaywrightCacheDir(), "ms-playwright");
|
|
56
|
+
if (!isAbsolute(registry)) registry = resolve(process.env.INIT_CWD || process.cwd(), registry);
|
|
57
|
+
const cli = join(dirname(playwrightPkg), "cli.js");
|
|
58
|
+
return {
|
|
59
|
+
path: join(registry, `ffmpeg-${revision}`, exe),
|
|
60
|
+
revision,
|
|
61
|
+
install: `node "${cli}" install ffmpeg`,
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function defaultPlaywrightCacheDir(): string {
|
|
69
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Caches");
|
|
70
|
+
if (process.platform === "win32") return process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
71
|
+
return process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A launch that failed on Playwright's missing ffmpeg helper, not on the browser. */
|
|
75
|
+
const FFMPEG_HELPER_RE = /playwright install ffmpeg|[\\/]ffmpeg-\d+[\\/]ffmpeg/i;
|
|
76
|
+
|
|
15
77
|
/** Human-readable description of the configured browser, for logs/doctor. */
|
|
16
78
|
export function describeChromeTarget(target: ChromeLaunchTarget = chromeLaunchOptions()): string {
|
|
17
79
|
if ("executablePath" in target) return `executable ${target.executablePath} (AIDEMO_CHROME_EXECUTABLE)`;
|
|
@@ -31,6 +93,20 @@ const MISSING_RE =
|
|
|
31
93
|
export function explainLaunchError(err: unknown, target: ChromeLaunchTarget): Error {
|
|
32
94
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
33
95
|
if (!MISSING_RE.test(e.message)) return e;
|
|
96
|
+
if (FFMPEG_HELPER_RE.test(e.message)) {
|
|
97
|
+
// The browser is fine — Playwright's recording helper is what's missing.
|
|
98
|
+
// Blaming Chrome here sent users to reinstall a browser that works.
|
|
99
|
+
const helper = recorderFfmpeg();
|
|
100
|
+
const out = new BrowserSetupError(
|
|
101
|
+
`Playwright's ffmpeg helper${helper ? ` (revision ${helper.revision})` : ""} is missing — ` +
|
|
102
|
+
`the ~2 MB encoder Playwright records the take with (not the system ffmpeg, not a ` +
|
|
103
|
+
`browser; no npm install fetches it). Install it once per Playwright version: ` +
|
|
104
|
+
`${helper?.install ?? "npx playwright install ffmpeg"}` +
|
|
105
|
+
`\nOriginal error: ${e.message}`
|
|
106
|
+
);
|
|
107
|
+
(out as Error & { cause?: unknown }).cause = e;
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
34
110
|
let hint: string;
|
|
35
111
|
if ("executablePath" in target) {
|
|
36
112
|
hint =
|
package/src/distribute.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
chromeLaunchOptions,
|
|
26
26
|
type ChromeLaunchTarget,
|
|
27
27
|
} from "./config.js";
|
|
28
|
-
import { describeChromeTarget, probeConfiguredBrowser } from "./browser.js";
|
|
28
|
+
import { describeChromeTarget, probeConfiguredBrowser, recorderFfmpeg } from "./browser.js";
|
|
29
29
|
import { localTtsStatus } from "./voice-local.js";
|
|
30
30
|
import { Project } from "./project.js";
|
|
31
31
|
import { STARTER_BRIEF, STARTER_STORYBOARD, briefFromInspect, storyboardFromInspect } from "./starter.js";
|
|
@@ -659,6 +659,12 @@ export interface DoctorReport {
|
|
|
659
659
|
};
|
|
660
660
|
};
|
|
661
661
|
playwright: boolean;
|
|
662
|
+
/**
|
|
663
|
+
* Playwright's ffmpeg helper for THIS engine's Playwright — the encoder the
|
|
664
|
+
* built-in capture records every take with (see recorderFfmpeg). null when
|
|
665
|
+
* its location can't be determined.
|
|
666
|
+
*/
|
|
667
|
+
recorderFfmpeg: { found: boolean; path: string; revision: string; install: string } | null;
|
|
662
668
|
skill: {
|
|
663
669
|
target: string;
|
|
664
670
|
installed: { version: string; stableAvailable: string | null } | null;
|
|
@@ -694,6 +700,7 @@ export async function doctorReport(dir: string | undefined): Promise<DoctorRepor
|
|
|
694
700
|
const localTts = ttsProvider() === "local" ? await localTtsStatus() : null;
|
|
695
701
|
const chrome = await probeChrome();
|
|
696
702
|
const browser = await probeBrowserTarget(chrome);
|
|
703
|
+
const helper = recorderFfmpeg();
|
|
697
704
|
const report: DoctorReport = {
|
|
698
705
|
engineVersion: latest,
|
|
699
706
|
node: process.version,
|
|
@@ -725,6 +732,7 @@ export async function doctorReport(dir: string | undefined): Promise<DoctorRepor
|
|
|
725
732
|
: {}),
|
|
726
733
|
},
|
|
727
734
|
playwright: playwrightOk,
|
|
735
|
+
recorderFfmpeg: helper ? { found: await exists(helper.path), ...helper } : null,
|
|
728
736
|
skill: {
|
|
729
737
|
target,
|
|
730
738
|
installed: manifest
|
|
@@ -741,6 +749,7 @@ export async function doctorReport(dir: string | undefined): Promise<DoctorRepor
|
|
|
741
749
|
report.ffmpeg &&
|
|
742
750
|
report.browser.found &&
|
|
743
751
|
report.playwright &&
|
|
752
|
+
(report.recorderFfmpeg?.found ?? true) &&
|
|
744
753
|
// Local TTS needs no key: voice is in-process, captions auto-fall-back
|
|
745
754
|
// to the offline derivation in `render`.
|
|
746
755
|
(report.apiKey !== "missing" || report.tts.provider === "local") &&
|
|
@@ -806,6 +815,15 @@ export async function doctor(dir: string | undefined): Promise<void> {
|
|
|
806
815
|
);
|
|
807
816
|
if (r.playwright) ok(`playwright: resolvable`);
|
|
808
817
|
else fail(`playwright: not installed`);
|
|
818
|
+
if (r.recorderFfmpeg?.found) {
|
|
819
|
+
ok(`playwright ffmpeg helper: revision ${r.recorderFfmpeg.revision} (records the take)`);
|
|
820
|
+
} else if (r.recorderFfmpeg) {
|
|
821
|
+
fail(
|
|
822
|
+
`playwright ffmpeg helper: revision ${r.recorderFfmpeg.revision} NOT FOUND at ` +
|
|
823
|
+
`${r.recorderFfmpeg.path} — every take fails without it (~2 MB, once per ` +
|
|
824
|
+
`Playwright version): ${r.recorderFfmpeg.install}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
809
827
|
|
|
810
828
|
if (r.skill.installed) {
|
|
811
829
|
ok(
|
package/src/mcp/server.ts
CHANGED
|
@@ -556,7 +556,8 @@ export function buildMcpServer(): { server: McpServer; jobs: JobManager } {
|
|
|
556
556
|
description:
|
|
557
557
|
"Check prereqs: node, ffmpeg, Chrome (or the AIDEMO_CHROME_CHANNEL / " +
|
|
558
558
|
"AIDEMO_CHROME_EXECUTABLE browser in use), TTS/STT endpoint (flags " +
|
|
559
|
-
"LLM-only servers like Ollama), API key, playwright
|
|
559
|
+
"LLM-only servers like Ollama), API key, playwright + its ffmpeg " +
|
|
560
|
+
"helper (records the take; missing = every take fails), installed skill.",
|
|
560
561
|
inputSchema: {
|
|
561
562
|
dir: z.string().optional().describe("repo to check for an installed skill"),
|
|
562
563
|
},
|
|
@@ -586,6 +587,15 @@ export function buildMcpServer(): { server: McpServer; jobs: JobManager } {
|
|
|
586
587
|
elevenLabsKey: z.enum(["set", "missing"]).optional(),
|
|
587
588
|
}),
|
|
588
589
|
playwright: z.boolean(),
|
|
590
|
+
recorderFfmpeg: z
|
|
591
|
+
.object({
|
|
592
|
+
found: z.boolean(),
|
|
593
|
+
path: z.string(),
|
|
594
|
+
revision: z.string(),
|
|
595
|
+
install: z.string().describe("exact command to install it"),
|
|
596
|
+
})
|
|
597
|
+
.nullable()
|
|
598
|
+
.describe("Playwright's ffmpeg helper the built-in capture records with"),
|
|
589
599
|
skill: z.object({
|
|
590
600
|
target: z.string(),
|
|
591
601
|
installed: z
|