atris 3.57.4 → 3.58.5
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +55 -12
- package/atris/skills/x-search/SKILL.md +8 -6
- package/atris/skills/youtube/SKILL.md +37 -8
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +153 -23
- package/bin/atris.js +78 -30
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +7 -7
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +3 -3
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +12 -12
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +90 -82
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +97 -40
- package/commands/worktree.js +1 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +89 -17
- package/commands/xp.js +6 -3
- package/commands/youtube.js +601 -48
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +45 -7
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +0 -3
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/youtube.js
CHANGED
|
@@ -31,6 +31,8 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
31
31
|
output(` ${commandName} search --paid "<query>" [--limit N] [--json]`);
|
|
32
32
|
output(` ${commandName} notes <youtube-url> [youtube-url-or-playlist...] [engine] [--save]`);
|
|
33
33
|
output(` ${commandName} teach <youtube-url> [--section N] [--save] [--recap TEXT] [--skip]`);
|
|
34
|
+
output(` ${commandName} teach owed [--json]`);
|
|
35
|
+
output(` ${commandName} teach next [--json]`);
|
|
34
36
|
output(` ${commandName} unsave <url-or-id>`);
|
|
35
37
|
output(` ${commandName} process <youtube-url> [options]`);
|
|
36
38
|
output(` ${commandName} digest [--days N]`);
|
|
@@ -40,14 +42,14 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
40
42
|
output(` ${commandName} watch tick`);
|
|
41
43
|
output(` ${commandName} <youtube-url> [options]`);
|
|
42
44
|
output('');
|
|
43
|
-
output('search = free local discovery (ytsearch / yt-dlp), returns youtu.be links');
|
|
44
|
-
output('search --paid = 5 credits, watch permalinks + titles from Atris');
|
|
45
|
-
output('notes = free local notes to stdout; ephemeral unless --save');
|
|
46
|
-
output('teach = one chapter from local captions;
|
|
47
|
-
output('rich ephemeral notes/teach print one apply next-step (no files)');
|
|
48
|
-
output('process = 5 credits cloud knowledge (needs a filled Apply)');
|
|
49
|
-
output('digest = one decision page from this week\'s video briefs');
|
|
50
|
-
output('watch = subscribed channels turn into briefs without a human');
|
|
45
|
+
output('search = free local discovery (ytsearch / yt-dlp), returns youtu.be links; rich free search prints one failing check; hands off to teach');
|
|
46
|
+
output('search --paid = 5 credits, watch permalinks + titles from Atris; rich paid search prints one failing check; hands off to teach');
|
|
47
|
+
output('notes = free local notes to stdout; ephemeral unless --save; hands off to teach');
|
|
48
|
+
output('teach = one chapter from local captions; bare teach resumes unpaid checks, then the next chapter after recap or skip');
|
|
49
|
+
output('rich ephemeral notes/teach print one apply next-step and one failing check (no files)');
|
|
50
|
+
output('process = 5 credits cloud knowledge (needs a filled Apply); rich process prints one failing check');
|
|
51
|
+
output('digest = one decision page from this week\'s video briefs; rich digest writes one apply and a failing keep/revert pack');
|
|
52
|
+
output('watch = subscribed channels turn into briefs without a human; add hands off to tick; tick hands off to teach when it briefed');
|
|
51
53
|
output('Process a YouTube video through Atris using timestamped transcript-first analysis.');
|
|
52
54
|
output('Falls back to cloud video processing when local captions are unavailable.');
|
|
53
55
|
output('');
|
|
@@ -58,6 +60,8 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
58
60
|
output(' --section <n> Chapter to teach, 1-based (teach only, default: 1)');
|
|
59
61
|
output(' --recap <text> Unlock the next teach section with the unpaid check');
|
|
60
62
|
output(' --skip Unlock the next teach section without answering');
|
|
63
|
+
output(' owed Print the unpaid teach check (no network)');
|
|
64
|
+
output(' next Next unlocked chapter (no url, no billing)');
|
|
61
65
|
output(' --unsave Delete filed brief, apply stub, and matching notes/teach experiment packs (no paid calls)');
|
|
62
66
|
output(' --query, -q <text> Focus question for the analysis');
|
|
63
67
|
output(' --agent <id> Agent id to store knowledge against');
|
|
@@ -77,6 +81,7 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
77
81
|
output(` ${commandName} notes https://www.youtube.com/watch?v=VIDEO_ID --save`);
|
|
78
82
|
output(` ${commandName} teach "https://www.youtube.com/watch?v=VIDEO_ID"`);
|
|
79
83
|
output(` ${commandName} teach "https://www.youtube.com/watch?v=VIDEO_ID" --section 2`);
|
|
84
|
+
output(` ${commandName} teach next`);
|
|
80
85
|
output(` ${commandName} notes --unsave VIDEO_ID`);
|
|
81
86
|
output(` ${commandName} unsave VIDEO_ID`);
|
|
82
87
|
output(` ${commandName} notes https://www.youtube.com/watch?v=VIDEO_ID https://youtu.be/OTHER_ID`);
|
|
@@ -507,6 +512,17 @@ async function processYoutube(options, deps = {}) {
|
|
|
507
512
|
return result.data;
|
|
508
513
|
}
|
|
509
514
|
|
|
515
|
+
function processAnalysisText(data) {
|
|
516
|
+
return data?.video_analysis || data?.analysis || data?.result || '';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function printProcessLearnerGate(data, { json = false } = {}, output) {
|
|
520
|
+
printLearnerCheckGate(output, notesLessonFromText(processAnalysisText(data)), {
|
|
521
|
+
includeCheck: true,
|
|
522
|
+
json,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
510
526
|
function formatYoutubeResult(data) {
|
|
511
527
|
const lines = [];
|
|
512
528
|
const metadata = data?.metadata || {};
|
|
@@ -526,7 +542,7 @@ function formatYoutubeResult(data) {
|
|
|
526
542
|
const remaining = data.credits_remaining !== undefined ? data.credits_remaining : '?';
|
|
527
543
|
lines.push(`Credits: ${used} used, ${remaining} remaining`);
|
|
528
544
|
}
|
|
529
|
-
const analysis = data
|
|
545
|
+
const analysis = processAnalysisText(data);
|
|
530
546
|
if (analysis) {
|
|
531
547
|
lines.push('');
|
|
532
548
|
lines.push(String(analysis).trim());
|
|
@@ -567,22 +583,24 @@ function parseNotesArgs(argv = []) {
|
|
|
567
583
|
let help = false;
|
|
568
584
|
let save = false;
|
|
569
585
|
let unsave = false;
|
|
586
|
+
let json = false;
|
|
570
587
|
for (const raw of argv) {
|
|
571
588
|
const arg = String(raw);
|
|
572
589
|
if (arg === '--help' || arg === '-h' || arg === 'help') help = true;
|
|
573
590
|
else if (arg === '--save') save = true;
|
|
574
591
|
else if (arg === '--unsave') unsave = true;
|
|
592
|
+
else if (arg === '--json') json = true;
|
|
575
593
|
}
|
|
576
594
|
for (const raw of argv) {
|
|
577
595
|
const arg = String(raw);
|
|
578
596
|
if (arg === '--help' || arg === '-h' || arg === 'help') continue;
|
|
579
|
-
if (arg === '--save' || arg === '--unsave') continue;
|
|
597
|
+
if (arg === '--save' || arg === '--unsave' || arg === '--json') continue;
|
|
580
598
|
if (arg.startsWith('-')) continue;
|
|
581
599
|
if (looksLikeYoutubeUrl(arg)) urls.push(arg);
|
|
582
600
|
else if (unsave && videoIdFromArg(arg)) urls.push(arg);
|
|
583
601
|
else engine = arg;
|
|
584
602
|
}
|
|
585
|
-
return { urls, engine, help, save, unsave };
|
|
603
|
+
return { urls, engine, help, save, unsave, json };
|
|
586
604
|
}
|
|
587
605
|
|
|
588
606
|
function dateStamp(now) {
|
|
@@ -680,7 +698,7 @@ function saveRichNotes(url, deps = {}) {
|
|
|
680
698
|
const workDir = deps.workDir || path.join(process.env.TMPDIR || '/tmp', 'ytnotes');
|
|
681
699
|
const lesson = notesLessonFromText(readNotesText({ url, workDir }));
|
|
682
700
|
if (isThinTeachLesson(lesson)) {
|
|
683
|
-
return { thin: true, brief: null, packRel: null };
|
|
701
|
+
return { thin: true, brief: null, packRel: null, lesson };
|
|
684
702
|
}
|
|
685
703
|
const brief = fileNotesBrief(url, deps);
|
|
686
704
|
const id = videoIdFromUrl(url);
|
|
@@ -691,20 +709,21 @@ function saveRichNotes(url, deps = {}) {
|
|
|
691
709
|
slug: id ? notesExperimentSlug(id) : null,
|
|
692
710
|
applyRel: id ? applySidecarRel(id) : null,
|
|
693
711
|
});
|
|
694
|
-
return { thin: false, brief, packRel };
|
|
712
|
+
return { thin: false, brief, packRel, lesson };
|
|
695
713
|
}
|
|
696
714
|
|
|
697
715
|
function ensureNotesApply({ cwd, url, packRel, now, output } = {}) {
|
|
698
716
|
const id = videoIdFromUrl(url);
|
|
699
717
|
const pack = packRel || (id ? notesExperimentRel(id) : null);
|
|
718
|
+
const slug = pack ? path.basename(pack) : null;
|
|
700
719
|
return applyGate.ensureApply({
|
|
701
720
|
cwd,
|
|
702
721
|
source: url,
|
|
703
722
|
rel: id ? applySidecarRel(id) : null,
|
|
704
723
|
now,
|
|
705
724
|
output,
|
|
706
|
-
incompleteMessage:
|
|
707
|
-
? `next:
|
|
725
|
+
incompleteMessage: slug
|
|
726
|
+
? `next: atris experiments keep ${slug}`
|
|
708
727
|
: APPLY_NEXT_MESSAGE,
|
|
709
728
|
required: false,
|
|
710
729
|
change: pack ? `apply ${pack}` : undefined,
|
|
@@ -815,6 +834,9 @@ function runYoutubeUnsave(args = [], deps = {}) {
|
|
|
815
834
|
const status = unsaveYoutubeNotes(target, deps);
|
|
816
835
|
if (status !== 0) code = status;
|
|
817
836
|
}
|
|
837
|
+
if (code === 0 && parsed.json !== true && deps.json !== true) {
|
|
838
|
+
printWatchSearchNext(output);
|
|
839
|
+
}
|
|
818
840
|
return code;
|
|
819
841
|
}
|
|
820
842
|
|
|
@@ -977,6 +999,104 @@ function appendDigestJournal({ cwd, date, relDigest }) {
|
|
|
977
999
|
fs.writeFileSync(journalPath, `${existing}${prefix}${line}\n`);
|
|
978
1000
|
}
|
|
979
1001
|
|
|
1002
|
+
function digestExperimentSlug(date) {
|
|
1003
|
+
return `digest-${String(date || '').slice(0, 10)}`;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function digestExperimentRel(date) {
|
|
1007
|
+
return `atris/experiments/${digestExperimentSlug(date)}`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function digestApplyRel(date) {
|
|
1011
|
+
return applyGate.applySidecarRel('digest', String(date || '').slice(0, 10));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function ensureDigestApply({ cwd, date, packRel, now, output, source } = {}) {
|
|
1015
|
+
const stamp = String(date || '').slice(0, 10);
|
|
1016
|
+
const pack = packRel || (stamp ? digestExperimentRel(stamp) : null);
|
|
1017
|
+
const slug = pack ? path.basename(pack) : null;
|
|
1018
|
+
return applyGate.ensureApply({
|
|
1019
|
+
cwd,
|
|
1020
|
+
source: source || (stamp ? `digest:${stamp}` : 'digest'),
|
|
1021
|
+
rel: stamp ? digestApplyRel(stamp) : null,
|
|
1022
|
+
now,
|
|
1023
|
+
output,
|
|
1024
|
+
incompleteMessage: slug
|
|
1025
|
+
? `next: atris experiments keep ${slug}`
|
|
1026
|
+
: applyGate.ephemeralApplyMessage('digest'),
|
|
1027
|
+
required: false,
|
|
1028
|
+
change: pack ? `apply ${pack}` : undefined,
|
|
1029
|
+
receipt: pack ? TEACH_KEEP_RULE : undefined,
|
|
1030
|
+
journalLine: pack ? `- [claimable] apply: ${pack}. ${TEACH_KEEP_RULE}` : undefined,
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function saveRichDigest({ cwd, date, lesson, source } = {}) {
|
|
1035
|
+
if (isThinTeachLesson(lesson)) {
|
|
1036
|
+
return { thin: true, packRel: null, lesson };
|
|
1037
|
+
}
|
|
1038
|
+
const packRel = fileTeachExperiment({
|
|
1039
|
+
cwd,
|
|
1040
|
+
lesson,
|
|
1041
|
+
slug: date ? digestExperimentSlug(date) : null,
|
|
1042
|
+
applyRel: date ? digestApplyRel(date) : null,
|
|
1043
|
+
});
|
|
1044
|
+
return { thin: false, packRel, lesson, source };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function watchExperimentSlug(id) {
|
|
1048
|
+
return `watch-${experimentIdToken(id)}`;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function watchExperimentRel(id) {
|
|
1052
|
+
return `atris/experiments/${watchExperimentSlug(id)}`;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function watchApplyRel(id) {
|
|
1056
|
+
return applyGate.applySidecarRel('watch', experimentIdToken(id));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function ensureWatchWiki(cwd) {
|
|
1060
|
+
if (!cwd) return;
|
|
1061
|
+
fs.mkdirSync(path.join(cwd, 'atris', 'wiki'), { recursive: true });
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function ensureWatchApply({ cwd, url, packRel, now, output, source } = {}) {
|
|
1065
|
+
const id = videoIdFromUrl(url);
|
|
1066
|
+
const pack = packRel || (id ? watchExperimentRel(id) : null);
|
|
1067
|
+
const slug = pack ? path.basename(pack) : null;
|
|
1068
|
+
ensureWatchWiki(cwd);
|
|
1069
|
+
return applyGate.ensureApply({
|
|
1070
|
+
cwd,
|
|
1071
|
+
source: source || url || (id ? `watch:${id}` : 'watch'),
|
|
1072
|
+
rel: id ? watchApplyRel(id) : null,
|
|
1073
|
+
now,
|
|
1074
|
+
output,
|
|
1075
|
+
incompleteMessage: slug
|
|
1076
|
+
? `next: atris experiments keep ${slug}`
|
|
1077
|
+
: applyGate.ephemeralApplyMessage('watch'),
|
|
1078
|
+
required: false,
|
|
1079
|
+
change: pack ? `apply ${pack}` : undefined,
|
|
1080
|
+
receipt: pack ? TEACH_KEEP_RULE : undefined,
|
|
1081
|
+
journalLine: pack ? `- [claimable] apply: ${pack}. ${TEACH_KEEP_RULE}` : undefined,
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function saveRichWatch({ cwd, url, lesson } = {}) {
|
|
1086
|
+
if (isThinTeachLesson(lesson)) {
|
|
1087
|
+
return { thin: true, packRel: null, lesson };
|
|
1088
|
+
}
|
|
1089
|
+
const id = videoIdFromUrl(url);
|
|
1090
|
+
const packRel = fileTeachExperiment({
|
|
1091
|
+
cwd,
|
|
1092
|
+
url,
|
|
1093
|
+
lesson,
|
|
1094
|
+
slug: id ? watchExperimentSlug(id) : null,
|
|
1095
|
+
applyRel: id ? watchApplyRel(id) : null,
|
|
1096
|
+
});
|
|
1097
|
+
return { thin: false, packRel, lesson, source: url };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
980
1100
|
function runYoutubeDigest(args = [], deps = {}) {
|
|
981
1101
|
const output = deps.output || ((line = '') => console.log(line));
|
|
982
1102
|
let options;
|
|
@@ -996,6 +1116,7 @@ function runYoutubeDigest(args = [], deps = {}) {
|
|
|
996
1116
|
const briefs = collectVideoBriefs({ cwd, now, days: options.days });
|
|
997
1117
|
if (!briefs.length) {
|
|
998
1118
|
output(`no video briefs in the last ${options.days} days`);
|
|
1119
|
+
printWatchSearchNext(output);
|
|
999
1120
|
return 0;
|
|
1000
1121
|
}
|
|
1001
1122
|
|
|
@@ -1023,7 +1144,32 @@ function runYoutubeDigest(args = [], deps = {}) {
|
|
|
1023
1144
|
fs.writeFileSync(path.join(cwd, relDigest), `${header}\n\n${text}\n`);
|
|
1024
1145
|
appendDigestJournal({ cwd, date, relDigest });
|
|
1025
1146
|
output(`digest filed: ${relDigest} (${briefs.length} briefs)`);
|
|
1026
|
-
|
|
1147
|
+
const lesson = notesLessonFromText(text);
|
|
1148
|
+
if (isThinTeachLesson(lesson)) {
|
|
1149
|
+
printLearnerCheckGate(output, lesson, { includeCheck: true });
|
|
1150
|
+
printWatchTickNext(output);
|
|
1151
|
+
return 0;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const saved = saveRichDigest({ cwd, date, lesson, source: relDigest });
|
|
1155
|
+
const ensureApply = deps.ensureApply || ensureDigestApply;
|
|
1156
|
+
const applyCode = ensureApply({
|
|
1157
|
+
cwd,
|
|
1158
|
+
date,
|
|
1159
|
+
packRel: saved.packRel,
|
|
1160
|
+
now,
|
|
1161
|
+
output,
|
|
1162
|
+
source: relDigest,
|
|
1163
|
+
});
|
|
1164
|
+
if (deps.ensureApply) return applyCode;
|
|
1165
|
+
const baseline = proveSavedLearnerBaseline({
|
|
1166
|
+
cwd,
|
|
1167
|
+
applyRel: digestApplyRel(date),
|
|
1168
|
+
lesson,
|
|
1169
|
+
output,
|
|
1170
|
+
});
|
|
1171
|
+
if (baseline !== 0) return baseline;
|
|
1172
|
+
return applyCode;
|
|
1027
1173
|
}
|
|
1028
1174
|
|
|
1029
1175
|
function watchStatePath(cwd = process.cwd()) {
|
|
@@ -1172,6 +1318,22 @@ function markSeen(state, channel, id, timestamp) {
|
|
|
1172
1318
|
state.seenByChannel[channel][id] = timestamp;
|
|
1173
1319
|
}
|
|
1174
1320
|
|
|
1321
|
+
const WATCH_TICK_NEXT = 'next: atris youtube watch tick';
|
|
1322
|
+
const WATCH_ADD_NEXT = 'next: atris youtube watch add <channel-url-or-@handle>';
|
|
1323
|
+
const WATCH_SEARCH_NEXT = 'next: atris youtube search " "';
|
|
1324
|
+
|
|
1325
|
+
function printWatchTickNext(output) {
|
|
1326
|
+
output(WATCH_TICK_NEXT);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function printWatchAddNext(output) {
|
|
1330
|
+
output(WATCH_ADD_NEXT);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function printWatchSearchNext(output) {
|
|
1334
|
+
output(WATCH_SEARCH_NEXT);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1175
1337
|
function addWatchChannel(channelInput, deps = {}) {
|
|
1176
1338
|
const output = deps.output || ((line = '') => console.log(line));
|
|
1177
1339
|
if (!channelInput) {
|
|
@@ -1191,6 +1353,7 @@ function addWatchChannel(channelInput, deps = {}) {
|
|
|
1191
1353
|
const state = loadWatchState(statePath);
|
|
1192
1354
|
if (state.channels.some((row) => row.channel === channel)) {
|
|
1193
1355
|
output(`already watching ${channel}`);
|
|
1356
|
+
printWatchTickNext(output);
|
|
1194
1357
|
return 0;
|
|
1195
1358
|
}
|
|
1196
1359
|
|
|
@@ -1200,6 +1363,7 @@ function addWatchChannel(channelInput, deps = {}) {
|
|
|
1200
1363
|
});
|
|
1201
1364
|
saveWatchState(statePath, state);
|
|
1202
1365
|
output(`watching ${channel}`);
|
|
1366
|
+
printWatchTickNext(output);
|
|
1203
1367
|
return 0;
|
|
1204
1368
|
}
|
|
1205
1369
|
|
|
@@ -1208,6 +1372,7 @@ function listWatchChannels(deps = {}) {
|
|
|
1208
1372
|
const state = loadWatchState(resolveWatchStatePath(deps));
|
|
1209
1373
|
if (!state.channels.length) {
|
|
1210
1374
|
output('no channels watched');
|
|
1375
|
+
printWatchAddNext(output);
|
|
1211
1376
|
return 0;
|
|
1212
1377
|
}
|
|
1213
1378
|
|
|
@@ -1215,6 +1380,7 @@ function listWatchChannels(deps = {}) {
|
|
|
1215
1380
|
const count = Object.keys(channelSeenMap(state, row.channel)).length;
|
|
1216
1381
|
output(`${index + 1}. ${row.channel} (${count} seen)`);
|
|
1217
1382
|
});
|
|
1383
|
+
printWatchTickNext(output);
|
|
1218
1384
|
return 0;
|
|
1219
1385
|
}
|
|
1220
1386
|
|
|
@@ -1231,6 +1397,11 @@ function removeWatchChannel(rawNumber, deps = {}) {
|
|
|
1231
1397
|
const [removed] = state.channels.splice(index - 1, 1);
|
|
1232
1398
|
saveWatchState(statePath, state);
|
|
1233
1399
|
output(`removed ${removed.channel}`);
|
|
1400
|
+
if (!state.channels.length) {
|
|
1401
|
+
printWatchAddNext(output);
|
|
1402
|
+
} else {
|
|
1403
|
+
printWatchTickNext(output);
|
|
1404
|
+
}
|
|
1234
1405
|
return 0;
|
|
1235
1406
|
}
|
|
1236
1407
|
|
|
@@ -1248,6 +1419,7 @@ async function tickWatch(deps = {}) {
|
|
|
1248
1419
|
const state = loadWatchState(statePath);
|
|
1249
1420
|
let totalNew = 0;
|
|
1250
1421
|
let totalBriefed = 0;
|
|
1422
|
+
let firstBriefedUrl = null;
|
|
1251
1423
|
|
|
1252
1424
|
for (const row of state.channels) {
|
|
1253
1425
|
const videosUrl = channelVideosUrl(row.channel);
|
|
@@ -1294,6 +1466,7 @@ async function tickWatch(deps = {}) {
|
|
|
1294
1466
|
}
|
|
1295
1467
|
markSeen(state, row.channel, video.id, timestamp);
|
|
1296
1468
|
briefed += 1;
|
|
1469
|
+
if (!firstBriefedUrl) firstBriefedUrl = url;
|
|
1297
1470
|
}
|
|
1298
1471
|
|
|
1299
1472
|
if (!isFresh) {
|
|
@@ -1311,6 +1484,40 @@ async function tickWatch(deps = {}) {
|
|
|
1311
1484
|
|
|
1312
1485
|
output(`total: ${totalNew} new, ${totalBriefed} briefed`);
|
|
1313
1486
|
saveWatchState(statePath, state);
|
|
1487
|
+
if (totalBriefed > 0) {
|
|
1488
|
+
if (firstBriefedUrl) {
|
|
1489
|
+
const lesson = notesLessonFromText(readNotesText({ url: firstBriefedUrl, workDir }));
|
|
1490
|
+
if (isThinTeachLesson(lesson)) {
|
|
1491
|
+
printLearnerCheckGate(output, lesson, { includeCheck: true });
|
|
1492
|
+
printYoutubeTeachNext(firstBriefedUrl, {}, output);
|
|
1493
|
+
return 0;
|
|
1494
|
+
}
|
|
1495
|
+
const saved = saveRichWatch({ cwd, url: firstBriefedUrl, lesson });
|
|
1496
|
+
const ensureApply = deps.ensureApply || ensureWatchApply;
|
|
1497
|
+
const applyCode = ensureApply({
|
|
1498
|
+
cwd,
|
|
1499
|
+
url: firstBriefedUrl,
|
|
1500
|
+
packRel: saved.packRel,
|
|
1501
|
+
now,
|
|
1502
|
+
output,
|
|
1503
|
+
source: firstBriefedUrl,
|
|
1504
|
+
});
|
|
1505
|
+
if (deps.ensureApply) return applyCode;
|
|
1506
|
+
const id = videoIdFromUrl(firstBriefedUrl);
|
|
1507
|
+
const baseline = proveSavedLearnerBaseline({
|
|
1508
|
+
cwd,
|
|
1509
|
+
applyRel: id ? watchApplyRel(id) : null,
|
|
1510
|
+
lesson,
|
|
1511
|
+
output,
|
|
1512
|
+
});
|
|
1513
|
+
if (baseline !== 0) return baseline;
|
|
1514
|
+
return applyCode;
|
|
1515
|
+
}
|
|
1516
|
+
printYoutubeTeachNext(firstBriefedUrl, {}, output);
|
|
1517
|
+
return 0;
|
|
1518
|
+
}
|
|
1519
|
+
if (!state.channels.length) printWatchAddNext(output);
|
|
1520
|
+
else printWatchSearchNext(output);
|
|
1314
1521
|
return 0;
|
|
1315
1522
|
}
|
|
1316
1523
|
|
|
@@ -1447,6 +1654,7 @@ function runOneNotesItem(item, engine, deps = {}) {
|
|
|
1447
1654
|
status = 1;
|
|
1448
1655
|
}
|
|
1449
1656
|
let brief = null;
|
|
1657
|
+
let lesson = null;
|
|
1450
1658
|
let ok = status === 0;
|
|
1451
1659
|
if (ok && deps.save) {
|
|
1452
1660
|
const saved = saveRichNotes(item.url, deps);
|
|
@@ -1455,6 +1663,7 @@ function runOneNotesItem(item, engine, deps = {}) {
|
|
|
1455
1663
|
ok = false;
|
|
1456
1664
|
} else {
|
|
1457
1665
|
brief = saved.brief;
|
|
1666
|
+
lesson = saved.lesson;
|
|
1458
1667
|
const ensureApply = deps.ensureApply || ensureNotesApply;
|
|
1459
1668
|
try {
|
|
1460
1669
|
ensureApply({
|
|
@@ -1471,7 +1680,7 @@ function runOneNotesItem(item, engine, deps = {}) {
|
|
|
1471
1680
|
}
|
|
1472
1681
|
const seconds = Math.max(0, Math.round((readNowMs(deps) - started) / 1000));
|
|
1473
1682
|
output(`${label} ${seconds}s ${ok ? (brief || 'ok') : 'FAILED'}`);
|
|
1474
|
-
return { url: item.url, id: item.id, seconds, ok, brief };
|
|
1683
|
+
return { url: item.url, id: item.id, seconds, ok, brief, lesson };
|
|
1475
1684
|
}
|
|
1476
1685
|
|
|
1477
1686
|
function formatNotesSummary(rows = []) {
|
|
@@ -1483,7 +1692,14 @@ function formatNotesSummary(rows = []) {
|
|
|
1483
1692
|
return lines.join('\n');
|
|
1484
1693
|
}
|
|
1485
1694
|
|
|
1486
|
-
function
|
|
1695
|
+
function printEphemeralNotesLearnerGate(url, { json = false, workDir } = {}, output) {
|
|
1696
|
+
const dir = workDir || path.join(process.env.TMPDIR || '/tmp', 'ytnotes');
|
|
1697
|
+
const lesson = notesLessonFromText(readNotesText({ url, workDir: dir }));
|
|
1698
|
+
if (!isThinTeachLesson(lesson)) applyGate.hintEphemeralApply(output, 'notes');
|
|
1699
|
+
printLearnerCheckGate(output, lesson, { includeCheck: true, json });
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function runYoutubeNotesBatch({ urls, engine, save, json } = {}, deps = {}) {
|
|
1487
1703
|
deps = { ...deps, save: save === true || deps.save === true };
|
|
1488
1704
|
const output = deps.output || ((line = '') => console.error(line));
|
|
1489
1705
|
const items = expandNotesTargets(urls || [], deps);
|
|
@@ -1496,7 +1712,30 @@ function runYoutubeNotesBatch({ urls, engine, save } = {}, deps = {}) {
|
|
|
1496
1712
|
output(formatNotesSummary(rows));
|
|
1497
1713
|
}
|
|
1498
1714
|
if (!rows.length) return 2;
|
|
1499
|
-
|
|
1715
|
+
const firstOk = rows.find((row) => row.ok);
|
|
1716
|
+
const asJson = json === true || deps.json === true;
|
|
1717
|
+
if (firstOk && !deps.save) {
|
|
1718
|
+
if (!asJson) printEphemeralNotesLearnerGate(firstOk.url, { workDir: deps.workDir }, output);
|
|
1719
|
+
printYoutubeTeachNext(firstOk.url, { json: asJson }, output);
|
|
1720
|
+
}
|
|
1721
|
+
if (firstOk && deps.save) {
|
|
1722
|
+
if (deps.ensureApply) return 0;
|
|
1723
|
+
const id = firstOk.id || videoIdFromUrl(firstOk.url);
|
|
1724
|
+
const workDir = deps.workDir || path.join(process.env.TMPDIR || '/tmp', 'ytnotes');
|
|
1725
|
+
const lesson = firstOk.lesson || notesLessonFromText(readNotesText({
|
|
1726
|
+
url: firstOk.url,
|
|
1727
|
+
workDir,
|
|
1728
|
+
}));
|
|
1729
|
+
const baseline = proveSavedLearnerBaseline({
|
|
1730
|
+
cwd: deps.cwd || process.cwd(),
|
|
1731
|
+
applyRel: id ? applySidecarRel(id) : null,
|
|
1732
|
+
lesson,
|
|
1733
|
+
output,
|
|
1734
|
+
json: asJson,
|
|
1735
|
+
});
|
|
1736
|
+
if (baseline !== 0) return baseline;
|
|
1737
|
+
}
|
|
1738
|
+
return firstOk ? 0 : 2;
|
|
1500
1739
|
}
|
|
1501
1740
|
|
|
1502
1741
|
function runSingleYoutubeNotes(url, engine, deps = {}) {
|
|
@@ -1510,24 +1749,37 @@ function runSingleYoutubeNotes(url, engine, deps = {}) {
|
|
|
1510
1749
|
if (status !== 0) return status;
|
|
1511
1750
|
const output = deps.output || ((line = '') => console.error(line));
|
|
1512
1751
|
if (!deps.save) {
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1752
|
+
const json = deps.json === true;
|
|
1753
|
+
printEphemeralNotesLearnerGate(url, { json, workDir: deps.workDir }, output);
|
|
1754
|
+
printYoutubeTeachNext(url, { json }, output);
|
|
1516
1755
|
return 0;
|
|
1517
1756
|
}
|
|
1518
1757
|
const saved = saveRichNotes(url, deps);
|
|
1519
1758
|
if (saved.thin) {
|
|
1520
1759
|
output(TEACH_THIN_REFUSE);
|
|
1760
|
+
printYoutubeTeachNext(url, { json: deps.json }, output);
|
|
1521
1761
|
return 2;
|
|
1522
1762
|
}
|
|
1523
1763
|
const ensureApply = deps.ensureApply || ensureNotesApply;
|
|
1524
|
-
|
|
1525
|
-
|
|
1764
|
+
const cwd = deps.cwd || process.cwd();
|
|
1765
|
+
const applyCode = ensureApply({
|
|
1766
|
+
cwd,
|
|
1526
1767
|
url,
|
|
1527
1768
|
packRel: saved.packRel,
|
|
1528
1769
|
now: deps.now,
|
|
1529
1770
|
output: deps.output,
|
|
1530
1771
|
});
|
|
1772
|
+
if (deps.ensureApply) return applyCode;
|
|
1773
|
+
const id = videoIdFromUrl(url);
|
|
1774
|
+
const baseline = proveSavedLearnerBaseline({
|
|
1775
|
+
cwd,
|
|
1776
|
+
applyRel: id ? applySidecarRel(id) : null,
|
|
1777
|
+
lesson: saved.lesson,
|
|
1778
|
+
output,
|
|
1779
|
+
json: deps.json === true,
|
|
1780
|
+
});
|
|
1781
|
+
if (baseline !== 0) return baseline;
|
|
1782
|
+
return applyCode;
|
|
1531
1783
|
}
|
|
1532
1784
|
|
|
1533
1785
|
function runYoutubeNotes(args = [], deps = {}) {
|
|
@@ -1545,7 +1797,7 @@ function runYoutubeNotes(args = [], deps = {}) {
|
|
|
1545
1797
|
output(YTNOTES_HINT);
|
|
1546
1798
|
return 2;
|
|
1547
1799
|
}
|
|
1548
|
-
const nextDeps = { ...deps, save: parsed.save };
|
|
1800
|
+
const nextDeps = { ...deps, save: parsed.save, json: parsed.json };
|
|
1549
1801
|
if (parsed.urls.length === 1 && !isPlaylistUrl(parsed.urls[0])) {
|
|
1550
1802
|
return runSingleYoutubeNotes(parsed.urls[0], parsed.engine, nextDeps);
|
|
1551
1803
|
}
|
|
@@ -1575,10 +1827,13 @@ function showYoutubeSearchHelp(output = console.log, commandName = 'atris youtub
|
|
|
1575
1827
|
output('');
|
|
1576
1828
|
output('Free local discovery. Uses ytsearch on PATH when present, else the');
|
|
1577
1829
|
output('bundled scripts/det/ytsearch, else yt-dlp ytsearchN with the same print contract.');
|
|
1578
|
-
output('Does not bill credits.
|
|
1830
|
+
output('Does not bill credits. A hit prints one next: atris youtube teach <first-url>.');
|
|
1831
|
+
output('A rich hit prints one failing check (score 0). A thin hit prints check: fill this.');
|
|
1579
1832
|
output('');
|
|
1580
1833
|
output(`--paid buys watch permalinks from Atris (${PAID_SEARCH_COST_HINT}).`);
|
|
1581
1834
|
output('Requires login. Same auth path as atris youtube process.');
|
|
1835
|
+
output('A hit also prints one next: atris youtube teach <first-url>.');
|
|
1836
|
+
output('A rich hit prints one failing check (score 0). A thin hit prints check: fill this.');
|
|
1582
1837
|
output('Empty or failed paid search refunds the credits.');
|
|
1583
1838
|
output('');
|
|
1584
1839
|
output('Options:');
|
|
@@ -1709,6 +1964,17 @@ function resolveSearchCachePath(deps = {}) {
|
|
|
1709
1964
|
return path.join(homeDir, '.atris', LOCAL_SEARCH_CACHE_FILE);
|
|
1710
1965
|
}
|
|
1711
1966
|
|
|
1967
|
+
function printYoutubeTeachNext(url, options, output) {
|
|
1968
|
+
if (options && options.json) return;
|
|
1969
|
+
if (!url) return;
|
|
1970
|
+
output(`next: atris youtube teach ${quoteYoutubeUrl(url)}`);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function printSearchTeachNext(rows, options, output) {
|
|
1974
|
+
const firstUrl = Array.isArray(rows) && rows[0] && rows[0].url;
|
|
1975
|
+
printYoutubeTeachNext(firstUrl, options, output);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1712
1978
|
function printSearchRows(rows, options, output) {
|
|
1713
1979
|
if (options.json) {
|
|
1714
1980
|
output(JSON.stringify(rows, null, 2));
|
|
@@ -1908,12 +2174,29 @@ async function runPaidYoutubeSearch(options, deps = {}) {
|
|
|
1908
2174
|
const rendered = formatPaidSearchResults(data);
|
|
1909
2175
|
if (!videos.length) {
|
|
1910
2176
|
output(rendered ? `no videos found\n${rendered}` : 'no videos found');
|
|
2177
|
+
printWatchTickNext(output);
|
|
1911
2178
|
return 2;
|
|
1912
2179
|
}
|
|
1913
2180
|
output(rendered);
|
|
2181
|
+
printSearchLearnerGate(videos, options, output);
|
|
2182
|
+
printSearchTeachNext(videos, options, output);
|
|
1914
2183
|
return 0;
|
|
1915
2184
|
}
|
|
1916
2185
|
|
|
2186
|
+
function searchLessonText(rows) {
|
|
2187
|
+
return (Array.isArray(rows) ? rows : [])
|
|
2188
|
+
.map((row) => String(row && row.title ? row.title : '').trim())
|
|
2189
|
+
.filter(Boolean)
|
|
2190
|
+
.join('\n');
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function printSearchLearnerGate(rows, options, output) {
|
|
2194
|
+
printLearnerCheckGate(output, notesLessonFromText(searchLessonText(rows)), {
|
|
2195
|
+
includeCheck: true,
|
|
2196
|
+
json: Boolean(options && options.json),
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
|
|
1917
2200
|
function commandOnPath(name, deps = {}) {
|
|
1918
2201
|
const spawn = deps.spawnSync || spawnSync;
|
|
1919
2202
|
const result = spawn('sh', ['-c', `command -v ${shellSingleQuote(name)}`], {
|
|
@@ -2049,6 +2332,7 @@ async function runYoutubeSearch(args = [], deps = {}) {
|
|
|
2049
2332
|
if (cached) {
|
|
2050
2333
|
const rows = cached.rows.slice(0, options.limit);
|
|
2051
2334
|
printSearchRows(rows, options, output);
|
|
2335
|
+
printSearchTeachNext(rows, options, output);
|
|
2052
2336
|
output(LOCAL_SEARCH_CACHE_NOTE);
|
|
2053
2337
|
return 0;
|
|
2054
2338
|
}
|
|
@@ -2067,19 +2351,26 @@ async function runYoutubeSearch(args = [], deps = {}) {
|
|
|
2067
2351
|
const rows = parseSearchStdout(stdout);
|
|
2068
2352
|
if (!rows.length) {
|
|
2069
2353
|
output('no videos found');
|
|
2354
|
+
if (!options.json) printWatchTickNext(output);
|
|
2070
2355
|
return 2;
|
|
2071
2356
|
}
|
|
2072
2357
|
|
|
2073
2358
|
writeLocalSearchCache(options.query, rows, deps);
|
|
2074
2359
|
printSearchRows(rows, options, output);
|
|
2360
|
+
printSearchLearnerGate(rows, options, output);
|
|
2361
|
+
printSearchTeachNext(rows, options, output);
|
|
2075
2362
|
return 0;
|
|
2076
2363
|
}
|
|
2077
2364
|
|
|
2078
|
-
const YTTEACH_USAGE = 'usage: atris youtube teach <youtube-url> [--section N] [--save] [--recap TEXT] [--skip]';
|
|
2365
|
+
const YTTEACH_USAGE = 'usage: atris youtube teach <youtube-url> [--section N] [--save] [--recap TEXT] [--skip] | owed | next';
|
|
2079
2366
|
const TEACH_PAID_REFUSE = 'teach is free local captions. drop --paid.';
|
|
2080
2367
|
const TEACH_THIN_REFUSE = 'thin: no number or named mechanism. no brief.';
|
|
2081
2368
|
const TEACH_OWED_FILE = 'youtube-teach-owed.json';
|
|
2082
2369
|
const TEACH_RECAP_MISSING = '--recap needs the unpaid check';
|
|
2370
|
+
const TEACH_RESUME_START = 'atris youtube teach <url>';
|
|
2371
|
+
const TEACH_RESUME_NEXT = 'next: atris youtube teach recap TEXT or atris youtube teach skip';
|
|
2372
|
+
const TEACH_WATCH_TICK_NEXT = WATCH_TICK_NEXT;
|
|
2373
|
+
const TEACH_CONTINUE_NEXT = 'next: atris youtube teach next';
|
|
2083
2374
|
const TEACH_APPLY_NEXT_MESSAGE = APPLY_NEXT_MESSAGE;
|
|
2084
2375
|
const TEACH_KEEP_RULE = 'keep only if measure.py moves 0→1. scores 1 only when the fixture contains the check tokens.';
|
|
2085
2376
|
const MECHANISM_STOP = new Set([
|
|
@@ -2108,12 +2399,16 @@ function parseTeachArgs(argv = []) {
|
|
|
2108
2399
|
save: false,
|
|
2109
2400
|
json: false,
|
|
2110
2401
|
skip: false,
|
|
2402
|
+
owed: false,
|
|
2403
|
+
resume: false,
|
|
2404
|
+
next: false,
|
|
2111
2405
|
recap: null,
|
|
2112
2406
|
url: null,
|
|
2113
2407
|
section: 1,
|
|
2114
2408
|
};
|
|
2409
|
+
let sectionSet = false;
|
|
2115
2410
|
|
|
2116
|
-
if (args.length
|
|
2411
|
+
if (args.length > 0 && ['help', '--help', '-h'].includes(args[0])) {
|
|
2117
2412
|
options.help = true;
|
|
2118
2413
|
return options;
|
|
2119
2414
|
}
|
|
@@ -2128,6 +2423,8 @@ function parseTeachArgs(argv = []) {
|
|
|
2128
2423
|
options.json = true;
|
|
2129
2424
|
} else if (arg === '--skip') {
|
|
2130
2425
|
options.skip = true;
|
|
2426
|
+
} else if (arg === '--owed') {
|
|
2427
|
+
options.owed = true;
|
|
2131
2428
|
} else if (arg === '--paid') {
|
|
2132
2429
|
throw new Error(TEACH_PAID_REFUSE);
|
|
2133
2430
|
} else if (arg === '--recap') {
|
|
@@ -2148,6 +2445,7 @@ function parseTeachArgs(argv = []) {
|
|
|
2148
2445
|
throw new Error('--section must be a positive integer');
|
|
2149
2446
|
}
|
|
2150
2447
|
options.section = value;
|
|
2448
|
+
sectionSet = true;
|
|
2151
2449
|
i += 1;
|
|
2152
2450
|
} else if (arg.startsWith('--section=')) {
|
|
2153
2451
|
const value = Number.parseInt(arg.slice('--section='.length), 10);
|
|
@@ -2155,6 +2453,7 @@ function parseTeachArgs(argv = []) {
|
|
|
2155
2453
|
throw new Error('--section must be a positive integer');
|
|
2156
2454
|
}
|
|
2157
2455
|
options.section = value;
|
|
2456
|
+
sectionSet = true;
|
|
2158
2457
|
} else if (arg.startsWith('-')) {
|
|
2159
2458
|
throw new Error(`Unknown option: ${arg}`);
|
|
2160
2459
|
} else if (arg === 'recap' && options.recap == null && !options.url) {
|
|
@@ -2169,6 +2468,10 @@ function parseTeachArgs(argv = []) {
|
|
|
2169
2468
|
if (!options.recap) throw new Error(TEACH_RECAP_MISSING);
|
|
2170
2469
|
} else if (arg === 'skip' && !options.url) {
|
|
2171
2470
|
options.skip = true;
|
|
2471
|
+
} else if (arg === 'owed' && !options.url) {
|
|
2472
|
+
options.owed = true;
|
|
2473
|
+
} else if (arg === 'next' && !options.url) {
|
|
2474
|
+
options.next = true;
|
|
2172
2475
|
} else if (!options.url && looksLikeYoutubeUrl(arg)) {
|
|
2173
2476
|
options.url = arg;
|
|
2174
2477
|
} else {
|
|
@@ -2177,7 +2480,12 @@ function parseTeachArgs(argv = []) {
|
|
|
2177
2480
|
}
|
|
2178
2481
|
|
|
2179
2482
|
if (options.help) return options;
|
|
2180
|
-
if (!options.url && options.recap == null && !options.skip) {
|
|
2483
|
+
if (!options.url && options.recap == null && !options.skip && !options.owed && !options.next) {
|
|
2484
|
+
if (!sectionSet && !options.save) {
|
|
2485
|
+
options.owed = true;
|
|
2486
|
+
options.resume = true;
|
|
2487
|
+
return options;
|
|
2488
|
+
}
|
|
2181
2489
|
throw new Error('Missing YouTube URL. Run "atris youtube teach --help".');
|
|
2182
2490
|
}
|
|
2183
2491
|
return options;
|
|
@@ -2297,7 +2605,13 @@ function extractTeachMechanisms(text) {
|
|
|
2297
2605
|
return found.slice(0, 8);
|
|
2298
2606
|
}
|
|
2299
2607
|
|
|
2300
|
-
|
|
2608
|
+
const LEARNER_CHECK_FILL = 'fill this';
|
|
2609
|
+
const LEARNER_SCORE_ZERO = 'score: 0';
|
|
2610
|
+
const LEARNER_CHECK_INCOMPLETE = 'incomplete: no measurable check. check: fill this';
|
|
2611
|
+
const LEARNER_BASELINE_INCOMPLETE = 'incomplete: check already passes. refuse invented success.';
|
|
2612
|
+
const LEARNER_APPLY_MISSING = 'incomplete: apply missing. check cannot score.';
|
|
2613
|
+
|
|
2614
|
+
function oneTeachCheck(mechanisms, numbers) {
|
|
2301
2615
|
if (mechanisms[0]) {
|
|
2302
2616
|
const name = mechanisms[0];
|
|
2303
2617
|
const named = new RegExp(`\\b(?:${MECHANISM_HEADS})\\b`, 'i').test(name);
|
|
@@ -2305,7 +2619,63 @@ function oneTeachCheck(mechanisms, numbers, title) {
|
|
|
2305
2619
|
return `what is ${name}?`;
|
|
2306
2620
|
}
|
|
2307
2621
|
if (numbers[0]) return `what does ${numbers[0]} measure in this chapter?`;
|
|
2308
|
-
return
|
|
2622
|
+
return LEARNER_CHECK_FILL;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
function learnerCheckFromLesson(lesson = {}) {
|
|
2626
|
+
const mechanisms = Array.isArray(lesson.mechanisms) ? lesson.mechanisms : [];
|
|
2627
|
+
const numbers = Array.isArray(lesson.numbers) ? lesson.numbers : [];
|
|
2628
|
+
const inferred = mechanisms.length > 0 || numbers.length > 0;
|
|
2629
|
+
return {
|
|
2630
|
+
inferred,
|
|
2631
|
+
line: inferred ? oneTeachCheck(mechanisms, numbers) : LEARNER_CHECK_FILL,
|
|
2632
|
+
needles: teachCheckNeedles({ mechanisms, numbers }),
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
function scoreLearnerNeedles(text, needles) {
|
|
2637
|
+
const list = Array.isArray(needles) ? needles : [];
|
|
2638
|
+
if (!list.length) return 0;
|
|
2639
|
+
const blob = String(text || '').toLowerCase();
|
|
2640
|
+
return list.every((needle) => blob.includes(String(needle || '').toLowerCase())) ? 1 : 0;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
function printLearnerCheckGate(output, lesson, { includeCheck = false, json = false } = {}) {
|
|
2644
|
+
if (json || typeof output !== 'function') return;
|
|
2645
|
+
const check = learnerCheckFromLesson(lesson);
|
|
2646
|
+
if (includeCheck) output(`check: ${check.line}`);
|
|
2647
|
+
if (check.inferred) output(LEARNER_SCORE_ZERO);
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
function proveSavedLearnerBaseline({ cwd, applyRel, lesson, output, json } = {}) {
|
|
2651
|
+
const print = typeof output === 'function' ? output : () => {};
|
|
2652
|
+
const check = learnerCheckFromLesson(lesson);
|
|
2653
|
+
if (!check.inferred) {
|
|
2654
|
+
if (!json) print(LEARNER_CHECK_INCOMPLETE);
|
|
2655
|
+
return 2;
|
|
2656
|
+
}
|
|
2657
|
+
if (!cwd || !applyRel) {
|
|
2658
|
+
if (!json) print(LEARNER_APPLY_MISSING);
|
|
2659
|
+
return 2;
|
|
2660
|
+
}
|
|
2661
|
+
const abs = path.join(cwd, applyRel);
|
|
2662
|
+
let text = '';
|
|
2663
|
+
try {
|
|
2664
|
+
if (!fs.existsSync(abs)) {
|
|
2665
|
+
if (!json) print(LEARNER_APPLY_MISSING);
|
|
2666
|
+
return 2;
|
|
2667
|
+
}
|
|
2668
|
+
text = fs.readFileSync(abs, 'utf8');
|
|
2669
|
+
} catch {
|
|
2670
|
+
if (!json) print(LEARNER_APPLY_MISSING);
|
|
2671
|
+
return 2;
|
|
2672
|
+
}
|
|
2673
|
+
if (scoreLearnerNeedles(text, check.needles) !== 0) {
|
|
2674
|
+
if (!json) print(LEARNER_BASELINE_INCOMPLETE);
|
|
2675
|
+
return 2;
|
|
2676
|
+
}
|
|
2677
|
+
if (!json) print(LEARNER_SCORE_ZERO);
|
|
2678
|
+
return 0;
|
|
2309
2679
|
}
|
|
2310
2680
|
|
|
2311
2681
|
function quoteYoutubeUrl(url) {
|
|
@@ -2336,10 +2706,7 @@ function teachLessonFromCues({ url, section, chapters, chapter, cues, title } =
|
|
|
2336
2706
|
lines.push(oneTeachCheck(mechanisms, numbers, heading));
|
|
2337
2707
|
if (section < total) {
|
|
2338
2708
|
lines.push('');
|
|
2339
|
-
lines.push(
|
|
2340
|
-
} else {
|
|
2341
|
-
lines.push('');
|
|
2342
|
-
lines.push('next: last section');
|
|
2709
|
+
lines.push(TEACH_RESUME_NEXT);
|
|
2343
2710
|
}
|
|
2344
2711
|
return { text: lines.join('\n'), numbers, mechanisms };
|
|
2345
2712
|
}
|
|
@@ -2429,32 +2796,136 @@ function writeTeachOwedStore(deps, store) {
|
|
|
2429
2796
|
}
|
|
2430
2797
|
}
|
|
2431
2798
|
|
|
2432
|
-
function rememberTeachOwed(deps, { url, section, lesson } = {}) {
|
|
2799
|
+
function rememberTeachOwed(deps, { url, section, lesson, total } = {}) {
|
|
2433
2800
|
const id = videoIdFromUrl(url);
|
|
2434
2801
|
if (!id) return;
|
|
2435
2802
|
const store = readTeachOwedStore(deps);
|
|
2436
|
-
|
|
2803
|
+
const row = {
|
|
2437
2804
|
section: Number(section) || 1,
|
|
2438
2805
|
check: teachCheckLine(lesson),
|
|
2439
2806
|
tokens: teachRecapTokens(lesson),
|
|
2807
|
+
url: url || undefined,
|
|
2440
2808
|
};
|
|
2809
|
+
const totalChapters = Number(total);
|
|
2810
|
+
if (Number.isInteger(totalChapters) && totalChapters > 0) row.total = totalChapters;
|
|
2811
|
+
store[id] = row;
|
|
2441
2812
|
writeTeachOwedStore(deps, store);
|
|
2442
2813
|
}
|
|
2443
2814
|
|
|
2815
|
+
function writeTeachContinue(store, id, entry) {
|
|
2816
|
+
const nextSection = (Number(entry && entry.section) || 1) + 1;
|
|
2817
|
+
const total = Number(entry && entry.total);
|
|
2818
|
+
if (Number.isInteger(total) && total > 0 && nextSection > total) {
|
|
2819
|
+
delete store[id];
|
|
2820
|
+
return null;
|
|
2821
|
+
}
|
|
2822
|
+
const cursor = {
|
|
2823
|
+
section: nextSection,
|
|
2824
|
+
url: (entry && entry.url) || undefined,
|
|
2825
|
+
};
|
|
2826
|
+
if (Number.isInteger(total) && total > 0) cursor.total = total;
|
|
2827
|
+
store[id] = cursor;
|
|
2828
|
+
return cursor;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
function lastTeachContinueEntry(store) {
|
|
2832
|
+
const ids = Object.keys(store || {});
|
|
2833
|
+
for (let i = ids.length - 1; i >= 0; i -= 1) {
|
|
2834
|
+
const entry = store[ids[i]];
|
|
2835
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
2836
|
+
if (String(entry.check || '').trim()) continue;
|
|
2837
|
+
const url = String(entry.url || '').trim();
|
|
2838
|
+
if (!url) continue;
|
|
2839
|
+
return {
|
|
2840
|
+
id: ids[i],
|
|
2841
|
+
url,
|
|
2842
|
+
section: Number(entry.section) || 1,
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
return null;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
function lastTeachUnpaidEntry(store) {
|
|
2849
|
+
const ids = Object.keys(store || {});
|
|
2850
|
+
for (let i = ids.length - 1; i >= 0; i -= 1) {
|
|
2851
|
+
const entry = store[ids[i]];
|
|
2852
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
2853
|
+
const check = String(entry.check || '').trim();
|
|
2854
|
+
if (!check) continue;
|
|
2855
|
+
return {
|
|
2856
|
+
id: ids[i],
|
|
2857
|
+
check,
|
|
2858
|
+
url: entry.url || null,
|
|
2859
|
+
section: Number(entry.section) || 1,
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
return null;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2444
2865
|
function previousTeachUnlocked(owed, section) {
|
|
2445
2866
|
if (!owed) return true;
|
|
2446
2867
|
return Number(owed.section) >= Number(section);
|
|
2447
2868
|
}
|
|
2448
2869
|
|
|
2870
|
+
function printTeachUnlockNext(parsed, entry, output) {
|
|
2871
|
+
if (!parsed || parsed.json) return;
|
|
2872
|
+
const url = parsed.url || (entry && entry.url) || '';
|
|
2873
|
+
if (!url) return;
|
|
2874
|
+
output(TEACH_CONTINUE_NEXT);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
function printTeachWatchTickNext(parsed, lesson, total, output) {
|
|
2878
|
+
if (!parsed || parsed.json) return;
|
|
2879
|
+
if (parsed.save && !isThinTeachLesson(lesson)) return;
|
|
2880
|
+
const section = Number(parsed.section) || 0;
|
|
2881
|
+
if (section < Number(total)) return;
|
|
2882
|
+
printWatchTickNext(output);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
function listTeachOwedEntries(store) {
|
|
2886
|
+
return Object.keys(store || {}).map((id) => {
|
|
2887
|
+
const entry = store[id] && typeof store[id] === 'object' ? store[id] : {};
|
|
2888
|
+
return {
|
|
2889
|
+
id,
|
|
2890
|
+
url: entry.url || null,
|
|
2891
|
+
section: Number(entry.section) || 1,
|
|
2892
|
+
check: String(entry.check || '').trim(),
|
|
2893
|
+
};
|
|
2894
|
+
}).filter((row) => row.check);
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
function printTeachOwed(parsed, deps, output) {
|
|
2898
|
+
const rows = listTeachOwedEntries(readTeachOwedStore(deps));
|
|
2899
|
+
if (parsed.json) {
|
|
2900
|
+
output(JSON.stringify(rows));
|
|
2901
|
+
return 0;
|
|
2902
|
+
}
|
|
2903
|
+
if (!rows.length) {
|
|
2904
|
+
output('nothing owed');
|
|
2905
|
+
if (parsed.resume) output(TEACH_RESUME_START);
|
|
2906
|
+
return 0;
|
|
2907
|
+
}
|
|
2908
|
+
rows.forEach((row, index) => {
|
|
2909
|
+
if (index) output('');
|
|
2910
|
+
output(row.url || row.id);
|
|
2911
|
+
output(`section ${row.section}`);
|
|
2912
|
+
if (row.check) output(row.check);
|
|
2913
|
+
});
|
|
2914
|
+
if (parsed.resume) output(TEACH_RESUME_NEXT);
|
|
2915
|
+
return 0;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2449
2918
|
function applyTeachRecap(parsed, deps, output) {
|
|
2450
2919
|
const store = readTeachOwedStore(deps);
|
|
2451
2920
|
let id = parsed.url ? videoIdFromUrl(parsed.url) : null;
|
|
2452
2921
|
let entry = id ? store[id] : null;
|
|
2453
2922
|
if (!entry && !id) {
|
|
2454
2923
|
const ids = Object.keys(store);
|
|
2924
|
+
const unpaidIds = ids.filter((key) => store[key] && String(store[key].check || '').trim());
|
|
2455
2925
|
if (parsed.recap) {
|
|
2456
|
-
id =
|
|
2926
|
+
id = unpaidIds.find((key) => recapHitsTokens(parsed.recap, store[key] && store[key].tokens));
|
|
2457
2927
|
}
|
|
2928
|
+
if (!id && unpaidIds.length === 1) id = unpaidIds[0];
|
|
2458
2929
|
if (!id && ids.length === 1) id = ids[0];
|
|
2459
2930
|
entry = id ? store[id] : null;
|
|
2460
2931
|
}
|
|
@@ -2463,8 +2934,13 @@ function applyTeachRecap(parsed, deps, output) {
|
|
|
2463
2934
|
if (!parsed.json) output('recap the previous section first');
|
|
2464
2935
|
return 2;
|
|
2465
2936
|
}
|
|
2937
|
+
if (!String(entry.check || '').trim()) {
|
|
2938
|
+
printTeachUnlockNext(parsed, entry, output);
|
|
2939
|
+
return 0;
|
|
2940
|
+
}
|
|
2466
2941
|
if (parsed.skip) {
|
|
2467
|
-
|
|
2942
|
+
printTeachUnlockNext(parsed, entry, output);
|
|
2943
|
+
writeTeachContinue(store, id, entry);
|
|
2468
2944
|
writeTeachOwedStore(deps, store);
|
|
2469
2945
|
return 0;
|
|
2470
2946
|
}
|
|
@@ -2472,11 +2948,40 @@ function applyTeachRecap(parsed, deps, output) {
|
|
|
2472
2948
|
if (!parsed.json) output(entry.check);
|
|
2473
2949
|
return 2;
|
|
2474
2950
|
}
|
|
2475
|
-
|
|
2951
|
+
printTeachUnlockNext(parsed, entry, output);
|
|
2952
|
+
writeTeachContinue(store, id, entry);
|
|
2476
2953
|
writeTeachOwedStore(deps, store);
|
|
2477
2954
|
return 0;
|
|
2478
2955
|
}
|
|
2479
2956
|
|
|
2957
|
+
function applyTeachNext(parsed, deps, output) {
|
|
2958
|
+
const store = readTeachOwedStore(deps);
|
|
2959
|
+
const cont = lastTeachContinueEntry(store);
|
|
2960
|
+
if (cont) {
|
|
2961
|
+
parsed.url = cont.url;
|
|
2962
|
+
parsed.section = cont.section;
|
|
2963
|
+
return 0;
|
|
2964
|
+
}
|
|
2965
|
+
const unpaid = lastTeachUnpaidEntry(store);
|
|
2966
|
+
if (unpaid) {
|
|
2967
|
+
if (!parsed.json) output(unpaid.check);
|
|
2968
|
+
return 2;
|
|
2969
|
+
}
|
|
2970
|
+
if (parsed.json) {
|
|
2971
|
+
output('[]');
|
|
2972
|
+
return 0;
|
|
2973
|
+
}
|
|
2974
|
+
output('nothing owed');
|
|
2975
|
+
output(TEACH_RESUME_START);
|
|
2976
|
+
return 0;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function applyTeachResume(parsed, deps, output) {
|
|
2980
|
+
const store = readTeachOwedStore(deps);
|
|
2981
|
+
if (lastTeachUnpaidEntry(store)) return printTeachOwed(parsed, deps, output);
|
|
2982
|
+
return applyTeachNext(parsed, deps, output);
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2480
2985
|
function teachCheckLine(lesson = {}) {
|
|
2481
2986
|
return oneTeachCheck(lesson.mechanisms || [], lesson.numbers || [], '');
|
|
2482
2987
|
}
|
|
@@ -2765,14 +3270,15 @@ function fileTeachBrief({ cwd, url, section, lesson, now } = {}) {
|
|
|
2765
3270
|
function ensureTeachApply({ cwd, url, section, packRel, now, output } = {}) {
|
|
2766
3271
|
const id = videoIdFromUrl(url);
|
|
2767
3272
|
const pack = packRel || (id ? teachExperimentRel(id, section) : null);
|
|
3273
|
+
const slug = pack ? path.basename(pack) : null;
|
|
2768
3274
|
return applyGate.ensureApply({
|
|
2769
3275
|
cwd,
|
|
2770
3276
|
source: url,
|
|
2771
3277
|
rel: id ? applySidecarRel(`${id}-s${section}`) : null,
|
|
2772
3278
|
now,
|
|
2773
3279
|
output,
|
|
2774
|
-
incompleteMessage:
|
|
2775
|
-
? `next:
|
|
3280
|
+
incompleteMessage: slug
|
|
3281
|
+
? `next: atris experiments keep ${slug}`
|
|
2776
3282
|
: TEACH_APPLY_NEXT_MESSAGE,
|
|
2777
3283
|
required: false,
|
|
2778
3284
|
change: pack ? `apply ${pack}` : undefined,
|
|
@@ -2832,11 +3338,22 @@ async function runYoutubeTeach(args = [], deps = {}) {
|
|
|
2832
3338
|
}
|
|
2833
3339
|
|
|
2834
3340
|
const owedDeps = { ...deps, cwd: deps.cwd || process.cwd() };
|
|
3341
|
+
if (parsed.owed) {
|
|
3342
|
+
if (!parsed.resume) return printTeachOwed(parsed, owedDeps, output);
|
|
3343
|
+
const resumeCode = applyTeachResume(parsed, owedDeps, output);
|
|
3344
|
+
if (resumeCode !== 0) return resumeCode;
|
|
3345
|
+
if (!parsed.url) return 0;
|
|
3346
|
+
}
|
|
2835
3347
|
if (parsed.recap != null || parsed.skip) {
|
|
2836
3348
|
const recapCode = applyTeachRecap(parsed, owedDeps, output);
|
|
2837
3349
|
if (recapCode !== 0) return recapCode;
|
|
2838
3350
|
if (!parsed.url || parsed.section <= 1) return 0;
|
|
2839
3351
|
}
|
|
3352
|
+
if (parsed.next) {
|
|
3353
|
+
const nextCode = applyTeachNext(parsed, owedDeps, output);
|
|
3354
|
+
if (nextCode !== 0) return nextCode;
|
|
3355
|
+
if (!parsed.url) return 0;
|
|
3356
|
+
}
|
|
2840
3357
|
|
|
2841
3358
|
if (!parsed.url) {
|
|
2842
3359
|
output('Missing YouTube URL. Run "atris youtube teach --help".');
|
|
@@ -2874,39 +3391,59 @@ async function runYoutubeTeach(args = [], deps = {}) {
|
|
|
2874
3391
|
title: source.title,
|
|
2875
3392
|
});
|
|
2876
3393
|
output(lesson.text);
|
|
2877
|
-
rememberTeachOwed(owedDeps, {
|
|
3394
|
+
rememberTeachOwed(owedDeps, {
|
|
3395
|
+
url: parsed.url,
|
|
3396
|
+
section: parsed.section,
|
|
3397
|
+
lesson,
|
|
3398
|
+
total: chapters.length,
|
|
3399
|
+
});
|
|
2878
3400
|
|
|
2879
3401
|
if (!parsed.save) {
|
|
2880
3402
|
if (!isThinTeachLesson(lesson)) applyGate.hintEphemeralApply(output, 'teach');
|
|
3403
|
+
printLearnerCheckGate(output, lesson, { includeCheck: true, json: parsed.json === true });
|
|
3404
|
+
printTeachWatchTickNext(parsed, lesson, chapters.length, output);
|
|
2881
3405
|
return 0;
|
|
2882
3406
|
}
|
|
2883
3407
|
if (isThinTeachLesson(lesson)) {
|
|
2884
3408
|
output(TEACH_THIN_REFUSE);
|
|
3409
|
+
printTeachWatchTickNext(parsed, lesson, chapters.length, output);
|
|
2885
3410
|
return 2;
|
|
2886
3411
|
}
|
|
2887
3412
|
|
|
3413
|
+
const cwd = deps.cwd || process.cwd();
|
|
2888
3414
|
fileTeachBrief({
|
|
2889
|
-
cwd
|
|
3415
|
+
cwd,
|
|
2890
3416
|
url: parsed.url,
|
|
2891
3417
|
section: parsed.section,
|
|
2892
3418
|
lesson: lesson.text,
|
|
2893
3419
|
now: deps.now,
|
|
2894
3420
|
});
|
|
2895
3421
|
const packRel = fileTeachExperiment({
|
|
2896
|
-
cwd
|
|
3422
|
+
cwd,
|
|
2897
3423
|
url: parsed.url,
|
|
2898
3424
|
section: parsed.section,
|
|
2899
3425
|
lesson,
|
|
2900
3426
|
});
|
|
2901
3427
|
const ensureApply = deps.ensureApply || ensureTeachApply;
|
|
2902
|
-
|
|
2903
|
-
cwd
|
|
3428
|
+
const applyCode = ensureApply({
|
|
3429
|
+
cwd,
|
|
2904
3430
|
url: parsed.url,
|
|
2905
3431
|
section: parsed.section,
|
|
2906
3432
|
packRel,
|
|
2907
3433
|
now: deps.now,
|
|
2908
3434
|
output,
|
|
2909
3435
|
});
|
|
3436
|
+
if (deps.ensureApply) return applyCode;
|
|
3437
|
+
const id = videoIdFromUrl(parsed.url);
|
|
3438
|
+
const baseline = proveSavedLearnerBaseline({
|
|
3439
|
+
cwd,
|
|
3440
|
+
applyRel: id ? applySidecarRel(`${id}-s${parsed.section}`) : null,
|
|
3441
|
+
lesson,
|
|
3442
|
+
output,
|
|
3443
|
+
json: parsed.json === true,
|
|
3444
|
+
});
|
|
3445
|
+
if (baseline !== 0) return baseline;
|
|
3446
|
+
return applyCode;
|
|
2910
3447
|
}
|
|
2911
3448
|
|
|
2912
3449
|
async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
@@ -2951,7 +3488,12 @@ async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
2951
3488
|
let status = 0;
|
|
2952
3489
|
try {
|
|
2953
3490
|
const data = await processYoutube(options, deps);
|
|
2954
|
-
|
|
3491
|
+
if (options.json) {
|
|
3492
|
+
output(JSON.stringify(data, null, 2));
|
|
3493
|
+
} else {
|
|
3494
|
+
output(formatYoutubeResult(data));
|
|
3495
|
+
printProcessLearnerGate(data, {}, output);
|
|
3496
|
+
}
|
|
2955
3497
|
} catch (err) {
|
|
2956
3498
|
if (!err.applyRequired) output(err.message);
|
|
2957
3499
|
status = Number.isInteger(err.exitCode) ? err.exitCode : 1;
|
|
@@ -2999,10 +3541,21 @@ module.exports = {
|
|
|
2999
3541
|
extractTeachNumbers,
|
|
3000
3542
|
extractTeachMechanisms,
|
|
3001
3543
|
extractTeachSource,
|
|
3544
|
+
oneTeachCheck,
|
|
3545
|
+
learnerCheckFromLesson,
|
|
3546
|
+
scoreLearnerNeedles,
|
|
3547
|
+
LEARNER_CHECK_FILL,
|
|
3548
|
+
LEARNER_SCORE_ZERO,
|
|
3549
|
+
printLearnerCheckGate,
|
|
3550
|
+
proveSavedLearnerBaseline,
|
|
3002
3551
|
isThinTeachLesson,
|
|
3003
3552
|
TEACH_THIN_REFUSE,
|
|
3553
|
+
TEACH_RESUME_NEXT,
|
|
3554
|
+
TEACH_WATCH_TICK_NEXT,
|
|
3004
3555
|
teachExperimentSlug,
|
|
3005
3556
|
notesExperimentSlug,
|
|
3557
|
+
digestExperimentSlug,
|
|
3558
|
+
watchExperimentSlug,
|
|
3006
3559
|
fileTeachExperiment,
|
|
3007
3560
|
youtubeCommand,
|
|
3008
3561
|
};
|