@tacone/prosey 0.2.3 → 0.2.6
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 +63 -12
- package/bin/prosey +248 -66
- package/package.json +1 -1
- package/src/config-command.test.ts +50 -0
- package/src/config.ts +12 -0
- package/src/debug.test.ts +87 -0
- package/src/debug.ts +47 -5
- package/src/default-config.toml +19 -2
- package/src/format.test.ts +17 -1
- package/src/index.ts +151 -44
- package/src/pager.test.ts +65 -0
- package/src/pager.ts +23 -0
- package/src/version-check.ts +32 -0
package/bin/prosey
CHANGED
|
@@ -2114,7 +2114,7 @@ var require_symbol_define_to_primitive = __commonJS((exports, module) => {
|
|
|
2114
2114
|
var valueOf = SymbolPrototype && SymbolPrototype.valueOf;
|
|
2115
2115
|
var TO_PRIMITIVE = wellKnownSymbol("toPrimitive");
|
|
2116
2116
|
if (SymbolPrototype && !SymbolPrototype[TO_PRIMITIVE]) {
|
|
2117
|
-
defineBuiltIn(SymbolPrototype, TO_PRIMITIVE, function(
|
|
2117
|
+
defineBuiltIn(SymbolPrototype, TO_PRIMITIVE, function(hint2) {
|
|
2118
2118
|
return call(valueOf, this);
|
|
2119
2119
|
}, { arity: 1 });
|
|
2120
2120
|
}
|
|
@@ -4397,9 +4397,9 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
4397
4397
|
return global2 ? result || [] : result && result[0];
|
|
4398
4398
|
};
|
|
4399
4399
|
XRegExp.matchChain = function(str, chain) {
|
|
4400
|
-
return function recurseChain(values,
|
|
4401
|
-
var item = chain[
|
|
4402
|
-
regex: chain[
|
|
4400
|
+
return function recurseChain(values, level2) {
|
|
4401
|
+
var item = chain[level2].regex ? chain[level2] : {
|
|
4402
|
+
regex: chain[level2]
|
|
4403
4403
|
};
|
|
4404
4404
|
var matches = [];
|
|
4405
4405
|
function addMatch(match) {
|
|
@@ -4430,7 +4430,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
4430
4430
|
} finally {
|
|
4431
4431
|
_iterator3.f();
|
|
4432
4432
|
}
|
|
4433
|
-
return
|
|
4433
|
+
return level2 === chain.length - 1 || !matches.length ? matches : recurseChain(matches, level2 + 1);
|
|
4434
4434
|
}([str], 0);
|
|
4435
4435
|
};
|
|
4436
4436
|
XRegExp.replace = function(str, search, replacement, scope) {
|
|
@@ -94145,9 +94145,81 @@ ${Me13.join(`
|
|
|
94145
94145
|
Y42 = k0(G42);
|
|
94146
94146
|
});
|
|
94147
94147
|
|
|
94148
|
+
// src/debug.ts
|
|
94149
|
+
var GRAY = "\x1B[90m";
|
|
94150
|
+
var RESET = "\x1B[0m";
|
|
94151
|
+
var level = "normal";
|
|
94152
|
+
function setLevel(l) {
|
|
94153
|
+
level = l;
|
|
94154
|
+
}
|
|
94155
|
+
var lastTime = performance.now();
|
|
94156
|
+
var resumed = false;
|
|
94157
|
+
function stamp() {
|
|
94158
|
+
const now = performance.now();
|
|
94159
|
+
if (resumed) {
|
|
94160
|
+
lastTime = now;
|
|
94161
|
+
resumed = false;
|
|
94162
|
+
}
|
|
94163
|
+
const elapsed = now - lastTime;
|
|
94164
|
+
lastTime = now;
|
|
94165
|
+
const text = elapsed < 1000 ? `${elapsed.toFixed(0)}ms` : `${(elapsed / 1000).toFixed(1)}s`;
|
|
94166
|
+
return text.padStart(5);
|
|
94167
|
+
}
|
|
94168
|
+
var INFO_BEFORE = "\x1B[0m\x1B[2m\x1B[1m";
|
|
94169
|
+
var INFO_AFTER = "\x1B[0m";
|
|
94170
|
+
function info(...args) {
|
|
94171
|
+
if (level === "quiet")
|
|
94172
|
+
return;
|
|
94173
|
+
console.error(INFO_BEFORE, `[${stamp()}]`, ...args, INFO_AFTER);
|
|
94174
|
+
}
|
|
94175
|
+
function debug(...args) {
|
|
94176
|
+
if (level !== "verbose")
|
|
94177
|
+
return;
|
|
94178
|
+
console.error(GRAY, `[${stamp()}]`, ...args, RESET);
|
|
94179
|
+
}
|
|
94180
|
+
function startTimer() {
|
|
94181
|
+
resumed = true;
|
|
94182
|
+
}
|
|
94183
|
+
function resetTimer() {
|
|
94184
|
+
lastTime = performance.now();
|
|
94185
|
+
resumed = false;
|
|
94186
|
+
}
|
|
94187
|
+
var YELLOW = "\x1B[33m";
|
|
94188
|
+
function hint(message) {
|
|
94189
|
+
console.error(YELLOW + message + RESET + " " + GRAY + "[use prosey config to disable hints]" + RESET);
|
|
94190
|
+
}
|
|
94191
|
+
|
|
94148
94192
|
// src/index.ts
|
|
94193
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
94149
94194
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
94150
94195
|
|
|
94196
|
+
// src/pager.ts
|
|
94197
|
+
import { execSync } from "node:child_process";
|
|
94198
|
+
function hasCommand(cmd) {
|
|
94199
|
+
try {
|
|
94200
|
+
execSync(`command -v ${cmd} 2>/dev/null`, { stdio: "ignore" });
|
|
94201
|
+
return true;
|
|
94202
|
+
} catch {
|
|
94203
|
+
return false;
|
|
94204
|
+
}
|
|
94205
|
+
}
|
|
94206
|
+
function detectPager(cfgPager) {
|
|
94207
|
+
const env = process.env.PROSEY_PAGER;
|
|
94208
|
+
if (env !== undefined && env !== "" && env !== "auto")
|
|
94209
|
+
return env;
|
|
94210
|
+
if (cfgPager !== undefined && cfgPager !== "" && cfgPager !== "auto")
|
|
94211
|
+
return cfgPager;
|
|
94212
|
+
if (hasCommand("bat"))
|
|
94213
|
+
return "bat -lmd --style plain";
|
|
94214
|
+
if (hasCommand("glow"))
|
|
94215
|
+
return "glow -p";
|
|
94216
|
+
if (hasCommand("mdcat"))
|
|
94217
|
+
return "mdcat -l -p";
|
|
94218
|
+
if (hasCommand("less"))
|
|
94219
|
+
return "less";
|
|
94220
|
+
return null;
|
|
94221
|
+
}
|
|
94222
|
+
|
|
94151
94223
|
// node_modules/youtube-transcript-plus/dist/youtube-transcript-plus.mjs
|
|
94152
94224
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
94153
94225
|
function adopt(value) {
|
|
@@ -101530,6 +101602,16 @@ var lt = (r) => {
|
|
|
101530
101602
|
var FALLBACK_CONFIG_TOML = `# Default prosey configuration
|
|
101531
101603
|
# Created automatically on first run. Edit as needed.
|
|
101532
101604
|
|
|
101605
|
+
# Pager command for transcript and summary output.
|
|
101606
|
+
# Defaults to "auto": bat -lmd --style plain → glow -p → mdcat -l -p → less
|
|
101607
|
+
# Set to a custom command (e.g. "less -R") to override.
|
|
101608
|
+
# Can also be set via the PROSEY_PAGER env var (takes precedence).
|
|
101609
|
+
pager = "auto"
|
|
101610
|
+
|
|
101611
|
+
# Show hints for missing tools (e.g. markdown highlighter).
|
|
101612
|
+
# Can also be set via PROSEY_HINTS env var (yes, no, 1, 0, true, false).
|
|
101613
|
+
hints = true
|
|
101614
|
+
|
|
101533
101615
|
[summarize]
|
|
101534
101616
|
# Prompt sent to the command via stdin.
|
|
101535
101617
|
# Customize this to change how transcripts are summarized.
|
|
@@ -101674,23 +101756,10 @@ async function writeCache(dir, filename, data) {
|
|
|
101674
101756
|
await mkdir2(dir, { recursive: true });
|
|
101675
101757
|
await writeFile2(join2(dir, filename), data, "utf8");
|
|
101676
101758
|
}
|
|
101677
|
-
|
|
101678
|
-
// src/debug.ts
|
|
101679
|
-
var GRAY = "\x1B[90m";
|
|
101680
|
-
var RESET = "\x1B[0m";
|
|
101681
|
-
var enabled = false;
|
|
101682
|
-
function enableDebug() {
|
|
101683
|
-
enabled = true;
|
|
101684
|
-
}
|
|
101685
|
-
function debug(...args) {
|
|
101686
|
-
if (!enabled)
|
|
101687
|
-
return;
|
|
101688
|
-
console.error(GRAY, ...args, RESET);
|
|
101689
|
-
}
|
|
101690
101759
|
// package.json
|
|
101691
101760
|
var package_default = {
|
|
101692
101761
|
name: "@tacone/prosey",
|
|
101693
|
-
version: "0.2.
|
|
101762
|
+
version: "0.2.6",
|
|
101694
101763
|
description: "Download YouTube video transcripts from the CLI",
|
|
101695
101764
|
module: "src/index.ts",
|
|
101696
101765
|
type: "module",
|
|
@@ -101753,6 +101822,31 @@ var package_default = {
|
|
|
101753
101822
|
}
|
|
101754
101823
|
};
|
|
101755
101824
|
|
|
101825
|
+
// src/version-check.ts
|
|
101826
|
+
var TIMEOUT_MS = 3000;
|
|
101827
|
+
var registryUrl = `https://registry.npmjs.org/${package_default.name}/latest`;
|
|
101828
|
+
async function checkVersion() {
|
|
101829
|
+
try {
|
|
101830
|
+
const controller = new AbortController;
|
|
101831
|
+
const timer2 = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
101832
|
+
const res = await fetch(registryUrl, { signal: controller.signal });
|
|
101833
|
+
clearTimeout(timer2);
|
|
101834
|
+
if (!res.ok) {
|
|
101835
|
+
debug("Version check failed: HTTP", res.status);
|
|
101836
|
+
return null;
|
|
101837
|
+
}
|
|
101838
|
+
const data = await res.json();
|
|
101839
|
+
if (!data.version) {
|
|
101840
|
+
debug("Version check: no version field in response");
|
|
101841
|
+
return null;
|
|
101842
|
+
}
|
|
101843
|
+
return data.version;
|
|
101844
|
+
} catch (err) {
|
|
101845
|
+
debug("Version check error:", err instanceof Error ? err.message : String(err));
|
|
101846
|
+
return null;
|
|
101847
|
+
}
|
|
101848
|
+
}
|
|
101849
|
+
|
|
101756
101850
|
// node_modules/prettier/index.mjs
|
|
101757
101851
|
import { createRequire as __prettierCreateRequire } from "module";
|
|
101758
101852
|
import { fileURLToPath as __prettierFileUrlToPath } from "url";
|
|
@@ -102266,7 +102360,7 @@ function addAlignmentToDoc(doc, size, tabWidth) {
|
|
|
102266
102360
|
assertDoc(doc);
|
|
102267
102361
|
let aligned = doc;
|
|
102268
102362
|
if (size > 0) {
|
|
102269
|
-
for (let
|
|
102363
|
+
for (let level2 = 0;level2 < Math.floor(size / tabWidth); ++level2) {
|
|
102270
102364
|
aligned = indent(aligned);
|
|
102271
102365
|
}
|
|
102272
102366
|
aligned = align(size % tabWidth, aligned);
|
|
@@ -107697,7 +107791,7 @@ var require_partial = __commonJS2({
|
|
|
107697
107791
|
match(filepath) {
|
|
107698
107792
|
const parts = filepath.split("/");
|
|
107699
107793
|
const levels = parts.length;
|
|
107700
|
-
const patterns = this._storage.filter((
|
|
107794
|
+
const patterns = this._storage.filter((info2) => !info2.complete || info2.segments.length > levels);
|
|
107701
107795
|
for (const pattern of patterns) {
|
|
107702
107796
|
const section = pattern.sections[0];
|
|
107703
107797
|
if (!pattern.complete && levels > section.length) {
|
|
@@ -108269,10 +108363,10 @@ var require_picocolors = __commonJS2({
|
|
|
108269
108363
|
} while (~index);
|
|
108270
108364
|
return result + string.substring(cursor2);
|
|
108271
108365
|
};
|
|
108272
|
-
var createColors2 = (
|
|
108273
|
-
let f7 =
|
|
108366
|
+
var createColors2 = (enabled = isColorSupported2) => {
|
|
108367
|
+
let f7 = enabled ? formatter : () => String;
|
|
108274
108368
|
return {
|
|
108275
|
-
isColorSupported:
|
|
108369
|
+
isColorSupported: enabled,
|
|
108276
108370
|
reset: f7("\x1B[0m", "\x1B[0m"),
|
|
108277
108371
|
bold: f7("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
108278
108372
|
dim: f7("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
@@ -113965,8 +114059,8 @@ function buildDefs(colors) {
|
|
|
113965
114059
|
}
|
|
113966
114060
|
var defsOn = buildDefs((0, import_picocolors4.createColors)(true));
|
|
113967
114061
|
var defsOff = buildDefs((0, import_picocolors4.createColors)(false));
|
|
113968
|
-
function getDefs(
|
|
113969
|
-
return
|
|
114062
|
+
function getDefs(enabled) {
|
|
114063
|
+
return enabled ? defsOn : defsOff;
|
|
113970
114064
|
}
|
|
113971
114065
|
var sometimesKeywords = /* @__PURE__ */ new Set(["as", "async", "from", "get", "of", "set"]);
|
|
113972
114066
|
var NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
|
|
@@ -120293,18 +120387,30 @@ var debugApis = {
|
|
|
120293
120387
|
// src/index.ts
|
|
120294
120388
|
var NAME2 = "prosey";
|
|
120295
120389
|
var VERSION2 = package_default.version;
|
|
120390
|
+
var latestVersion = null;
|
|
120391
|
+
var versionCheck = checkVersion().then((v11) => {
|
|
120392
|
+
latestVersion = v11;
|
|
120393
|
+
});
|
|
120394
|
+
function exitProcess(code) {
|
|
120395
|
+
if (useHints && code === 0 && latestVersion && latestVersion !== VERSION2) {
|
|
120396
|
+
hint(`\uD83D\uDCE6 New version available: ${latestVersion} — use npm/pnpm/bun -g i ${package_default.name} to upgrade`);
|
|
120397
|
+
}
|
|
120398
|
+
process.exit(code);
|
|
120399
|
+
}
|
|
120296
120400
|
function help() {
|
|
120297
120401
|
return `${NAME2} v${VERSION2}
|
|
120298
120402
|
|
|
120299
120403
|
Usage: ${NAME2} [options] <video-url-or-id>
|
|
120300
120404
|
${NAME2} info [options] <video-url-or-id>
|
|
120301
120405
|
${NAME2} summarize [options] <video-url-or-id>
|
|
120406
|
+
${NAME2} config
|
|
120302
120407
|
|
|
120303
120408
|
Download a YouTube video transcript or show video details.
|
|
120304
120409
|
|
|
120305
120410
|
Commands:
|
|
120306
120411
|
info Show video metadata (title, channel, duration, etc.)
|
|
120307
120412
|
summarize Pipe transcript to the command configured in [summarize]
|
|
120413
|
+
config Open config file in $EDITOR
|
|
120308
120414
|
|
|
120309
120415
|
Arguments:
|
|
120310
120416
|
video-url-or-id YouTube URL (full or short) or bare video ID
|
|
@@ -120322,7 +120428,12 @@ Options:
|
|
|
120322
120428
|
--reset-config Reset config file to defaults and exit.
|
|
120323
120429
|
--no-cache Skip cache and overwrite cache files.
|
|
120324
120430
|
--no-format Skip prettier formatting.
|
|
120325
|
-
--
|
|
120431
|
+
--no-pager Disable pager for stdout output.
|
|
120432
|
+
--pager Use pager for stdout output (default).
|
|
120433
|
+
--no-hints Disable hints.
|
|
120434
|
+
--hints Show hints (default).
|
|
120435
|
+
-q, --quiet Suppress all stderr logging.
|
|
120436
|
+
-v, --verbose Print debug information to stderr.
|
|
120326
120437
|
--help Show this help message.
|
|
120327
120438
|
--version Show version.
|
|
120328
120439
|
|
|
@@ -120392,23 +120503,56 @@ async function formatMd(text) {
|
|
|
120392
120503
|
return text;
|
|
120393
120504
|
}
|
|
120394
120505
|
}
|
|
120506
|
+
async function outputText(text) {
|
|
120507
|
+
if (outputPath) {
|
|
120508
|
+
await writeFile3(outputPath, text, "utf8");
|
|
120509
|
+
return;
|
|
120510
|
+
}
|
|
120511
|
+
if (!pagerCmd || !process.stdout.isTTY) {
|
|
120512
|
+
process.stdout.write(text);
|
|
120513
|
+
return;
|
|
120514
|
+
}
|
|
120515
|
+
const parts = pagerCmd.split(/\s+/);
|
|
120516
|
+
const proc = spawn2(parts[0], parts.slice(1), {
|
|
120517
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
120518
|
+
});
|
|
120519
|
+
await new Promise((resolve3) => {
|
|
120520
|
+
let done = false;
|
|
120521
|
+
proc.on("error", () => {
|
|
120522
|
+
if (done)
|
|
120523
|
+
return;
|
|
120524
|
+
done = true;
|
|
120525
|
+
process.stdout.write(text);
|
|
120526
|
+
resolve3();
|
|
120527
|
+
});
|
|
120528
|
+
proc.on("exit", () => {
|
|
120529
|
+
if (done)
|
|
120530
|
+
return;
|
|
120531
|
+
done = true;
|
|
120532
|
+
resolve3();
|
|
120533
|
+
});
|
|
120534
|
+
proc.stdin.write(text);
|
|
120535
|
+
proc.stdin.end();
|
|
120536
|
+
});
|
|
120537
|
+
}
|
|
120538
|
+
var pagerCmd = null;
|
|
120395
120539
|
var args = process.argv.slice(2);
|
|
120396
120540
|
if (args.length === 0 || args.includes("--help")) {
|
|
120397
120541
|
console.log(help());
|
|
120398
|
-
|
|
120542
|
+
exitProcess(0);
|
|
120399
120543
|
}
|
|
120400
120544
|
if (args.includes("--version")) {
|
|
120401
120545
|
console.log(VERSION2);
|
|
120402
|
-
|
|
120546
|
+
exitProcess(0);
|
|
120403
120547
|
}
|
|
120404
120548
|
if (args.includes("--reset-config")) {
|
|
120405
120549
|
const path15 = await resetConfig();
|
|
120406
120550
|
console.log(`Config reset to defaults: ${path15}`);
|
|
120407
|
-
|
|
120551
|
+
exitProcess(0);
|
|
120408
120552
|
}
|
|
120409
120553
|
var config = await loadConfig().catch(() => ({}));
|
|
120410
120554
|
var mode = "transcript";
|
|
120411
|
-
var subcmdIndex = args.findIndex((a5) => a5 === "info" || a5 === "summarize");
|
|
120555
|
+
var subcmdIndex = args.findIndex((a5) => a5 === "info" || a5 === "summarize" || a5 === "config");
|
|
120412
120556
|
if (subcmdIndex !== -1) {
|
|
120413
120557
|
mode = args[subcmdIndex];
|
|
120414
120558
|
args.splice(subcmdIndex, 1);
|
|
@@ -120423,7 +120567,9 @@ var noDecode = false;
|
|
|
120423
120567
|
var showDetails = true;
|
|
120424
120568
|
var noCache = false;
|
|
120425
120569
|
var noFormat = false;
|
|
120426
|
-
var
|
|
120570
|
+
var usePager = true;
|
|
120571
|
+
var useHints = true;
|
|
120572
|
+
var logLevel = "normal";
|
|
120427
120573
|
for (let i = 0;i < args.length; i++) {
|
|
120428
120574
|
const arg = args[i];
|
|
120429
120575
|
if (!arg)
|
|
@@ -120432,7 +120578,7 @@ for (let i = 0;i < args.length; i++) {
|
|
|
120432
120578
|
lang = args[++i] ?? undefined;
|
|
120433
120579
|
if (!lang) {
|
|
120434
120580
|
console.error("Error: --lang requires a language code");
|
|
120435
|
-
|
|
120581
|
+
exitProcess(1);
|
|
120436
120582
|
}
|
|
120437
120583
|
} else if (arg === "--timestamps" || arg === "-t") {
|
|
120438
120584
|
timestamps = true;
|
|
@@ -120442,7 +120588,7 @@ for (let i = 0;i < args.length; i++) {
|
|
|
120442
120588
|
outputPath = args[++i] ?? undefined;
|
|
120443
120589
|
if (!outputPath) {
|
|
120444
120590
|
console.error("Error: -o/--output requires a file path");
|
|
120445
|
-
|
|
120591
|
+
exitProcess(1);
|
|
120446
120592
|
}
|
|
120447
120593
|
} else if (arg === "--json") {
|
|
120448
120594
|
outputJson = true;
|
|
@@ -120456,35 +120602,76 @@ for (let i = 0;i < args.length; i++) {
|
|
|
120456
120602
|
noCache = true;
|
|
120457
120603
|
} else if (arg === "--no-format") {
|
|
120458
120604
|
noFormat = true;
|
|
120459
|
-
} else if (arg === "--
|
|
120460
|
-
|
|
120605
|
+
} else if (arg === "--no-pager") {
|
|
120606
|
+
usePager = false;
|
|
120607
|
+
} else if (arg === "--pager") {
|
|
120608
|
+
usePager = true;
|
|
120609
|
+
} else if (arg === "--no-hints") {
|
|
120610
|
+
useHints = false;
|
|
120611
|
+
} else if (arg === "--hints") {
|
|
120612
|
+
useHints = true;
|
|
120613
|
+
} else if (arg === "--quiet" || arg === "-q") {
|
|
120614
|
+
logLevel = "quiet";
|
|
120615
|
+
} else if (arg === "--verbose" || arg === "-v") {
|
|
120616
|
+
logLevel = "verbose";
|
|
120461
120617
|
} else if (arg === "--no-decode-entities") {
|
|
120462
120618
|
noDecode = true;
|
|
120463
120619
|
} else if (arg.startsWith("-")) {
|
|
120464
120620
|
console.error(`Unknown option: ${arg}`);
|
|
120465
|
-
|
|
120621
|
+
exitProcess(1);
|
|
120466
120622
|
} else {
|
|
120467
120623
|
videoId = arg;
|
|
120468
120624
|
}
|
|
120469
120625
|
}
|
|
120626
|
+
if (mode === "config") {
|
|
120627
|
+
const path15 = configPath();
|
|
120628
|
+
const editor = process.env.EDITOR;
|
|
120629
|
+
if (editor) {
|
|
120630
|
+
await new Promise((resolve3) => {
|
|
120631
|
+
const proc = spawn2(editor, [path15], { stdio: "inherit" });
|
|
120632
|
+
proc.on("exit", () => resolve3());
|
|
120633
|
+
proc.on("error", () => resolve3());
|
|
120634
|
+
});
|
|
120635
|
+
} else {
|
|
120636
|
+
console.log(`Config file: ${path15}`);
|
|
120637
|
+
}
|
|
120638
|
+
exitProcess(0);
|
|
120639
|
+
}
|
|
120470
120640
|
if (!videoId) {
|
|
120471
120641
|
console.error("Error: missing video URL or ID");
|
|
120472
120642
|
console.log(help());
|
|
120473
|
-
|
|
120643
|
+
exitProcess(1);
|
|
120474
120644
|
}
|
|
120475
120645
|
var extracted = extractVideoId(videoId);
|
|
120476
120646
|
if (!extracted) {
|
|
120477
120647
|
console.error("Error: invalid YouTube video URL or ID");
|
|
120478
|
-
|
|
120648
|
+
exitProcess(65);
|
|
120479
120649
|
}
|
|
120480
120650
|
videoId = extracted;
|
|
120481
|
-
|
|
120482
|
-
|
|
120651
|
+
setLevel(logLevel);
|
|
120652
|
+
resetTimer();
|
|
120653
|
+
pagerCmd = usePager ? detectPager(config.pager) : null;
|
|
120654
|
+
debug("Pager:", pagerCmd ?? "none");
|
|
120655
|
+
{
|
|
120656
|
+
const envHints = process.env.PROSEY_HINTS;
|
|
120657
|
+
if (envHints !== undefined) {
|
|
120658
|
+
useHints = envHints === "yes" || envHints === "1" || envHints === "true";
|
|
120659
|
+
} else if (config.hints !== undefined) {
|
|
120660
|
+
useHints = config.hints;
|
|
120661
|
+
}
|
|
120662
|
+
}
|
|
120663
|
+
if (useHints) {
|
|
120664
|
+
const hasMarkdownPager = pagerCmd === "bat -lmd --style plain" || pagerCmd === "glow -p" || pagerCmd === "mdcat -l -p";
|
|
120665
|
+
if (!hasMarkdownPager) {
|
|
120666
|
+
hint("Tip: install a markdown highlighter for better output (e.g. bat, glow, mdcat)");
|
|
120667
|
+
}
|
|
120668
|
+
}
|
|
120483
120669
|
debug("Config file:", configPath());
|
|
120484
120670
|
debug("Video ID:", videoId);
|
|
120485
120671
|
debug("Mode:", mode);
|
|
120486
120672
|
if (lang)
|
|
120487
120673
|
debug("Language:", lang);
|
|
120674
|
+
await Promise.race([versionCheck, new Promise((r5) => setTimeout(r5, 1000))]);
|
|
120488
120675
|
try {
|
|
120489
120676
|
if (mode === "info") {
|
|
120490
120677
|
const result = await fetchTranscript(videoId, { videoDetails: true, lang });
|
|
@@ -120493,21 +120680,23 @@ try {
|
|
|
120493
120680
|
} else {
|
|
120494
120681
|
printVideoInfo(result.videoDetails);
|
|
120495
120682
|
}
|
|
120496
|
-
|
|
120683
|
+
exitProcess(0);
|
|
120497
120684
|
}
|
|
120498
120685
|
if (mode === "summarize") {
|
|
120499
120686
|
if (!config.summarize?.command) {
|
|
120500
120687
|
console.error("Error: [summarize] section with a command is required in config");
|
|
120501
|
-
|
|
120688
|
+
exitProcess(1);
|
|
120502
120689
|
}
|
|
120503
120690
|
const cacheOpts2 = { lang, mode: "summarize", noDecode };
|
|
120504
120691
|
const dir2 = cacheDir(videoId, cacheOpts2);
|
|
120505
120692
|
let segments2 = null;
|
|
120506
120693
|
let summary = null;
|
|
120694
|
+
startTimer();
|
|
120507
120695
|
if (!noCache) {
|
|
120508
120696
|
const cachedSegments = await readCache(dir2, "transcript.json");
|
|
120509
120697
|
const cachedSummary = await readCache(dir2, "summary.md");
|
|
120510
120698
|
if (cachedSegments && cachedSummary) {
|
|
120699
|
+
info("Transcript cached");
|
|
120511
120700
|
debug("Cache hit:", dir2);
|
|
120512
120701
|
segments2 = JSON.parse(cachedSegments);
|
|
120513
120702
|
summary = cachedSummary;
|
|
@@ -120518,46 +120707,45 @@ try {
|
|
|
120518
120707
|
debug("Cache skipped (--no-cache)");
|
|
120519
120708
|
}
|
|
120520
120709
|
if (!segments2) {
|
|
120521
|
-
|
|
120710
|
+
info("Fetching transcript...");
|
|
120522
120711
|
segments2 = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
|
|
120523
|
-
|
|
120712
|
+
info(`Transcript: ${segments2.length} segments`);
|
|
120524
120713
|
await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
|
|
120525
120714
|
debug("Cache written: transcript.json");
|
|
120526
120715
|
}
|
|
120527
120716
|
const prompt = config.summarize.prompt ?? "";
|
|
120528
120717
|
const transcriptText = toText(segments2, !noDecode);
|
|
120529
120718
|
if (!summary) {
|
|
120530
|
-
|
|
120719
|
+
info(`Summarizing...`);
|
|
120531
120720
|
summary = await summarize({
|
|
120532
120721
|
prompt,
|
|
120533
120722
|
command: config.summarize.command,
|
|
120534
120723
|
transcript: transcriptText,
|
|
120535
120724
|
cwd: dir2
|
|
120536
120725
|
});
|
|
120537
|
-
|
|
120726
|
+
info("Summary ready");
|
|
120538
120727
|
await writeCache(dir2, "summary.md", summary);
|
|
120539
120728
|
debug("Cache written: summary.md");
|
|
120540
120729
|
}
|
|
120541
120730
|
const formatted = noFormat ? summary : await formatMd(summary);
|
|
120542
|
-
|
|
120543
|
-
|
|
120544
|
-
|
|
120545
|
-
console.log(formatted);
|
|
120546
|
-
}
|
|
120547
|
-
process.exit(0);
|
|
120731
|
+
await outputText(formatted + `
|
|
120732
|
+
`);
|
|
120733
|
+
exitProcess(0);
|
|
120548
120734
|
} else if (listOnly) {
|
|
120549
120735
|
const languages2 = await listLanguages(videoId);
|
|
120550
120736
|
printLanguages(languages2);
|
|
120551
|
-
|
|
120737
|
+
exitProcess(0);
|
|
120552
120738
|
}
|
|
120553
120739
|
const decode = !noDecode;
|
|
120554
120740
|
const cacheOpts = { lang, timestamps, json: outputJson, noDecode };
|
|
120555
120741
|
const dir = cacheDir(videoId, cacheOpts);
|
|
120556
120742
|
let segments = null;
|
|
120557
120743
|
let videoDetailsCache = null;
|
|
120744
|
+
startTimer();
|
|
120558
120745
|
if (!noCache) {
|
|
120559
120746
|
const cached = await readCache(dir, "transcript.json");
|
|
120560
120747
|
if (cached) {
|
|
120748
|
+
info("Transcript cached");
|
|
120561
120749
|
debug("Cache hit:", dir);
|
|
120562
120750
|
segments = JSON.parse(cached);
|
|
120563
120751
|
} else {
|
|
@@ -120567,7 +120755,7 @@ try {
|
|
|
120567
120755
|
debug("Cache skipped (--no-cache)");
|
|
120568
120756
|
}
|
|
120569
120757
|
if (!segments) {
|
|
120570
|
-
|
|
120758
|
+
info("Fetching transcript...");
|
|
120571
120759
|
if (showDetails && !outputJson) {
|
|
120572
120760
|
const opts = lang ? { lang, videoDetails: true } : { videoDetails: true };
|
|
120573
120761
|
const result = await fetchTranscript(videoId, opts);
|
|
@@ -120576,6 +120764,7 @@ try {
|
|
|
120576
120764
|
} else {
|
|
120577
120765
|
segments = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
|
|
120578
120766
|
}
|
|
120767
|
+
info(`Transcript: ${segments.length} segments`);
|
|
120579
120768
|
await writeCache(dir, "transcript.json", JSON.stringify(segments));
|
|
120580
120769
|
}
|
|
120581
120770
|
if (showDetails && !outputJson) {
|
|
@@ -120591,24 +120780,17 @@ try {
|
|
|
120591
120780
|
|
|
120592
120781
|
` + transcriptText + `
|
|
120593
120782
|
`;
|
|
120594
|
-
|
|
120595
|
-
await writeFile3(outputPath, output, "utf8");
|
|
120596
|
-
} else {
|
|
120597
|
-
console.log(output);
|
|
120598
|
-
}
|
|
120783
|
+
await outputText(output);
|
|
120599
120784
|
} else {
|
|
120600
120785
|
const output = outputJson ? toJSON(segments, decode) + `
|
|
120601
120786
|
` : timestamps ? formatWithTimestamps(segments, decode) + `
|
|
120602
120787
|
` : toText(segments, decode) + `
|
|
120603
120788
|
`;
|
|
120604
|
-
|
|
120605
|
-
await writeFile3(outputPath, output, "utf8");
|
|
120606
|
-
} else {
|
|
120607
|
-
console.log(output);
|
|
120608
|
-
}
|
|
120789
|
+
await outputText(output);
|
|
120609
120790
|
}
|
|
120791
|
+
exitProcess(0);
|
|
120610
120792
|
} catch (err) {
|
|
120611
120793
|
const message = err instanceof Error ? err.message : String(err);
|
|
120612
120794
|
console.error(`Error: ${message}`);
|
|
120613
|
-
|
|
120795
|
+
exitProcess(1);
|
|
120614
120796
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, test, afterEach } from "bun:test";
|
|
2
|
+
import { $ } from "bun";
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const testConfigPath = "/tmp/prosey-test-config.toml";
|
|
7
|
+
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
try {
|
|
10
|
+
await rm(testConfigPath);
|
|
11
|
+
} catch {}
|
|
12
|
+
delete process.env.PROSEY_CONFIG_PATH;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("config subcommand", () => {
|
|
16
|
+
test("prints config path when EDITOR is not set", async () => {
|
|
17
|
+
delete process.env.EDITOR;
|
|
18
|
+
process.env.PROSEY_CONFIG_PATH = testConfigPath;
|
|
19
|
+
|
|
20
|
+
const { stdout, exitCode } = await $`bin/prosey config`.quiet();
|
|
21
|
+
|
|
22
|
+
expect(exitCode).toBe(0);
|
|
23
|
+
expect(stdout.toString().trim()).toBe(`Config file: ${testConfigPath}`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("creates config file when missing", async () => {
|
|
27
|
+
delete process.env.EDITOR;
|
|
28
|
+
process.env.PROSEY_CONFIG_PATH = testConfigPath;
|
|
29
|
+
|
|
30
|
+
expect(existsSync(testConfigPath)).toBe(false);
|
|
31
|
+
await $`bin/prosey config`.quiet();
|
|
32
|
+
expect(existsSync(testConfigPath)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("does not require a video ID", async () => {
|
|
36
|
+
delete process.env.EDITOR;
|
|
37
|
+
process.env.PROSEY_CONFIG_PATH = testConfigPath;
|
|
38
|
+
|
|
39
|
+
const { exitCode } = await $`bin/prosey config`.quiet();
|
|
40
|
+
expect(exitCode).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("exits with code 0 after spawning editor", async () => {
|
|
44
|
+
process.env.EDITOR = "cat";
|
|
45
|
+
process.env.PROSEY_CONFIG_PATH = testConfigPath;
|
|
46
|
+
|
|
47
|
+
const { exitCode } = await $`bin/prosey config`.quiet();
|
|
48
|
+
expect(exitCode).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { load } from "js-toml";
|
|
7
7
|
|
|
8
8
|
export interface ProseyConfig {
|
|
9
|
+
pager?: string;
|
|
10
|
+
hints?: boolean;
|
|
9
11
|
summarize?: {
|
|
10
12
|
prompt?: string;
|
|
11
13
|
command?: string;
|
|
@@ -15,6 +17,16 @@ export interface ProseyConfig {
|
|
|
15
17
|
const FALLBACK_CONFIG_TOML = `# Default prosey configuration
|
|
16
18
|
# Created automatically on first run. Edit as needed.
|
|
17
19
|
|
|
20
|
+
# Pager command for transcript and summary output.
|
|
21
|
+
# Defaults to "auto": bat -lmd --style plain → glow -p → mdcat -l -p → less
|
|
22
|
+
# Set to a custom command (e.g. "less -R") to override.
|
|
23
|
+
# Can also be set via the PROSEY_PAGER env var (takes precedence).
|
|
24
|
+
pager = "auto"
|
|
25
|
+
|
|
26
|
+
# Show hints for missing tools (e.g. markdown highlighter).
|
|
27
|
+
# Can also be set via PROSEY_HINTS env var (yes, no, 1, 0, true, false).
|
|
28
|
+
hints = true
|
|
29
|
+
|
|
18
30
|
[summarize]
|
|
19
31
|
# Prompt sent to the command via stdin.
|
|
20
32
|
# Customize this to change how transcripts are summarized.
|