atris 3.58.5 → 3.58.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/atris/skills/x-search/SKILL.md +2 -2
- package/atris/skills/youtube/SKILL.md +44 -28
- package/commands/auth.js +52 -24
- package/commands/learn.js +205 -39
- package/commands/workflow.js +4 -8
- package/commands/x-search.js +9 -10
- package/commands/youtube.js +444 -101
- package/package.json +1 -1
- package/scripts/det/ytnotes +82 -5
- package/utils/auth.js +47 -6
package/package.json
CHANGED
package/scripts/det/ytnotes
CHANGED
|
@@ -8,7 +8,7 @@ set -euo pipefail
|
|
|
8
8
|
USAGE="usage: ytnotes <youtube-url> [haiku|atris-fast|gemini|grok|codex|cursor]"
|
|
9
9
|
|
|
10
10
|
case "${1:-}" in
|
|
11
|
-
*youtube.com*|*youtu.be*) ;;
|
|
11
|
+
*youtube.com*|*youtu.be*|*youtube-nocookie.com*) ;;
|
|
12
12
|
*) echo "$USAGE" >&2; exit 2 ;;
|
|
13
13
|
esac
|
|
14
14
|
|
|
@@ -20,17 +20,94 @@ WORK="${TMPDIR:-/tmp}/ytnotes"
|
|
|
20
20
|
mkdir -p "$WORK"
|
|
21
21
|
cd "$WORK"
|
|
22
22
|
|
|
23
|
+
# Printed metadata and a written English VTT are a hit even when yt-dlp exits 429.
|
|
24
|
+
# Manual English is --write-subs. Auto English is often en-orig / en-US / en-GB.
|
|
25
|
+
# Teach reads the same list on disk. Empty --print still keeps a VTT written
|
|
26
|
+
# in this run (id from the watch / shorts / embed / live / e / nocookie embed URL
|
|
27
|
+
# or the new filename). A copied #t= timestamp is stripped so leftover
|
|
28
|
+
# yt_<id> files still match.
|
|
29
|
+
existing=""
|
|
30
|
+
for name in yt_*.en.vtt yt_*.en-orig.vtt yt_*.en-US.vtt yt_*.en-GB.vtt; do
|
|
31
|
+
[ -e "$name" ] || continue
|
|
32
|
+
existing="$existing $name"
|
|
33
|
+
done
|
|
34
|
+
|
|
23
35
|
META=$(yt-dlp --no-update --quiet --no-warnings --skip-download \
|
|
24
|
-
--write-auto-subs --sub-format vtt --sub-langs en \
|
|
36
|
+
--write-subs --write-auto-subs --sub-format vtt --sub-langs "en,en-orig,en-US,en-GB" \
|
|
25
37
|
--no-simulate \
|
|
26
38
|
-o "yt_%(id)s" \
|
|
27
39
|
--print "%(id)s|%(title)s|%(channel)s|%(duration_string)s" \
|
|
28
|
-
"$URL" 2>/dev/null)
|
|
40
|
+
"$URL" 2>/dev/null) || true
|
|
29
41
|
ID="${META%%|*}"
|
|
30
42
|
INFO="${META#*|}"
|
|
43
|
+
if [ -z "$ID" ]; then
|
|
44
|
+
case "$URL" in
|
|
45
|
+
*'?v='*|*'&v='*)
|
|
46
|
+
ID="${URL#*'?v='}"
|
|
47
|
+
if [ "$ID" = "$URL" ]; then
|
|
48
|
+
ID="${URL#*'&v='}"
|
|
49
|
+
fi
|
|
50
|
+
ID="${ID%%&*}"
|
|
51
|
+
ID="${ID%%\?*}"
|
|
52
|
+
;;
|
|
53
|
+
*'youtu.be/'*)
|
|
54
|
+
ID="${URL#*'youtu.be/'}"
|
|
55
|
+
ID="${ID%%\?*}"
|
|
56
|
+
ID="${ID%%&*}"
|
|
57
|
+
ID="${ID%%/*}"
|
|
58
|
+
;;
|
|
59
|
+
*'/shorts/'*)
|
|
60
|
+
ID="${URL#*'/shorts/'}"
|
|
61
|
+
ID="${ID%%\?*}"
|
|
62
|
+
ID="${ID%%&*}"
|
|
63
|
+
ID="${ID%%/*}"
|
|
64
|
+
;;
|
|
65
|
+
*'/embed/'*)
|
|
66
|
+
ID="${URL#*'/embed/'}"
|
|
67
|
+
ID="${ID%%\?*}"
|
|
68
|
+
ID="${ID%%&*}"
|
|
69
|
+
ID="${ID%%/*}"
|
|
70
|
+
;;
|
|
71
|
+
*'/e/'*)
|
|
72
|
+
ID="${URL#*'/e/'}"
|
|
73
|
+
ID="${ID%%\?*}"
|
|
74
|
+
ID="${ID%%&*}"
|
|
75
|
+
ID="${ID%%/*}"
|
|
76
|
+
;;
|
|
77
|
+
*'/live/'*)
|
|
78
|
+
ID="${URL#*'/live/'}"
|
|
79
|
+
ID="${ID%%\?*}"
|
|
80
|
+
ID="${ID%%&*}"
|
|
81
|
+
ID="${ID%%/*}"
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
ID="${ID%%#*}"
|
|
85
|
+
fi
|
|
31
86
|
|
|
32
|
-
VTT="
|
|
33
|
-
if [
|
|
87
|
+
VTT=""
|
|
88
|
+
if [ -n "$ID" ]; then
|
|
89
|
+
for name in "yt_${ID}.en.vtt" "yt_${ID}.en-orig.vtt" "yt_${ID}.en-US.vtt" "yt_${ID}.en-GB.vtt"; do
|
|
90
|
+
if [ -s "$name" ]; then
|
|
91
|
+
VTT="$name"
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
fi
|
|
96
|
+
if [ -z "$VTT" ]; then
|
|
97
|
+
for name in yt_*.en.vtt yt_*.en-orig.vtt yt_*.en-US.vtt yt_*.en-GB.vtt; do
|
|
98
|
+
[ -s "$name" ] || continue
|
|
99
|
+
case " $existing " in
|
|
100
|
+
*" $name "*) continue ;;
|
|
101
|
+
esac
|
|
102
|
+
VTT="$name"
|
|
103
|
+
if [ -z "$ID" ]; then
|
|
104
|
+
ID="${name#yt_}"
|
|
105
|
+
ID="${ID%%.*}"
|
|
106
|
+
fi
|
|
107
|
+
break
|
|
108
|
+
done
|
|
109
|
+
fi
|
|
110
|
+
if [ -z "$VTT" ]; then
|
|
34
111
|
echo "No English captions found for $URL. Falling back is manual (atris youtube process, 5 credits)." >&2
|
|
35
112
|
exit 2
|
|
36
113
|
fi
|
package/utils/auth.js
CHANGED
|
@@ -352,9 +352,13 @@ function autoSaveProfile(credentials) {
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
function saveCredentials(token, refreshToken, email, userId, provider) {
|
|
355
|
+
function saveCredentials(token, refreshToken, email, userId, provider, extras = {}) {
|
|
356
|
+
if (decodeJwtClaims(token)?.type === 'agent_access') {
|
|
357
|
+
throw new Error('Refusing to save a scoped agent token as the login token; keep it under agent_token');
|
|
358
|
+
}
|
|
356
359
|
const credentialsPath = getCredentialsPath();
|
|
357
360
|
const credentials = {
|
|
361
|
+
...extras,
|
|
358
362
|
token,
|
|
359
363
|
refresh_token: refreshToken || null,
|
|
360
364
|
email: email || null,
|
|
@@ -374,7 +378,43 @@ function saveCredentials(token, refreshToken, email, userId, provider) {
|
|
|
374
378
|
autoSaveProfile(credentials);
|
|
375
379
|
}
|
|
376
380
|
|
|
377
|
-
|
|
381
|
+
// Supplying the auth client enables asynchronous repair. Local-only readers stay synchronous.
|
|
382
|
+
function loadCredentials(apiRequestJson) {
|
|
383
|
+
const credentials = readCredentials();
|
|
384
|
+
if (!credentials || credentials.source || decodeJwtClaims(credentials.token)?.type !== 'agent_access') {
|
|
385
|
+
return credentials;
|
|
386
|
+
}
|
|
387
|
+
if (!credentials.refresh_token) {
|
|
388
|
+
console.error('atris login');
|
|
389
|
+
return credentials;
|
|
390
|
+
}
|
|
391
|
+
if (!apiRequestJson) return credentials;
|
|
392
|
+
return repairLoginCredentials(credentials, apiRequestJson);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function repairLoginCredentials(credentials, apiRequestJson) {
|
|
396
|
+
const refreshed = await refreshAccessToken(credentials.refresh_token, null, apiRequestJson);
|
|
397
|
+
const token = refreshed.data?.access_token;
|
|
398
|
+
if (!refreshed.ok || !token || decodeJwtClaims(token)?.type === 'agent_access') {
|
|
399
|
+
throw new Error('Could not repair login file. Run atris login');
|
|
400
|
+
}
|
|
401
|
+
const claims = decodeJwtClaims(credentials.token);
|
|
402
|
+
const { source_profile, ...rest } = credentials;
|
|
403
|
+
const next = {
|
|
404
|
+
...rest,
|
|
405
|
+
token,
|
|
406
|
+
refresh_token: refreshed.data.refresh_token || credentials.refresh_token,
|
|
407
|
+
agent_token: credentials.token,
|
|
408
|
+
agent_token_scopes: claims.scopes || [],
|
|
409
|
+
agent_token_expires_at: typeof claims.exp === 'number' ? new Date(claims.exp * 1000).toISOString() : null,
|
|
410
|
+
};
|
|
411
|
+
if (source_profile) saveProfile(source_profile, next);
|
|
412
|
+
else saveCredentials(token, next.refresh_token, next.email, next.user_id, next.provider, next);
|
|
413
|
+
console.error('Repaired login file: moved a scoped agent token aside.');
|
|
414
|
+
return { ...next, ...(source_profile ? { source_profile } : {}) };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function readCredentials() {
|
|
378
418
|
// Priority: ATRIS_TOKEN env var → placed agent token → ATRIS_PROFILE env var
|
|
379
419
|
// → per-terminal session file → global credentials.json. A fresh placed token
|
|
380
420
|
// overrides a different env token because cloud images can carry a stale one.
|
|
@@ -552,7 +592,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
|
|
|
552
592
|
saved_at: new Date().toISOString(),
|
|
553
593
|
});
|
|
554
594
|
} else {
|
|
555
|
-
saveCredentials(accessToken, newRefreshToken, email, userId, provider);
|
|
595
|
+
saveCredentials(accessToken, newRefreshToken, email, userId, provider, credentials);
|
|
556
596
|
}
|
|
557
597
|
};
|
|
558
598
|
|
|
@@ -586,7 +626,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
|
|
|
586
626
|
saved_at: new Date().toISOString(),
|
|
587
627
|
});
|
|
588
628
|
} else {
|
|
589
|
-
saveCredentials(accessToken, newRefreshToken, updatedEmail, updatedUserId, updatedProvider);
|
|
629
|
+
saveCredentials(accessToken, newRefreshToken, updatedEmail, updatedUserId, updatedProvider, credentials);
|
|
590
630
|
}
|
|
591
631
|
latestCreds = loadCredentials();
|
|
592
632
|
}
|
|
@@ -603,7 +643,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
|
|
|
603
643
|
}
|
|
604
644
|
|
|
605
645
|
async function ensureValidCredentials(apiRequestJson, options = {}) {
|
|
606
|
-
let credentials = loadCredentials();
|
|
646
|
+
let credentials = await loadCredentials(apiRequestJson);
|
|
607
647
|
if (!credentials || !credentials.token) {
|
|
608
648
|
return { error: 'not_logged_in' };
|
|
609
649
|
}
|
|
@@ -659,7 +699,8 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
|
|
|
659
699
|
credentials.refresh_token,
|
|
660
700
|
updatedEmail,
|
|
661
701
|
updatedUserId,
|
|
662
|
-
updatedProvider
|
|
702
|
+
updatedProvider,
|
|
703
|
+
credentials
|
|
663
704
|
);
|
|
664
705
|
}
|
|
665
706
|
}
|