luckiest-co 1.0.47 → 1.0.49

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.
Files changed (274) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/ab-testing/CHANGELOG.md +5 -0
  5. package/skills/ab-testing/SKILL.md +12 -9
  6. package/skills/ab-testing/evals/evals.json +18 -7
  7. package/skills/ab-testing/references/sample-size-guide.md +2 -2
  8. package/skills/ads/CHANGELOG.md +5 -0
  9. package/skills/ads/SKILL.md +4 -4
  10. package/skills/ai-seo/CHANGELOG.md +7 -0
  11. package/skills/ai-seo/SKILL.md +27 -20
  12. package/skills/analytics/CHANGELOG.md +6 -0
  13. package/skills/analytics/SKILL.md +4 -3
  14. package/skills/analytics/evals/evals.json +27 -0
  15. package/skills/analytics/references/ga4-implementation.md +7 -3
  16. package/skills/analytics/references/gtm-implementation.md +8 -2
  17. package/skills/aso/CHANGELOG.md +5 -0
  18. package/skills/aso/SKILL.md +4 -4
  19. package/skills/aso/evals/evals.json +15 -2
  20. package/skills/aso/references/benchmarks.md +4 -2
  21. package/skills/aso/references/google-play-specs.md +5 -1
  22. package/skills/brainstorm/SKILL.md +59 -16
  23. package/skills/brainstorm/builder-check-prompt.md +23 -0
  24. package/skills/churn-prevention/CHANGELOG.md +5 -0
  25. package/skills/churn-prevention/SKILL.md +3 -3
  26. package/skills/churn-prevention/evals/evals.json +14 -0
  27. package/skills/churn-prevention/references/dunning-playbook.md +6 -1
  28. package/skills/coder-brainstorming/CHANGELOG.md +10 -0
  29. package/skills/coder-brainstorming/SKILL.md +62 -20
  30. package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
  31. package/skills/coder-brainstorming/scripts/server.cjs +1 -1
  32. package/skills/coder-debugging/CHANGELOG.md +4 -0
  33. package/skills/coder-debugging/SKILL.md +5 -5
  34. package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
  35. package/skills/coder-requesting-code-review/SKILL.md +4 -4
  36. package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
  37. package/skills/coder-subagent-driven-development/SKILL.md +3 -3
  38. package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
  39. package/skills/cold-email/CHANGELOG.md +7 -0
  40. package/skills/cold-email/SKILL.md +27 -7
  41. package/skills/cold-email/evals/evals.json +30 -0
  42. package/skills/cold-email/references/benchmarks.md +15 -11
  43. package/skills/cold-email/references/deliverability.md +110 -0
  44. package/skills/cold-email/references/linkedin-outreach.md +91 -0
  45. package/skills/cold-email/references/multichannel-cadence.md +79 -0
  46. package/skills/cold-email/references/outbound-plays.md +88 -0
  47. package/skills/cold-email/references/personalization.md +11 -0
  48. package/skills/cold-email/references/reply-handling.md +65 -0
  49. package/skills/competitor-profiling/CHANGELOG.md +5 -0
  50. package/skills/competitor-profiling/SKILL.md +4 -4
  51. package/skills/content-strategy/CHANGELOG.md +5 -0
  52. package/skills/content-strategy/SKILL.md +5 -3
  53. package/skills/copy-editing/CHANGELOG.md +5 -0
  54. package/skills/copy-editing/SKILL.md +5 -5
  55. package/skills/copy-editing/references/content-refresh.md +33 -0
  56. package/skills/cro/CHANGELOG.md +5 -0
  57. package/skills/cro/SKILL.md +14 -3
  58. package/skills/cro/references/form.md +1 -1
  59. package/skills/directory-submissions/CHANGELOG.md +5 -0
  60. package/skills/directory-submissions/SKILL.md +13 -4
  61. package/skills/directory-submissions/evals/evals.json +15 -1
  62. package/skills/directory-submissions/references/directory-list.md +2 -4
  63. package/skills/emails/CHANGELOG.md +5 -0
  64. package/skills/emails/SKILL.md +14 -4
  65. package/skills/launch/CHANGELOG.md +7 -0
  66. package/skills/launch/SKILL.md +8 -4
  67. package/skills/launch/evals/evals.json +29 -0
  68. package/skills/launch/references/shipped-changes.md +84 -0
  69. package/skills/launch/references/site-launch-qa.md +55 -0
  70. package/skills/lead-magnets/CHANGELOG.md +5 -0
  71. package/skills/lead-magnets/SKILL.md +6 -4
  72. package/skills/lead-magnets/evals/evals.json +18 -0
  73. package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
  74. package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
  75. package/skills/lead-magnets/references/content-to-skill.md +108 -0
  76. package/skills/marketing-plan/CHANGELOG.md +5 -0
  77. package/skills/marketing-plan/SKILL.md +3 -3
  78. package/skills/marketing-plan/evals/evals.json +13 -0
  79. package/skills/marketing-plan/references/budget-planning.md +26 -17
  80. package/skills/model-router/CHANGELOG.md +6 -0
  81. package/skills/model-router/SKILL.md +3 -3
  82. package/skills/model-router/scripts/route.py +1 -1
  83. package/skills/model-router/scripts/test_route.py +5 -0
  84. package/skills/pricing/CHANGELOG.md +5 -0
  85. package/skills/pricing/SKILL.md +4 -4
  86. package/skills/pricing/evals/evals.json +12 -0
  87. package/skills/pricing/references/research-methods.md +8 -4
  88. package/skills/programmatic-seo/CHANGELOG.md +5 -0
  89. package/skills/programmatic-seo/SKILL.md +8 -4
  90. package/skills/programmatic-seo/evals/evals.json +13 -0
  91. package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
  92. package/skills/prospecting/CHANGELOG.md +7 -0
  93. package/skills/prospecting/SKILL.md +27 -13
  94. package/skills/prospecting/evals/evals.json +41 -0
  95. package/skills/prospecting/references/account-research.md +59 -0
  96. package/skills/prospecting/references/b2b-prospecting.md +1 -1
  97. package/skills/prospecting/references/compliance.md +44 -6
  98. package/skills/prospecting/references/data-sources.md +13 -15
  99. package/skills/prospecting/references/enrichment-playbook.md +61 -0
  100. package/skills/prospecting/references/saas-prospecting.md +1 -1
  101. package/skills/prospecting/references/signal-plays.md +99 -0
  102. package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
  103. package/skills/referrals/CHANGELOG.md +5 -0
  104. package/skills/referrals/SKILL.md +10 -4
  105. package/skills/referrals/evals/evals.json +99 -0
  106. package/skills/referrals/references/affiliate-operations.md +64 -0
  107. package/skills/referrals/references/affiliate-programs.md +2 -2
  108. package/skills/referrals/references/program-examples.md +14 -6
  109. package/skills/referrals/references/review-platform-rules.md +36 -0
  110. package/skills/referrals/references/review-program-operations.md +62 -0
  111. package/skills/referrals/references/review-programs.md +96 -0
  112. package/skills/revops/CHANGELOG.md +5 -0
  113. package/skills/revops/SKILL.md +10 -6
  114. package/skills/revops/evals/evals.json +13 -0
  115. package/skills/revops/references/automation-playbooks.md +1 -1
  116. package/skills/revops/references/inbound-routing.md +43 -0
  117. package/skills/revops/references/lifecycle-definitions.md +31 -0
  118. package/skills/sales-enablement/CHANGELOG.md +5 -0
  119. package/skills/sales-enablement/SKILL.md +6 -4
  120. package/skills/sales-enablement/evals/evals.json +14 -0
  121. package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
  122. package/skills/schema/CHANGELOG.md +5 -0
  123. package/skills/schema/SKILL.md +20 -8
  124. package/skills/schema/evals/evals.json +40 -0
  125. package/skills/schema/references/schema-examples.md +9 -15
  126. package/skills/seo-audit/CHANGELOG.md +5 -0
  127. package/skills/seo-audit/SKILL.md +10 -16
  128. package/skills/seo-audit/evals/evals.json +68 -0
  129. package/skills/seo-audit/references/international-seo.md +9 -11
  130. package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
  131. package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
  132. package/skills/seo-audit/references/local-seo.md +86 -0
  133. package/skills/seo-audit/references/rankings-push.md +62 -0
  134. package/skills/seo-audit/references/title-tags.md +89 -0
  135. package/skills/signup/CHANGELOG.md +5 -0
  136. package/skills/signup/SKILL.md +7 -3
  137. package/skills/site-architecture/CHANGELOG.md +5 -0
  138. package/skills/site-architecture/SKILL.md +5 -4
  139. package/skills/site-architecture/evals/evals.json +25 -0
  140. package/skills/site-architecture/references/platforms-and-migration.md +84 -0
  141. package/skills/social/CHANGELOG.md +5 -0
  142. package/skills/social/SKILL.md +4 -4
  143. package/skills/trends/.skillignore +3 -0
  144. package/skills/trends/CHANGELOG.md +20 -0
  145. package/skills/trends/SKILL.md +12 -8
  146. package/skills/trends/references/agent-mode.md +36 -0
  147. package/skills/trends/references/category-peers.md +38 -0
  148. package/skills/trends/references/comparison.md +227 -0
  149. package/skills/trends/references/competitors.md +29 -0
  150. package/skills/trends/references/discovery.md +90 -0
  151. package/skills/trends/references/engine-contract.md +111 -1889
  152. package/skills/trends/references/followups.md +112 -0
  153. package/skills/trends/references/grok-bot-x.md +55 -0
  154. package/skills/trends/references/hiring-signals.md +19 -0
  155. package/skills/trends/references/library-queue.md +33 -0
  156. package/skills/trends/references/recommendations.md +86 -0
  157. package/skills/trends/references/research-runbook.md +781 -0
  158. package/skills/trends/references/runtime.md +165 -0
  159. package/skills/trends/references/save-html-brief.md +39 -20
  160. package/skills/trends/references/setup-wizard.md +253 -0
  161. package/skills/trends/references/synthesis.md +289 -0
  162. package/skills/trends/scripts/box_chrome_login.py +209 -0
  163. package/skills/trends/scripts/briefing.py +9 -17
  164. package/skills/trends/scripts/last30days.py +3247 -280
  165. package/skills/trends/scripts/lib/agentcookie.py +196 -0
  166. package/skills/trends/scripts/lib/amazon.py +877 -0
  167. package/skills/trends/scripts/lib/arxiv.py +42 -8
  168. package/skills/trends/scripts/lib/backends.py +769 -0
  169. package/skills/trends/scripts/lib/bird_x.py +340 -57
  170. package/skills/trends/scripts/lib/bluesky.py +84 -10
  171. package/skills/trends/scripts/lib/bounded_get.py +156 -0
  172. package/skills/trends/scripts/lib/brightdata.py +291 -0
  173. package/skills/trends/scripts/lib/categories.py +7 -1
  174. package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
  175. package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
  176. package/skills/trends/scripts/lib/cluster.py +31 -54
  177. package/skills/trends/scripts/lib/competitors.py +10 -1
  178. package/skills/trends/scripts/lib/cookie_extract.py +221 -44
  179. package/skills/trends/scripts/lib/cookie_paths.py +27 -0
  180. package/skills/trends/scripts/lib/corpus.py +439 -0
  181. package/skills/trends/scripts/lib/devto.py +313 -0
  182. package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
  183. package/skills/trends/scripts/lib/doctor.py +2166 -0
  184. package/skills/trends/scripts/lib/dripstack.py +203 -0
  185. package/skills/trends/scripts/lib/entity_extract.py +37 -0
  186. package/skills/trends/scripts/lib/env.py +1092 -138
  187. package/skills/trends/scripts/lib/fanout.py +11 -1
  188. package/skills/trends/scripts/lib/feed.py +61 -0
  189. package/skills/trends/scripts/lib/fetch_memo.py +135 -0
  190. package/skills/trends/scripts/lib/freshness.py +566 -0
  191. package/skills/trends/scripts/lib/fusion.py +314 -28
  192. package/skills/trends/scripts/lib/github.py +438 -24
  193. package/skills/trends/scripts/lib/grok_x.py +1237 -0
  194. package/skills/trends/scripts/lib/grounding.py +129 -11
  195. package/skills/trends/scripts/lib/hackernews.py +61 -4
  196. package/skills/trends/scripts/lib/health.py +373 -0
  197. package/skills/trends/scripts/lib/hosted.py +333 -0
  198. package/skills/trends/scripts/lib/html_publish.py +34 -0
  199. package/skills/trends/scripts/lib/html_render.py +169 -3
  200. package/skills/trends/scripts/lib/http.py +1215 -70
  201. package/skills/trends/scripts/lib/instagram.py +132 -9
  202. package/skills/trends/scripts/lib/jobs.py +4 -2
  203. package/skills/trends/scripts/lib/library.py +308 -0
  204. package/skills/trends/scripts/lib/library_index.py +477 -0
  205. package/skills/trends/scripts/lib/linkedin.py +271 -24
  206. package/skills/trends/scripts/lib/log.py +5 -2
  207. package/skills/trends/scripts/lib/meta_ads.py +982 -0
  208. package/skills/trends/scripts/lib/normalize.py +382 -28
  209. package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
  210. package/skills/trends/scripts/lib/permission_preflight.py +42 -3
  211. package/skills/trends/scripts/lib/perplexity.py +1080 -468
  212. package/skills/trends/scripts/lib/pipeline.py +5079 -211
  213. package/skills/trends/scripts/lib/planner.py +396 -53
  214. package/skills/trends/scripts/lib/polymarket.py +285 -23
  215. package/skills/trends/scripts/lib/prescriptions.py +432 -0
  216. package/skills/trends/scripts/lib/private_files.py +21 -0
  217. package/skills/trends/scripts/lib/providers.py +231 -10
  218. package/skills/trends/scripts/lib/quality_nudge.py +122 -37
  219. package/skills/trends/scripts/lib/reddit.py +177 -12
  220. package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
  221. package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
  222. package/skills/trends/scripts/lib/reddit_listing.py +215 -14
  223. package/skills/trends/scripts/lib/reddit_public.py +12 -13
  224. package/skills/trends/scripts/lib/reddit_search.py +304 -0
  225. package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
  226. package/skills/trends/scripts/lib/registers.py +134 -0
  227. package/skills/trends/scripts/lib/relevance.py +17 -0
  228. package/skills/trends/scripts/lib/render.py +2161 -271
  229. package/skills/trends/scripts/lib/rerank.py +623 -55
  230. package/skills/trends/scripts/lib/resolve.py +50 -1
  231. package/skills/trends/scripts/lib/safari_cookies.py +42 -15
  232. package/skills/trends/scripts/lib/schema.py +782 -1
  233. package/skills/trends/scripts/lib/setup_wizard.py +779 -120
  234. package/skills/trends/scripts/lib/signals.py +206 -7
  235. package/skills/trends/scripts/lib/stocktwits.py +374 -0
  236. package/skills/trends/scripts/lib/subproc.py +307 -53
  237. package/skills/trends/scripts/lib/techmeme.py +89 -64
  238. package/skills/trends/scripts/lib/telegram.py +317 -0
  239. package/skills/trends/scripts/lib/tiktok.py +13 -9
  240. package/skills/trends/scripts/lib/topic_shape.py +340 -0
  241. package/skills/trends/scripts/lib/transcribe.py +12 -3
  242. package/skills/trends/scripts/lib/trustpilot.py +256 -5
  243. package/skills/trends/scripts/lib/truthsocial.py +9 -1
  244. package/skills/trends/scripts/lib/ui.py +6 -0
  245. package/skills/trends/scripts/lib/usage.py +132 -0
  246. package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
  247. package/skills/trends/scripts/lib/windows_job.py +138 -0
  248. package/skills/trends/scripts/lib/x_api.py +824 -0
  249. package/skills/trends/scripts/lib/x_envelope.py +733 -0
  250. package/skills/trends/scripts/lib/x_judge.py +292 -0
  251. package/skills/trends/scripts/lib/xai_x.py +26 -3
  252. package/skills/trends/scripts/lib/xquik.py +89 -15
  253. package/skills/trends/scripts/lib/xurl_x.py +284 -91
  254. package/skills/trends/scripts/lib/youtube_yt.py +570 -119
  255. package/skills/trends/scripts/setup-keychain.sh +12 -4
  256. package/skills/trends/scripts/setup-pass.sh +3 -0
  257. package/skills/trends/scripts/store.py +444 -50
  258. package/skills/trends/scripts/watchlist.py +38 -11
  259. package/skills/trends/scripts/x_lane_parity.py +107 -0
  260. package/skills/video-studio/CHANGELOG.md +4 -0
  261. package/skills/video-studio/SKILL.md +4 -4
  262. package/skills/video-studio/references/styles/product-flight.md +45 -0
  263. package/skills/video-studio/references/styles.md +3 -1
  264. package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
  265. package/skills/video-studio-motion/CHANGELOG.md +4 -0
  266. package/skills/video-studio-motion/SKILL.md +8 -5
  267. package/skills/youtube-planner/ATTRIBUTION.md +25 -0
  268. package/skills/youtube-planner/CHANGELOG.md +12 -0
  269. package/skills/youtube-planner/SKILL.md +191 -0
  270. package/skills/youtube-planner/references/plan-template.md +57 -0
  271. package/skills/youtube-planner/references/stages.md +119 -0
  272. package/skills/youtube-planner/scripts/floor_ceiling.py +132 -0
  273. package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
  274. package/skills/trends/scripts/lib/reddit_rss.py +0 -224
