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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.58.5",
3
+ "version": "3.58.6",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {
@@ -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="yt_${ID}.en.vtt"
33
- if [ ! -s "$VTT" ]; then
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
- function loadCredentials() {
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
  }