@agentproto/corpus-cli 0.1.1 → 0.2.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 +310 -33
- package/dist/cli.mjs.map +1 -1
- package/dist/ports/index.d.ts +34 -6
- package/dist/ports/index.mjs +53 -24
- package/dist/ports/index.mjs.map +1 -1
- package/dist/specs/resources/aip-14/draft/TOOL.schema.json +1 -1
- package/dist/specs/resources/aip-52/draft/HARNESS.schema.json +254 -0
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -926,7 +926,8 @@ var YtDlpWhisperFetcher = class {
|
|
|
926
926
|
this.download = opts.download ?? defaultYtDlpDownloader(opts.ytDlpBin ?? "yt-dlp", {
|
|
927
927
|
maxDurationSec: opts.maxDurationSec,
|
|
928
928
|
cookiesFromBrowser: opts.cookiesFromBrowser,
|
|
929
|
-
cookiesFile: opts.cookiesFile
|
|
929
|
+
cookiesFile: opts.cookiesFile,
|
|
930
|
+
ffmpegLocation: opts.ffmpegLocation
|
|
930
931
|
});
|
|
931
932
|
this.extraHosts = opts.extraVideoHosts ? new Set(opts.extraVideoHosts) : void 0;
|
|
932
933
|
}
|
|
@@ -970,32 +971,44 @@ function isAuthError(e) {
|
|
|
970
971
|
const m = msg(e).toLowerCase();
|
|
971
972
|
return m.includes(" 401") || m.includes(" 403") || m.includes("unauthorized") || m.includes("forbidden") || m.includes("api key") || m.includes("api_key");
|
|
972
973
|
}
|
|
974
|
+
function buildYtDlpArgs(url, dir, opts) {
|
|
975
|
+
const durationGuard = opts?.maxDurationSec && opts.maxDurationSec > 0 ? ["--match-filter", `duration <= ${Math.floor(opts.maxDurationSec)}`] : [];
|
|
976
|
+
const cookieArgs = opts?.cookiesFromBrowser ? ["--cookies-from-browser", opts.cookiesFromBrowser] : opts?.cookiesFile ? ["--cookies", opts.cookiesFile] : [];
|
|
977
|
+
const ffmpegArgs = opts?.ffmpegLocation ? ["--ffmpeg-location", opts.ffmpegLocation] : [];
|
|
978
|
+
const client = cookieArgs.length === 0 ? "android" : "web_creator";
|
|
979
|
+
const extractorArgs = ["--extractor-args", `youtube:player_client=${client}`];
|
|
980
|
+
return [
|
|
981
|
+
"-f",
|
|
982
|
+
"bestaudio/best",
|
|
983
|
+
// /best fallback: many videos lack a standalone bestaudio
|
|
984
|
+
"-x",
|
|
985
|
+
"--audio-format",
|
|
986
|
+
"mp3",
|
|
987
|
+
"--audio-quality",
|
|
988
|
+
"64K",
|
|
989
|
+
"--no-playlist",
|
|
990
|
+
"--no-progress",
|
|
991
|
+
"--remote-components",
|
|
992
|
+
"ejs:github",
|
|
993
|
+
// solves YouTube nsig "n challenge"
|
|
994
|
+
...extractorArgs,
|
|
995
|
+
...durationGuard,
|
|
996
|
+
...cookieArgs,
|
|
997
|
+
...ffmpegArgs,
|
|
998
|
+
"--write-info-json",
|
|
999
|
+
"-o",
|
|
1000
|
+
join(dir, "track.%(ext)s"),
|
|
1001
|
+
url
|
|
1002
|
+
];
|
|
1003
|
+
}
|
|
973
1004
|
function defaultYtDlpDownloader(bin, opts) {
|
|
974
1005
|
return async (url) => {
|
|
975
1006
|
const dir = await mkdtemp(join(tmpdir(), "corpus-ytdlp-"));
|
|
976
1007
|
const cleanup = () => rm(dir, { recursive: true, force: true });
|
|
977
1008
|
try {
|
|
978
|
-
const durationGuard = opts?.maxDurationSec && opts.maxDurationSec > 0 ? ["--match-filter", `duration <= ${Math.floor(opts.maxDurationSec)}`] : [];
|
|
979
|
-
const cookieArgs = opts?.cookiesFromBrowser ? ["--cookies-from-browser", opts.cookiesFromBrowser] : opts?.cookiesFile ? ["--cookies", opts.cookiesFile] : [];
|
|
980
1009
|
await execFileAsync(
|
|
981
1010
|
bin,
|
|
982
|
-
|
|
983
|
-
"-f",
|
|
984
|
-
"bestaudio",
|
|
985
|
-
"-x",
|
|
986
|
-
"--audio-format",
|
|
987
|
-
"mp3",
|
|
988
|
-
"--audio-quality",
|
|
989
|
-
"64K",
|
|
990
|
-
"--no-playlist",
|
|
991
|
-
"--no-progress",
|
|
992
|
-
...durationGuard,
|
|
993
|
-
...cookieArgs,
|
|
994
|
-
"--write-info-json",
|
|
995
|
-
"-o",
|
|
996
|
-
join(dir, "track.%(ext)s"),
|
|
997
|
-
url
|
|
998
|
-
],
|
|
1011
|
+
buildYtDlpArgs(url, dir, opts),
|
|
999
1012
|
{ maxBuffer: 16 * 1024 * 1024 }
|
|
1000
1013
|
);
|
|
1001
1014
|
const files = await readdir(dir);
|
|
@@ -1451,6 +1464,7 @@ function parse(args) {
|
|
|
1451
1464
|
maxDurationSec: void 0,
|
|
1452
1465
|
cookiesFromBrowser: void 0,
|
|
1453
1466
|
cookiesFile: void 0,
|
|
1467
|
+
ffmpegLocation: void 0,
|
|
1454
1468
|
throttleMs: 2e3,
|
|
1455
1469
|
force: false,
|
|
1456
1470
|
diarize: false,
|
|
@@ -1495,6 +1509,9 @@ function parse(args) {
|
|
|
1495
1509
|
case "--cookies":
|
|
1496
1510
|
out.cookiesFile = next();
|
|
1497
1511
|
break;
|
|
1512
|
+
case "--ffmpeg-location":
|
|
1513
|
+
out.ffmpegLocation = next();
|
|
1514
|
+
break;
|
|
1498
1515
|
case "--throttle": {
|
|
1499
1516
|
const v = next();
|
|
1500
1517
|
if (v) out.throttleMs = Number(v);
|
|
@@ -1619,7 +1636,8 @@ ${plan}`);
|
|
|
1619
1636
|
stt,
|
|
1620
1637
|
...parsed.maxDurationSec ? { maxDurationSec: parsed.maxDurationSec } : {},
|
|
1621
1638
|
...parsed.cookiesFromBrowser ? { cookiesFromBrowser: parsed.cookiesFromBrowser } : {},
|
|
1622
|
-
...parsed.cookiesFile ? { cookiesFile: parsed.cookiesFile } : {}
|
|
1639
|
+
...parsed.cookiesFile ? { cookiesFile: parsed.cookiesFile } : {},
|
|
1640
|
+
...parsed.ffmpegLocation ? { ffmpegLocation: parsed.ffmpegLocation } : {}
|
|
1623
1641
|
})
|
|
1624
1642
|
);
|
|
1625
1643
|
if (parsed.scrapeMcp) {
|
|
@@ -1690,6 +1708,231 @@ knowledge engine (RAG) via the WriterPort. Import only stages the sources.
|
|
|
1690
1708
|
function msg2(e) {
|
|
1691
1709
|
return e instanceof Error ? e.message : String(e);
|
|
1692
1710
|
}
|
|
1711
|
+
function parse2(args) {
|
|
1712
|
+
const out = {
|
|
1713
|
+
topic: void 0,
|
|
1714
|
+
outputPath: void 0,
|
|
1715
|
+
max: 20,
|
|
1716
|
+
channels: /* @__PURE__ */ new Set(["web"]),
|
|
1717
|
+
lang: void 0,
|
|
1718
|
+
tags: [],
|
|
1719
|
+
doImport: false
|
|
1720
|
+
};
|
|
1721
|
+
for (let i = 0; i < args.length; i++) {
|
|
1722
|
+
const a = args[i];
|
|
1723
|
+
const next = () => args[++i];
|
|
1724
|
+
switch (a) {
|
|
1725
|
+
case "--max": {
|
|
1726
|
+
const v = next();
|
|
1727
|
+
if (v) out.max = Number(v);
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
case "--channels": {
|
|
1731
|
+
const v = next();
|
|
1732
|
+
if (v) out.channels = new Set(v.split(",").map((s) => s.trim()).filter(Boolean));
|
|
1733
|
+
break;
|
|
1734
|
+
}
|
|
1735
|
+
case "--lang":
|
|
1736
|
+
out.lang = next();
|
|
1737
|
+
break;
|
|
1738
|
+
case "--tags": {
|
|
1739
|
+
const v = next();
|
|
1740
|
+
if (v) out.tags.push(...v.split(",").map((s) => s.trim()).filter(Boolean));
|
|
1741
|
+
break;
|
|
1742
|
+
}
|
|
1743
|
+
case "--import":
|
|
1744
|
+
out.doImport = true;
|
|
1745
|
+
break;
|
|
1746
|
+
default:
|
|
1747
|
+
if (!a.startsWith("-")) {
|
|
1748
|
+
if (out.topic === void 0) out.topic = a;
|
|
1749
|
+
else if (out.outputPath === void 0) out.outputPath = a;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return out;
|
|
1754
|
+
}
|
|
1755
|
+
async function searchSerper(query, lang, max) {
|
|
1756
|
+
const apiKey = process.env.SERPER_API_KEY;
|
|
1757
|
+
const gl = lang === "fr" ? "fr" : lang ?? "us";
|
|
1758
|
+
const hl = lang ?? "en";
|
|
1759
|
+
const body = JSON.stringify({ q: query, gl, hl, num: max });
|
|
1760
|
+
const res = await fetch("https://google.serper.dev/search", {
|
|
1761
|
+
method: "POST",
|
|
1762
|
+
headers: { "X-API-KEY": apiKey, "Content-Type": "application/json" },
|
|
1763
|
+
body
|
|
1764
|
+
});
|
|
1765
|
+
if (!res.ok) throw new Error(`Serper ${res.status}: ${await res.text()}`);
|
|
1766
|
+
const json = await res.json();
|
|
1767
|
+
return (json.organic ?? []).flatMap((r) => r.link ? [{ url: r.link, title: r.title }] : []);
|
|
1768
|
+
}
|
|
1769
|
+
async function searchExa(query, _lang, max) {
|
|
1770
|
+
const apiKey = process.env.EXA_API_KEY;
|
|
1771
|
+
const body = JSON.stringify({ query, numResults: max, type: "neural", contents: { text: false } });
|
|
1772
|
+
const res = await fetch("https://api.exa.ai/search", {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
|
|
1775
|
+
body
|
|
1776
|
+
});
|
|
1777
|
+
if (!res.ok) throw new Error(`Exa ${res.status}: ${await res.text()}`);
|
|
1778
|
+
const json = await res.json();
|
|
1779
|
+
return (json.results ?? []).flatMap((r) => r.url ? [{ url: r.url, title: r.title }] : []);
|
|
1780
|
+
}
|
|
1781
|
+
async function searchTavily(query, _lang, max) {
|
|
1782
|
+
const apiKey = process.env.TAVILY_API_KEY;
|
|
1783
|
+
const body = JSON.stringify({ api_key: apiKey, query, max_results: max });
|
|
1784
|
+
const res = await fetch("https://api.tavily.com/search", {
|
|
1785
|
+
method: "POST",
|
|
1786
|
+
headers: { "Content-Type": "application/json" },
|
|
1787
|
+
body
|
|
1788
|
+
});
|
|
1789
|
+
if (!res.ok) throw new Error(`Tavily ${res.status}: ${await res.text()}`);
|
|
1790
|
+
const json = await res.json();
|
|
1791
|
+
return (json.results ?? []).flatMap((r) => r.url ? [{ url: r.url, title: r.title }] : []);
|
|
1792
|
+
}
|
|
1793
|
+
async function searchGoogle(query, lang, max) {
|
|
1794
|
+
const apiKey = process.env.GOOGLE_SEARCH_API_KEY;
|
|
1795
|
+
const cx = process.env.GOOGLE_SEARCH_CX ?? "";
|
|
1796
|
+
const params = new URLSearchParams({
|
|
1797
|
+
key: apiKey,
|
|
1798
|
+
q: query,
|
|
1799
|
+
num: String(Math.min(max, 10)),
|
|
1800
|
+
...cx ? { cx } : {},
|
|
1801
|
+
...lang ? { lr: `lang_${lang}`, hl: lang } : {}
|
|
1802
|
+
});
|
|
1803
|
+
const res = await fetch(`https://www.googleapis.com/customsearch/v1?${params}`);
|
|
1804
|
+
if (!res.ok) throw new Error(`Google ${res.status}: ${await res.text()}`);
|
|
1805
|
+
const json = await res.json();
|
|
1806
|
+
return (json.items ?? []).flatMap((r) => r.link ? [{ url: r.link, title: r.title }] : []);
|
|
1807
|
+
}
|
|
1808
|
+
function resolveWebProvider() {
|
|
1809
|
+
if (process.env.SERPER_API_KEY) return { name: "serper", fn: searchSerper };
|
|
1810
|
+
if (process.env.EXA_API_KEY) return { name: "exa", fn: searchExa };
|
|
1811
|
+
if (process.env.TAVILY_API_KEY) return { name: "tavily", fn: searchTavily };
|
|
1812
|
+
if (process.env.GOOGLE_SEARCH_API_KEY) return { name: "google", fn: searchGoogle };
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
function buildWebQueries(topic, lang) {
|
|
1816
|
+
const langSuffix = lang && lang !== "en" ? ` ${lang}` : "";
|
|
1817
|
+
const q1 = topic;
|
|
1818
|
+
const q2 = `${topic} guide tutorial${langSuffix}`;
|
|
1819
|
+
const q3 = lang && lang !== "en" ? `${topic} ressources ${lang}` : `${topic} best practices resources`;
|
|
1820
|
+
return [q1, q2, q3];
|
|
1821
|
+
}
|
|
1822
|
+
async function discoverWeb(topic, lang, max) {
|
|
1823
|
+
const provider = resolveWebProvider();
|
|
1824
|
+
if (!provider) {
|
|
1825
|
+
process.stderr.write("corpus discover: no web search API key found (SERPER_API_KEY / EXA_API_KEY / TAVILY_API_KEY / GOOGLE_SEARCH_API_KEY) \u2014 web channel skipped.\n");
|
|
1826
|
+
return { urls: [], provider: "none" };
|
|
1827
|
+
}
|
|
1828
|
+
const queries = buildWebQueries(topic, lang);
|
|
1829
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1830
|
+
const urls = [];
|
|
1831
|
+
for (const q of queries) {
|
|
1832
|
+
if (urls.length >= max) break;
|
|
1833
|
+
try {
|
|
1834
|
+
const results = await provider.fn(q, lang, max);
|
|
1835
|
+
for (const r of results) {
|
|
1836
|
+
if (urls.length >= max) break;
|
|
1837
|
+
if (!seen.has(r.url)) {
|
|
1838
|
+
seen.add(r.url);
|
|
1839
|
+
urls.push(r.url);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
} catch (e) {
|
|
1843
|
+
process.stderr.write(`corpus discover: web search query "${q}" failed \u2014 ${e instanceof Error ? e.message : String(e)}
|
|
1844
|
+
`);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
return { urls, provider: provider.name };
|
|
1848
|
+
}
|
|
1849
|
+
async function discoverYoutube(topic, max) {
|
|
1850
|
+
return new Promise((resolve2) => {
|
|
1851
|
+
execFile(
|
|
1852
|
+
"yt-dlp",
|
|
1853
|
+
[`ytsearch${max}:${topic}`, "--flat-playlist", "--print", "%(url)s", "--no-warnings"],
|
|
1854
|
+
{ timeout: 6e4 },
|
|
1855
|
+
(err, stdout, _stderr) => {
|
|
1856
|
+
if (err) {
|
|
1857
|
+
process.stderr.write(`corpus discover: youtube channel failed \u2014 ${err.message}
|
|
1858
|
+
`);
|
|
1859
|
+
resolve2([]);
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
resolve2(
|
|
1863
|
+
stdout.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("http"))
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
);
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
async function discoverSocial(_topic, _lang, _max) {
|
|
1870
|
+
process.stdout.write(" social channel needs a Bureau session \u2014 skipped.\n");
|
|
1871
|
+
return [];
|
|
1872
|
+
}
|
|
1873
|
+
async function runDiscover(args) {
|
|
1874
|
+
const parsed = parse2(args);
|
|
1875
|
+
if (!parsed.topic) {
|
|
1876
|
+
return fail("discover requires a <topic> argument. Usage: corpus discover <topic> [path] [--max N] [--channels web,youtube,social] [--lang fr] [--tags t] [--import]", 2);
|
|
1877
|
+
}
|
|
1878
|
+
const outputPath = parsed.outputPath ? resolveWorkspacePath(parsed.outputPath) : process.cwd();
|
|
1879
|
+
process.stdout.write(`discover "${parsed.topic}"
|
|
1880
|
+
`);
|
|
1881
|
+
process.stdout.write(` channels: ${[...parsed.channels].join(", ")} \xB7 max: ${parsed.max}${parsed.lang ? ` \xB7 lang: ${parsed.lang}` : ""}
|
|
1882
|
+
`);
|
|
1883
|
+
const webUrls = [];
|
|
1884
|
+
const ytUrls = [];
|
|
1885
|
+
const socialUrls = [];
|
|
1886
|
+
if (parsed.channels.has("web")) {
|
|
1887
|
+
const { urls, provider } = await discoverWeb(parsed.topic, parsed.lang, parsed.max);
|
|
1888
|
+
webUrls.push(...urls);
|
|
1889
|
+
if (urls.length > 0) process.stdout.write(` web (${provider}): ${urls.length} URLs
|
|
1890
|
+
`);
|
|
1891
|
+
}
|
|
1892
|
+
if (parsed.channels.has("youtube")) {
|
|
1893
|
+
const urls = await discoverYoutube(parsed.topic, parsed.max);
|
|
1894
|
+
ytUrls.push(...urls);
|
|
1895
|
+
if (urls.length > 0) process.stdout.write(` youtube: ${urls.length} URLs
|
|
1896
|
+
`);
|
|
1897
|
+
}
|
|
1898
|
+
if (parsed.channels.has("social")) {
|
|
1899
|
+
const urls = await discoverSocial();
|
|
1900
|
+
socialUrls.push(...urls);
|
|
1901
|
+
}
|
|
1902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1903
|
+
const allUrls = [];
|
|
1904
|
+
for (const url of [...webUrls, ...ytUrls, ...socialUrls]) {
|
|
1905
|
+
if (!seen.has(url)) {
|
|
1906
|
+
seen.add(url);
|
|
1907
|
+
allUrls.push(url);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
process.stdout.write(`
|
|
1911
|
+
web: ${webUrls.length} \xB7 youtube: ${ytUrls.length} \xB7 social: ${socialUrls.length} \xB7 total: ${allUrls.length} unique
|
|
1912
|
+
`);
|
|
1913
|
+
if (allUrls.length === 0) {
|
|
1914
|
+
process.stderr.write("corpus discover: no URLs found across any channel.\n");
|
|
1915
|
+
return 1;
|
|
1916
|
+
}
|
|
1917
|
+
await mkdir(outputPath, { recursive: true });
|
|
1918
|
+
const outFile = join(outputPath, "urls.discovered.txt");
|
|
1919
|
+
await writeFile(outFile, allUrls.join("\n") + "\n", "utf-8");
|
|
1920
|
+
process.stdout.write(`
|
|
1921
|
+
written \u2192 ${outFile}
|
|
1922
|
+
`);
|
|
1923
|
+
if (parsed.doImport) {
|
|
1924
|
+
process.stdout.write("\n --import: chaining import-web\u2026\n");
|
|
1925
|
+
const importArgs = [
|
|
1926
|
+
outputPath,
|
|
1927
|
+
"--urls-file",
|
|
1928
|
+
outFile,
|
|
1929
|
+
...parsed.tags.flatMap((t) => ["--tags", t]),
|
|
1930
|
+
...parsed.lang ? ["--lang", parsed.lang] : []
|
|
1931
|
+
];
|
|
1932
|
+
return runImportWeb(importArgs);
|
|
1933
|
+
}
|
|
1934
|
+
return 0;
|
|
1935
|
+
}
|
|
1693
1936
|
|
|
1694
1937
|
// src/ports/anthropic-distiller.ts
|
|
1695
1938
|
var ANTHROPIC_RESPONSE = z.object({
|
|
@@ -1703,15 +1946,17 @@ var AnthropicDistiller = class {
|
|
|
1703
1946
|
baseUrl;
|
|
1704
1947
|
maxItems;
|
|
1705
1948
|
onUsage;
|
|
1949
|
+
lang;
|
|
1706
1950
|
constructor(opts) {
|
|
1707
1951
|
this.apiKey = opts.apiKey;
|
|
1708
1952
|
this.model = opts.model ?? "claude-sonnet-4-6";
|
|
1709
1953
|
this.baseUrl = (opts.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/+$/, "");
|
|
1710
1954
|
this.maxItems = opts.maxItems ?? 8;
|
|
1711
1955
|
this.onUsage = opts.onUsage;
|
|
1956
|
+
this.lang = opts.lang;
|
|
1712
1957
|
}
|
|
1713
1958
|
async distill(input) {
|
|
1714
|
-
const prompt = buildDistillPrompt(input, this.maxItems);
|
|
1959
|
+
const prompt = buildDistillPrompt(input, this.maxItems, { lang: this.lang });
|
|
1715
1960
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1716
1961
|
const res = await fetch(`${this.baseUrl}/messages`, {
|
|
1717
1962
|
method: "POST",
|
|
@@ -1750,18 +1995,20 @@ var CliAgentDistiller = class {
|
|
|
1750
1995
|
maxItems;
|
|
1751
1996
|
timeoutMs;
|
|
1752
1997
|
cwd;
|
|
1998
|
+
lang;
|
|
1753
1999
|
constructor(opts) {
|
|
1754
2000
|
this.engine = opts.engine;
|
|
1755
2001
|
this.model = opts.model;
|
|
1756
2002
|
this.maxItems = opts.maxItems ?? 8;
|
|
1757
2003
|
this.timeoutMs = opts.timeoutMs ?? 12e4;
|
|
1758
2004
|
this.cwd = opts.cwd ?? tmpdir();
|
|
2005
|
+
this.lang = opts.lang;
|
|
1759
2006
|
}
|
|
1760
2007
|
async distill(input) {
|
|
1761
|
-
const prompt = buildDistillPrompt(input, this.maxItems);
|
|
2008
|
+
const prompt = buildDistillPrompt(input, this.maxItems, { lang: this.lang });
|
|
1762
2009
|
const stdout = await spawnWithStdin({
|
|
1763
2010
|
command: this.engine.command,
|
|
1764
|
-
args: this.engine.buildArgs({ ...this.model ? { model: this.model } : {} }),
|
|
2011
|
+
args: this.engine.buildArgs({ ...this.model ? { model: this.model } : {}, prompt }),
|
|
1765
2012
|
stdin: prompt,
|
|
1766
2013
|
cwd: this.cwd,
|
|
1767
2014
|
timeoutMs: this.timeoutMs
|
|
@@ -1836,11 +2083,23 @@ var OPENCODE = {
|
|
|
1836
2083
|
buildArgs: ({ model }) => ["run", ...model ? ["-m", model] : []],
|
|
1837
2084
|
parseOutput: plainTextOutput
|
|
1838
2085
|
};
|
|
2086
|
+
var HERMES = {
|
|
2087
|
+
id: "hermes",
|
|
2088
|
+
subscriptionBilled: true,
|
|
2089
|
+
command: "hermes",
|
|
2090
|
+
buildArgs: ({ model, prompt }) => [
|
|
2091
|
+
"--oneshot",
|
|
2092
|
+
prompt ?? "",
|
|
2093
|
+
...model ? ["-m", model] : []
|
|
2094
|
+
],
|
|
2095
|
+
parseOutput: plainTextOutput
|
|
2096
|
+
};
|
|
1839
2097
|
var CLI_ENGINES = {
|
|
1840
2098
|
[CLAUDE_CODE.id]: CLAUDE_CODE,
|
|
1841
2099
|
[GEMINI.id]: GEMINI,
|
|
1842
2100
|
[CODEX.id]: CODEX,
|
|
1843
|
-
[OPENCODE.id]: OPENCODE
|
|
2101
|
+
[OPENCODE.id]: OPENCODE,
|
|
2102
|
+
[HERMES.id]: HERMES
|
|
1844
2103
|
};
|
|
1845
2104
|
var PRICING = [
|
|
1846
2105
|
{ match: "opus", inPerM: 15, outPerM: 75 },
|
|
@@ -2009,10 +2268,11 @@ var DISTILLER_ENGINES = {
|
|
|
2009
2268
|
[DEFAULT_ENGINE]: {
|
|
2010
2269
|
id: DEFAULT_ENGINE,
|
|
2011
2270
|
needsApiKey: true,
|
|
2012
|
-
create: ({ apiKey, model, onUsage }) => new AnthropicDistiller({
|
|
2271
|
+
create: ({ apiKey, model, onUsage, lang }) => new AnthropicDistiller({
|
|
2013
2272
|
apiKey,
|
|
2014
2273
|
...model ? { model } : {},
|
|
2015
|
-
...onUsage ? { onUsage } : {}
|
|
2274
|
+
...onUsage ? { onUsage } : {},
|
|
2275
|
+
...lang ? { lang } : {}
|
|
2016
2276
|
})
|
|
2017
2277
|
},
|
|
2018
2278
|
...Object.fromEntries(
|
|
@@ -2021,19 +2281,24 @@ var DISTILLER_ENGINES = {
|
|
|
2021
2281
|
{
|
|
2022
2282
|
id: engine.id,
|
|
2023
2283
|
needsApiKey: false,
|
|
2024
|
-
create: ({ model }) => new CliAgentDistiller({
|
|
2284
|
+
create: ({ model, lang }) => new CliAgentDistiller({
|
|
2285
|
+
engine,
|
|
2286
|
+
...model ? { model } : {},
|
|
2287
|
+
...lang ? { lang } : {}
|
|
2288
|
+
})
|
|
2025
2289
|
}
|
|
2026
2290
|
])
|
|
2027
2291
|
)
|
|
2028
2292
|
};
|
|
2029
|
-
function
|
|
2293
|
+
function parse3(args) {
|
|
2030
2294
|
const out = {
|
|
2031
2295
|
workspace: void 0,
|
|
2032
2296
|
sourceId: void 0,
|
|
2033
2297
|
max: void 0,
|
|
2034
2298
|
throttleMs: 1e3,
|
|
2035
2299
|
model: void 0,
|
|
2036
|
-
engine: DEFAULT_ENGINE
|
|
2300
|
+
engine: DEFAULT_ENGINE,
|
|
2301
|
+
lang: void 0
|
|
2037
2302
|
};
|
|
2038
2303
|
for (let i = 0; i < args.length; i++) {
|
|
2039
2304
|
const a = args[i];
|
|
@@ -2060,6 +2325,9 @@ function parse2(args) {
|
|
|
2060
2325
|
if (v) out.engine = v;
|
|
2061
2326
|
break;
|
|
2062
2327
|
}
|
|
2328
|
+
case "--lang":
|
|
2329
|
+
out.lang = next();
|
|
2330
|
+
break;
|
|
2063
2331
|
default:
|
|
2064
2332
|
if (!a.startsWith("-") && out.workspace === void 0) out.workspace = a;
|
|
2065
2333
|
}
|
|
@@ -2119,7 +2387,7 @@ async function scanDistilledSourceIds(root) {
|
|
|
2119
2387
|
}
|
|
2120
2388
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2121
2389
|
async function runDistill(args) {
|
|
2122
|
-
const parsed =
|
|
2390
|
+
const parsed = parse3(args);
|
|
2123
2391
|
const target = resolveWorkspacePath(parsed.workspace);
|
|
2124
2392
|
const engine = DISTILLER_ENGINES[parsed.engine];
|
|
2125
2393
|
if (!engine) {
|
|
@@ -2157,7 +2425,8 @@ async function runDistill(args) {
|
|
|
2157
2425
|
distiller: engine.create({
|
|
2158
2426
|
...apiKey ? { apiKey } : {},
|
|
2159
2427
|
...parsed.model ? { model: parsed.model } : {},
|
|
2160
|
-
onUsage: usage.record
|
|
2428
|
+
onUsage: usage.record,
|
|
2429
|
+
...parsed.lang ? { lang: parsed.lang } : {}
|
|
2161
2430
|
})
|
|
2162
2431
|
});
|
|
2163
2432
|
let totalEntries = 0;
|
|
@@ -2741,6 +3010,12 @@ Commands:
|
|
|
2741
3010
|
events:emit <kind> --payload <json> [path]
|
|
2742
3011
|
Append an event to _log.md
|
|
2743
3012
|
events:tail [path] Print _log.md
|
|
3013
|
+
discover <topic> [path] [--max N --channels web,youtube,social --lang l --tags t --import]
|
|
3014
|
+
Fan out across channels (web search + YouTube +
|
|
3015
|
+
social), dedup, write urls.discovered.txt.
|
|
3016
|
+
Web: auto-picks first available key (SERPER /
|
|
3017
|
+
EXA / TAVILY / GOOGLE_SEARCH). YouTube: yt-dlp
|
|
3018
|
+
flat-playlist. --import chains import-web.
|
|
2744
3019
|
import-web [path] --urls-file <f> [--max n --max-duration s --throttle ms --tags t --lang l --force --diarize]
|
|
2745
3020
|
Import URLs as sources (video\u2192Whisper, article\u2192
|
|
2746
3021
|
readability). Resumable: skips already-ingested
|
|
@@ -2755,7 +3030,7 @@ Commands:
|
|
|
2755
3030
|
scrape MCP server (stealth + clean Markdown) for
|
|
2756
3031
|
walled/JS pages, ahead of plain readability.
|
|
2757
3032
|
--diarize: AssemblyAI speaker labels (interviews).
|
|
2758
|
-
distill [path] [--source id --max n --throttle ms --model m]
|
|
3033
|
+
distill [path] [--source id --max n --throttle ms --model m --lang l --engine e]
|
|
2759
3034
|
Distill raw sources \u2192 refined entries
|
|
2760
3035
|
(principle/pattern/\u2026) with sources:[id] provenance.
|
|
2761
3036
|
Resumable: skips already-distilled sources.
|
|
@@ -2805,6 +3080,8 @@ async function main(argv2) {
|
|
|
2805
3080
|
return await runEventsTail(rest);
|
|
2806
3081
|
case "import-web":
|
|
2807
3082
|
return await runImportWeb(rest);
|
|
3083
|
+
case "discover":
|
|
3084
|
+
return await runDiscover(rest);
|
|
2808
3085
|
case "distill":
|
|
2809
3086
|
return await runDistill(rest);
|
|
2810
3087
|
case "knowledge":
|