@agentproto/corpus-cli 0.2.0 → 0.3.0
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/dist/cli.mjs
CHANGED
|
@@ -404,8 +404,8 @@ function resolveWorkspacePath(arg) {
|
|
|
404
404
|
if (!arg) return process.cwd();
|
|
405
405
|
return path.resolve(process.cwd(), arg);
|
|
406
406
|
}
|
|
407
|
-
function fail(
|
|
408
|
-
process.stderr.write(`corpus: ${
|
|
407
|
+
function fail(msg5, code = 1) {
|
|
408
|
+
process.stderr.write(`corpus: ${msg5}
|
|
409
409
|
`);
|
|
410
410
|
return code;
|
|
411
411
|
}
|
|
@@ -1040,6 +1040,146 @@ function defaultYtDlpDownloader(bin, opts) {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
};
|
|
1042
1042
|
}
|
|
1043
|
+
var execFileAsync2 = promisify(execFile);
|
|
1044
|
+
var YTDLP_INFO2 = z.object({ title: z.string().optional(), language: z.string().optional() }).loose();
|
|
1045
|
+
var YtDlpCaptionsFetcher = class {
|
|
1046
|
+
download;
|
|
1047
|
+
extraHosts;
|
|
1048
|
+
minChars;
|
|
1049
|
+
constructor(opts = {}) {
|
|
1050
|
+
this.minChars = opts.minChars ?? 400;
|
|
1051
|
+
this.download = opts.download ?? defaultYtDlpCaptionDownloader(opts.ytDlpBin ?? "yt-dlp", {
|
|
1052
|
+
...opts.subLangs ? { subLangs: opts.subLangs } : {},
|
|
1053
|
+
...opts.preferLang ? { preferLang: opts.preferLang } : {},
|
|
1054
|
+
...opts.maxDurationSec ? { maxDurationSec: opts.maxDurationSec } : {},
|
|
1055
|
+
...opts.cookiesFromBrowser ? { cookiesFromBrowser: opts.cookiesFromBrowser } : {},
|
|
1056
|
+
...opts.cookiesFile ? { cookiesFile: opts.cookiesFile } : {}
|
|
1057
|
+
});
|
|
1058
|
+
this.extraHosts = opts.extraVideoHosts ? new Set(opts.extraVideoHosts) : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
async fetch(url) {
|
|
1061
|
+
if (!isVideoUrl(url, this.extraHosts)) return null;
|
|
1062
|
+
let dl;
|
|
1063
|
+
try {
|
|
1064
|
+
dl = await this.download(url);
|
|
1065
|
+
} catch (e) {
|
|
1066
|
+
process.stderr.write(
|
|
1067
|
+
`corpus: caption fetch failed for ${url} \u2014 falling back (${msg2(e)})
|
|
1068
|
+
`
|
|
1069
|
+
);
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
if (!dl.vttPath) return null;
|
|
1074
|
+
const text = parseVttToText(await readFile(dl.vttPath, "utf-8"));
|
|
1075
|
+
if (text.length < this.minChars) return null;
|
|
1076
|
+
return {
|
|
1077
|
+
title: dl.title || url,
|
|
1078
|
+
text,
|
|
1079
|
+
kind: "video",
|
|
1080
|
+
...dl.language ? { language: dl.language } : {},
|
|
1081
|
+
via: "captions"
|
|
1082
|
+
};
|
|
1083
|
+
} catch {
|
|
1084
|
+
return null;
|
|
1085
|
+
} finally {
|
|
1086
|
+
await dl.cleanup().catch(() => {
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
function msg2(e) {
|
|
1092
|
+
return e instanceof Error ? e.message : String(e);
|
|
1093
|
+
}
|
|
1094
|
+
function parseVttToText(vtt) {
|
|
1095
|
+
const out = [];
|
|
1096
|
+
for (const raw of vtt.split(/\r?\n/)) {
|
|
1097
|
+
if (/^WEBVTT/.test(raw)) continue;
|
|
1098
|
+
if (/^(Kind|Language|NOTE|STYLE):/i.test(raw)) continue;
|
|
1099
|
+
if (raw.includes("-->")) continue;
|
|
1100
|
+
const line = raw.replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/ /g, " ").trim();
|
|
1101
|
+
if (!line) continue;
|
|
1102
|
+
if (out.length && out[out.length - 1] === line) continue;
|
|
1103
|
+
out.push(line);
|
|
1104
|
+
}
|
|
1105
|
+
const cleaned = [];
|
|
1106
|
+
for (let i = 0; i < out.length; i++) {
|
|
1107
|
+
const next = out[i + 1];
|
|
1108
|
+
if (next && next.startsWith(out[i])) continue;
|
|
1109
|
+
cleaned.push(out[i]);
|
|
1110
|
+
}
|
|
1111
|
+
return cleaned.join(" ").replace(/\s+/g, " ").trim();
|
|
1112
|
+
}
|
|
1113
|
+
function buildYtDlpCaptionArgs(url, dir, opts) {
|
|
1114
|
+
const subLangs = opts?.subLangs ?? (opts?.preferLang ? `${opts.preferLang}.*` : "en.*,fr.*,es.*,de.*,it.*,pt.*,nl.*");
|
|
1115
|
+
const durationGuard = opts?.maxDurationSec && opts.maxDurationSec > 0 ? ["--match-filter", `duration <= ${Math.floor(opts.maxDurationSec)}`] : [];
|
|
1116
|
+
const cookieArgs = opts?.cookiesFromBrowser ? ["--cookies-from-browser", opts.cookiesFromBrowser] : opts?.cookiesFile ? ["--cookies", opts.cookiesFile] : [];
|
|
1117
|
+
return [
|
|
1118
|
+
"--skip-download",
|
|
1119
|
+
"--write-auto-subs",
|
|
1120
|
+
"--write-subs",
|
|
1121
|
+
"--sub-langs",
|
|
1122
|
+
subLangs,
|
|
1123
|
+
"--sub-format",
|
|
1124
|
+
"vtt",
|
|
1125
|
+
"--no-playlist",
|
|
1126
|
+
"--no-progress",
|
|
1127
|
+
"--sleep-requests",
|
|
1128
|
+
"1",
|
|
1129
|
+
...durationGuard,
|
|
1130
|
+
...cookieArgs,
|
|
1131
|
+
"--write-info-json",
|
|
1132
|
+
"-o",
|
|
1133
|
+
join(dir, "%(id)s.%(ext)s"),
|
|
1134
|
+
url
|
|
1135
|
+
];
|
|
1136
|
+
}
|
|
1137
|
+
function pickVtt(files, preferLang) {
|
|
1138
|
+
const vtts = files.filter((f) => f.endsWith(".vtt"));
|
|
1139
|
+
if (vtts.length === 0) return void 0;
|
|
1140
|
+
const bySuffix = (suffix) => vtts.find((f) => f.toLowerCase().endsWith(suffix.toLowerCase()));
|
|
1141
|
+
if (preferLang) {
|
|
1142
|
+
return bySuffix(`.${preferLang}-orig.vtt`) ?? bySuffix(`.${preferLang}.vtt`) ?? vtts.find((f) => new RegExp(`\\.${preferLang}[.-]`, "i").test(f)) ?? vtts.find((f) => /-orig\.vtt$/i.test(f)) ?? vtts[0];
|
|
1143
|
+
}
|
|
1144
|
+
return vtts.find((f) => /-orig\.vtt$/i.test(f)) ?? vtts[0];
|
|
1145
|
+
}
|
|
1146
|
+
function defaultYtDlpCaptionDownloader(bin, opts) {
|
|
1147
|
+
return async (url) => {
|
|
1148
|
+
const dir = await mkdtemp(join(tmpdir(), "corpus-captions-"));
|
|
1149
|
+
const cleanup = () => rm(dir, { recursive: true, force: true });
|
|
1150
|
+
try {
|
|
1151
|
+
await execFileAsync2(bin, buildYtDlpCaptionArgs(url, dir, opts), {
|
|
1152
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1153
|
+
}).catch(() => {
|
|
1154
|
+
});
|
|
1155
|
+
const files = await readdir(dir);
|
|
1156
|
+
const vtt = pickVtt(files, opts?.preferLang);
|
|
1157
|
+
let title = url;
|
|
1158
|
+
let language;
|
|
1159
|
+
const infoName = files.find((f) => f.endsWith(".info.json"));
|
|
1160
|
+
if (infoName) {
|
|
1161
|
+
try {
|
|
1162
|
+
const info = YTDLP_INFO2.parse(
|
|
1163
|
+
JSON.parse(await readFile(join(dir, infoName), "utf-8"))
|
|
1164
|
+
);
|
|
1165
|
+
if (info.title) title = info.title;
|
|
1166
|
+
if (info.language) language = info.language;
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
vttPath: vtt ? join(dir, vtt) : null,
|
|
1172
|
+
title,
|
|
1173
|
+
...language ? { language } : {},
|
|
1174
|
+
cleanup
|
|
1175
|
+
};
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
await cleanup().catch(() => {
|
|
1178
|
+
});
|
|
1179
|
+
throw e;
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1043
1183
|
var WHISPER_RESPONSE = z.object({ text: z.string().optional(), language: z.string().optional() }).loose();
|
|
1044
1184
|
var WHISPER_MAX_BYTES = 25 * 1024 * 1024;
|
|
1045
1185
|
var OpenAiWhisperStt = class {
|
|
@@ -1178,7 +1318,7 @@ function formatDiarized(job) {
|
|
|
1178
1318
|
}
|
|
1179
1319
|
return (job.text ?? "").trim();
|
|
1180
1320
|
}
|
|
1181
|
-
var
|
|
1321
|
+
var execFileAsync3 = promisify(execFile);
|
|
1182
1322
|
var DEFAULT_MAX_BYTES = 24 * 1024 * 1024;
|
|
1183
1323
|
var DEFAULT_SEGMENT_SECONDS = 1200;
|
|
1184
1324
|
var ChunkedStt = class {
|
|
@@ -1237,7 +1377,7 @@ var ChunkedStt = class {
|
|
|
1237
1377
|
function defaultFfmpegSplitter(bin) {
|
|
1238
1378
|
return async (audioPath, segmentSeconds, outDir) => {
|
|
1239
1379
|
const ext = extname(audioPath) || ".mp3";
|
|
1240
|
-
await
|
|
1380
|
+
await execFileAsync3(
|
|
1241
1381
|
bin,
|
|
1242
1382
|
[
|
|
1243
1383
|
"-hide_banner",
|
|
@@ -1468,6 +1608,7 @@ function parse(args) {
|
|
|
1468
1608
|
throttleMs: 2e3,
|
|
1469
1609
|
force: false,
|
|
1470
1610
|
diarize: false,
|
|
1611
|
+
noCaptions: false,
|
|
1471
1612
|
browserMcp: process.env.BROWSER_MCP_URL,
|
|
1472
1613
|
scrapeMcp: process.env.SCRAPE_MCP_URL,
|
|
1473
1614
|
importerId: "web",
|
|
@@ -1523,6 +1664,9 @@ function parse(args) {
|
|
|
1523
1664
|
case "--diarize":
|
|
1524
1665
|
out.diarize = true;
|
|
1525
1666
|
break;
|
|
1667
|
+
case "--no-captions":
|
|
1668
|
+
out.noCaptions = true;
|
|
1669
|
+
break;
|
|
1526
1670
|
case "--browser-mcp":
|
|
1527
1671
|
out.browserMcp = next();
|
|
1528
1672
|
break;
|
|
@@ -1589,10 +1733,11 @@ async function runImportWeb(args) {
|
|
|
1589
1733
|
const todo = urls.filter((u) => !done.has(u));
|
|
1590
1734
|
const batch = parsed.max !== void 0 ? todo.slice(0, parsed.max) : todo;
|
|
1591
1735
|
const remaining = todo.length - batch.length;
|
|
1736
|
+
const videoLabel = parsed.noCaptions ? parsed.diarize ? "AssemblyAI" : "Whisper" : `captions-first \u2192 ${parsed.diarize ? "AssemblyAI" : "Whisper"} fallback`;
|
|
1592
1737
|
const plan = ` workspace: ${target}
|
|
1593
1738
|
urls: ${urls.length} total \xB7 ${done.size && !parsed.force ? `${urls.length - todo.length} already ingested \xB7 ` : ""}${todo.length} to do
|
|
1594
1739
|
this run: ${batch.length}${parsed.max !== void 0 ? ` (--max ${parsed.max})` : ""} \xB7 ${remaining} remaining after
|
|
1595
|
-
video
|
|
1740
|
+
video: ${videoLabel}
|
|
1596
1741
|
throttle: ${parsed.throttleMs} ms between fetches
|
|
1597
1742
|
`;
|
|
1598
1743
|
if (batch.length === 0) {
|
|
@@ -1613,6 +1758,16 @@ ${plan}`);
|
|
|
1613
1758
|
process.stdout.write(`import-web
|
|
1614
1759
|
${plan}`);
|
|
1615
1760
|
const chain = [];
|
|
1761
|
+
if (!parsed.noCaptions) {
|
|
1762
|
+
chain.push(
|
|
1763
|
+
new YtDlpCaptionsFetcher({
|
|
1764
|
+
...parsed.lang ? { preferLang: parsed.lang } : {},
|
|
1765
|
+
...parsed.maxDurationSec ? { maxDurationSec: parsed.maxDurationSec } : {},
|
|
1766
|
+
...parsed.cookiesFromBrowser ? { cookiesFromBrowser: parsed.cookiesFromBrowser } : {},
|
|
1767
|
+
...parsed.cookiesFile ? { cookiesFile: parsed.cookiesFile } : {}
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1616
1771
|
let stt;
|
|
1617
1772
|
if (parsed.diarize) {
|
|
1618
1773
|
const aaiKey = process.env.ASSEMBLYAI_API_KEY;
|
|
@@ -1627,7 +1782,7 @@ ${plan}`);
|
|
|
1627
1782
|
stt = new ChunkedStt({ base: new OpenAiWhisperStt({ apiKey: openaiKey }) });
|
|
1628
1783
|
else
|
|
1629
1784
|
process.stderr.write(
|
|
1630
|
-
"corpus: OPENAI_API_KEY not set \u2014 video URLs will be skipped (no transcription).\n"
|
|
1785
|
+
parsed.noCaptions ? "corpus: OPENAI_API_KEY not set \u2014 video URLs will be skipped (no transcription).\n" : "corpus: OPENAI_API_KEY not set \u2014 caption-less videos will be skipped (captioned videos still import).\n"
|
|
1631
1786
|
);
|
|
1632
1787
|
}
|
|
1633
1788
|
if (stt)
|
|
@@ -1645,7 +1800,7 @@ ${plan}`);
|
|
|
1645
1800
|
const client = await connectBrowserMcp({ endpoint: parsed.scrapeMcp });
|
|
1646
1801
|
chain.push(new ScrapeMcpFetcher({ client }));
|
|
1647
1802
|
} catch (e) {
|
|
1648
|
-
return fail(`could not connect to scrape MCP at ${parsed.scrapeMcp}: ${
|
|
1803
|
+
return fail(`could not connect to scrape MCP at ${parsed.scrapeMcp}: ${msg3(e)}`, 1);
|
|
1649
1804
|
}
|
|
1650
1805
|
}
|
|
1651
1806
|
if (parsed.browserMcp) {
|
|
@@ -1654,7 +1809,7 @@ ${plan}`);
|
|
|
1654
1809
|
browser = await connectBrowserMcp({ endpoint: parsed.browserMcp });
|
|
1655
1810
|
chain.push(new BrowserMcpFetcher({ browser }));
|
|
1656
1811
|
} catch (e) {
|
|
1657
|
-
return fail(`could not connect to browser MCP at ${parsed.browserMcp}: ${
|
|
1812
|
+
return fail(`could not connect to browser MCP at ${parsed.browserMcp}: ${msg3(e)}`, 1);
|
|
1658
1813
|
}
|
|
1659
1814
|
}
|
|
1660
1815
|
chain.push(new HttpReadabilityFetcher());
|
|
@@ -1685,7 +1840,7 @@ ${plan}`);
|
|
|
1685
1840
|
}
|
|
1686
1841
|
});
|
|
1687
1842
|
} catch (e) {
|
|
1688
|
-
return fail(`import failed: ${
|
|
1843
|
+
return fail(`import failed: ${msg3(e)}`, 1);
|
|
1689
1844
|
}
|
|
1690
1845
|
process.stdout.write(
|
|
1691
1846
|
`import-web \u2192 ${target}
|
|
@@ -1705,7 +1860,7 @@ knowledge engine (RAG) via the WriterPort. Import only stages the sources.
|
|
|
1705
1860
|
);
|
|
1706
1861
|
return 0;
|
|
1707
1862
|
}
|
|
1708
|
-
function
|
|
1863
|
+
function msg3(e) {
|
|
1709
1864
|
return e instanceof Error ? e.message : String(e);
|
|
1710
1865
|
}
|
|
1711
1866
|
function parse2(args) {
|
|
@@ -2786,10 +2941,10 @@ var SseMcpClient = class {
|
|
|
2786
2941
|
try {
|
|
2787
2942
|
const parsed = RPC_MESSAGE.safeParse(JSON.parse(payload));
|
|
2788
2943
|
if (!parsed.success) return;
|
|
2789
|
-
const
|
|
2790
|
-
if (typeof
|
|
2791
|
-
this.pending.get(
|
|
2792
|
-
this.pending.delete(
|
|
2944
|
+
const msg5 = parsed.data;
|
|
2945
|
+
if (typeof msg5.id === "number" && this.pending.has(msg5.id)) {
|
|
2946
|
+
this.pending.get(msg5.id)(msg5);
|
|
2947
|
+
this.pending.delete(msg5.id);
|
|
2793
2948
|
}
|
|
2794
2949
|
} catch {
|
|
2795
2950
|
}
|
|
@@ -2858,7 +3013,7 @@ var McpSink = class {
|
|
|
2858
3013
|
try {
|
|
2859
3014
|
client = await this.ensure();
|
|
2860
3015
|
} catch (e) {
|
|
2861
|
-
return { uri: item.uri, ok: false, error: `connect: ${
|
|
3016
|
+
return { uri: item.uri, ok: false, error: `connect: ${msg4(e)}` };
|
|
2862
3017
|
}
|
|
2863
3018
|
const args = template(this.config.args, item);
|
|
2864
3019
|
const call = this.config.importedAlias ? { name: "mcp_imported_call", args: { alias: this.config.importedAlias, toolName: this.config.tool, args } } : { name: this.config.tool, args };
|
|
@@ -2867,7 +3022,7 @@ var McpSink = class {
|
|
|
2867
3022
|
if (res.isError) return { uri: item.uri, ok: false, error: "tool returned isError" };
|
|
2868
3023
|
return { uri: item.uri, ok: true };
|
|
2869
3024
|
} catch (e) {
|
|
2870
|
-
return { uri: item.uri, ok: false, error:
|
|
3025
|
+
return { uri: item.uri, ok: false, error: msg4(e) };
|
|
2871
3026
|
}
|
|
2872
3027
|
}
|
|
2873
3028
|
};
|
|
@@ -2911,7 +3066,7 @@ function stringify(v) {
|
|
|
2911
3066
|
if (Array.isArray(v)) return v.join(", ");
|
|
2912
3067
|
return v == null ? "" : String(v);
|
|
2913
3068
|
}
|
|
2914
|
-
function
|
|
3069
|
+
function msg4(e) {
|
|
2915
3070
|
return e instanceof Error ? e.message : String(e);
|
|
2916
3071
|
}
|
|
2917
3072
|
|