@@ -0,0 +1,196 @@
1
+ """agentcookie sidecar reader — an X cookie source for the bird backend.
2
+
3
+ ``agentcookie`` is an external, user-installed CLI that can deliver browser
4
+ cookies on Linux (where Chrome's SQLite cookie store cannot be decrypted by
5
+ this engine's stdlib extractor). This module shells out to it and pulls the
6
+ ``auth_token`` + ``ct0`` pair that the bird backend needs.
7
+
8
+ Deliberate constraints (see docs/plans/2026-08-31 X plan):
9
+
10
+ * **Soft dependency.** Activation is gated on ``shutil.which("agentcookie")``
11
+ resolving on the agent subprocess PATH, exactly like the other CLI-gated
12
+ optional sources (Digg, yt-dlp). A binary that is absent is not an error.
13
+ * **``AGENTCOOKIE=off`` disables it** regardless of PATH.
14
+ * **Independent of ``FROM_BROWSER``.** Reading the sidecar is not a browser
15
+ extraction, so it runs even when ``FROM_BROWSER`` is unset (the state in
16
+ which the in-process browser extractor stays off).
17
+ * We call ``agentcookie cookies --domain .x.com --json`` and parse its stdout.
18
+ We never open ``cookies-plain.db`` directly, never import an ``agentcookie``
19
+ Python package, and never add a Python dependency on it.
20
+ * Cookie **values are never logged**. Only counts / names are ever emitted.
21
+ * First complete pair wins: a lone ``auth_token`` or a lone ``ct0`` is not a
22
+ usable result — both must be present.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import os
29
+ import shutil
30
+ import subprocess
31
+ from pathlib import Path
32
+ from typing import Any, Dict, List, Optional
33
+
34
+ from . import log
35
+
36
+ AGENTCOOKIE_BIN = "agentcookie"
37
+ X_COOKIE_DOMAIN = ".x.com"
38
+ X_COOKIE_NAMES = ("auth_token", "ct0")
39
+
40
+ # agentcookie writes its config (including its role: "source" or "sink") here.
41
+ # A SINK receives cookies delivered from another machine; on Darwin, a sink is
42
+ # an "extra host" that gets the extra cookie lookups even though it is not a
43
+ # Mac mini. Reading this file is a plain filesystem read — NEVER a subprocess —
44
+ # so a MacBook (source/unknown role) never spawns agentcookie just to be
45
+ # classified (see the AE8 no-subprocess contract). Override the path for tests
46
+ # or non-default installs with AGENTCOOKIE_CONFIG.
47
+ _DEFAULT_CONFIG_PATH = Path.home() / ".config" / "agentcookie" / "config.json"
48
+
49
+ # The sidecar read shells out to another process; keep it bounded so a hung
50
+ # agentcookie never stalls config loading.
51
+ _TIMEOUT_SECONDS = 10
52
+
53
+
54
+ def _log(msg: str) -> None:
55
+ log.source_log("agentcookie", msg, tty_only=False)
56
+
57
+
58
+ def is_disabled(config: Optional[Dict[str, Any]] = None) -> bool:
59
+ """True when ``AGENTCOOKIE=off`` in config/env disables the sidecar."""
60
+ raw = ""
61
+ if config is not None:
62
+ raw = config.get("AGENTCOOKIE") or ""
63
+ if not raw:
64
+ from . import env
65
+
66
+ raw = env.read_secret_env("AGENTCOOKIE") or ""
67
+ return str(raw).strip().lower() == "off"
68
+
69
+
70
+ def is_available(config: Optional[Dict[str, Any]] = None) -> bool:
71
+ """True when the sidecar could be used: on PATH and not disabled.
72
+
73
+ PATH-only, side-effect free (no subprocess). Safe for the doctor /
74
+ preflight prediction path — it reads no cookies, only whether the binary
75
+ would be reachable at run time.
76
+ """
77
+ if is_disabled(config):
78
+ return False
79
+ return shutil.which(AGENTCOOKIE_BIN) is not None
80
+
81
+
82
+ def _config_path(config: Optional[Dict[str, Any]] = None) -> Path:
83
+ """Path to agentcookie's config file (AGENTCOOKIE_CONFIG override wins)."""
84
+ override = ""
85
+ if config is not None:
86
+ override = config.get("AGENTCOOKIE_CONFIG") or ""
87
+ override = override or os.environ.get("AGENTCOOKIE_CONFIG") or ""
88
+ return Path(override) if override else _DEFAULT_CONFIG_PATH
89
+
90
+
91
+ def role(config: Optional[Dict[str, Any]] = None) -> Optional[str]:
92
+ """agentcookie's configured role ("source"/"sink"), or None.
93
+
94
+ Reads and parses the config FILE only — no subprocess, so it is safe on a
95
+ MacBook that must not spawn agentcookie. Any failure (missing file, bad
96
+ JSON, no ``role`` key, wrong type) returns None. "parse failure = not sink".
97
+ """
98
+ path = _config_path(config)
99
+ try:
100
+ data = json.loads(path.read_text(encoding="utf-8"))
101
+ except (OSError, ValueError):
102
+ return None
103
+ if not isinstance(data, dict):
104
+ return None
105
+ raw = data.get("role")
106
+ return raw.strip().lower() if isinstance(raw, str) else None
107
+
108
+
109
+ def role_is_sink(config: Optional[Dict[str, Any]] = None) -> bool:
110
+ """True only when agentcookie's configured role parses as ``sink``."""
111
+ return role(config) == "sink"
112
+
113
+
114
+ def _consume_cookie_list(items: List[Any], names: tuple, found: Dict[str, str]) -> None:
115
+ """Collect ``name -> value`` for wanted cookie names from a cookie list."""
116
+ for item in items:
117
+ if not isinstance(item, dict):
118
+ continue
119
+ name = item.get("name")
120
+ value = item.get("value")
121
+ if name in names and isinstance(value, str) and value and name not in found:
122
+ found[name] = value
123
+
124
+
125
+ def _pair_from_json(data: Any, names: tuple) -> Dict[str, str]:
126
+ """Extract wanted cookies from agentcookie JSON, tolerant of its shape.
127
+
128
+ Accepts a list of ``{"name","value",...}`` objects, a ``{"cookies": [...]}``
129
+ wrapper, or a flat ``{name: value}`` / ``{name: {"value": ...}}`` mapping.
130
+ """
131
+ found: Dict[str, str] = {}
132
+ if isinstance(data, list):
133
+ _consume_cookie_list(data, names, found)
134
+ elif isinstance(data, dict):
135
+ cookies = data.get("cookies")
136
+ if isinstance(cookies, list):
137
+ _consume_cookie_list(cookies, names, found)
138
+ else:
139
+ for name in names:
140
+ raw = data.get(name)
141
+ if isinstance(raw, str) and raw:
142
+ found[name] = raw
143
+ elif isinstance(raw, dict) and isinstance(raw.get("value"), str) and raw["value"]:
144
+ found[name] = raw["value"]
145
+ return found
146
+
147
+
148
+ def read_x_cookies(config: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, str]]:
149
+ """Return the complete X cookie pair from agentcookie, or None.
150
+
151
+ Returns ``{"auth_token": ..., "ct0": ...}`` only when BOTH cookies are
152
+ present (no half-pair). Any failure (binary absent, disabled, non-zero
153
+ exit, unparsable JSON, timeout, incomplete pair) returns None so the
154
+ caller falls through to the next cookie source. Never raises.
155
+ """
156
+ if is_disabled(config):
157
+ return None
158
+ binary = shutil.which(AGENTCOOKIE_BIN)
159
+ if binary is None:
160
+ return None
161
+
162
+ try:
163
+ result = subprocess.run(
164
+ [binary, "cookies", "--domain", X_COOKIE_DOMAIN, "--json"],
165
+ capture_output=True,
166
+ text=True,
167
+ timeout=_TIMEOUT_SECONDS,
168
+ )
169
+ except subprocess.TimeoutExpired:
170
+ _log(f"timed out after {_TIMEOUT_SECONDS}s; skipping")
171
+ return None
172
+ except OSError as exc:
173
+ _log(f"could not run agentcookie: {type(exc).__name__}")
174
+ return None
175
+
176
+ if result.returncode != 0:
177
+ # stderr may carry a reason; do not echo it (it can contain values).
178
+ _log(f"exited {result.returncode}; skipping")
179
+ return None
180
+
181
+ output = (result.stdout or "").strip()
182
+ if not output:
183
+ return None
184
+ try:
185
+ data = json.loads(output)
186
+ except json.JSONDecodeError:
187
+ _log("returned non-JSON output; skipping")
188
+ return None
189
+
190
+ found = _pair_from_json(data, X_COOKIE_NAMES)
191
+ if all(name in found for name in X_COOKIE_NAMES):
192
+ _log(f"delivered a complete X cookie pair ({len(found)} of {len(X_COOKIE_NAMES)} names)")
193
+ return {name: found[name] for name in X_COOKIE_NAMES}
194
+ if found:
195
+ _log(f"returned an incomplete pair ({sorted(found)}); ignoring per no-half-pair rule")
196
+ return None