@duckmind/dm-windows-x64 0.60.6 → 0.60.9

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 (197) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +70 -123
  3. package/extensions/dm-9router-ext/src/index.js +410 -5
  4. package/extensions/dm-caveman/extensions/caveman.js +283 -12
  5. package/extensions/dm-cliproxy/index.js +182 -2
  6. package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
  7. package/extensions/dm-cliproxy/src/apply.js +228 -1
  8. package/extensions/dm-cliproxy/src/cache.js +42 -1
  9. package/extensions/dm-cliproxy/src/commands.js +50 -2
  10. package/extensions/dm-cliproxy/src/compat.js +81 -1
  11. package/extensions/dm-cliproxy/src/config.js +192 -2
  12. package/extensions/dm-cliproxy/src/conflicts.js +46 -1
  13. package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
  14. package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
  15. package/extensions/dm-cliproxy/src/log.js +23 -1
  16. package/extensions/dm-cliproxy/src/status-quota.js +77 -1
  17. package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
  18. package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
  19. package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
  20. package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
  21. package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
  22. package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
  23. package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
  24. package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
  25. package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
  26. package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
  27. package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
  28. package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
  29. package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
  30. package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
  31. package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
  32. package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
  33. package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
  34. package/extensions/dm-context/src/context.js +144 -1
  35. package/extensions/dm-context/src/index.js +339 -7
  36. package/extensions/dm-context/src/utils.js +9 -1
  37. package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
  38. package/extensions/dm-cua/index.js +75 -6
  39. package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
  40. package/extensions/dm-cua/src/browser-install.mjs +331 -2
  41. package/extensions/dm-fff/src/index.js +688 -12
  42. package/extensions/dm-fff/src/query.js +60 -1
  43. package/extensions/dm-goal/src/goal.js +894 -23
  44. package/extensions/dm-image2/index.js +103 -9
  45. package/extensions/dm-image2/src/image-lib.mjs +1275 -8
  46. package/extensions/dm-subagents/install.mjs +62 -8
  47. package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
  48. package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
  49. package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
  50. package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
  51. package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
  52. package/extensions/dm-subagents/src/agents/agents.js +1139 -11
  53. package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
  54. package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
  55. package/extensions/dm-subagents/src/agents/identity.js +29 -1
  56. package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
  57. package/extensions/dm-subagents/src/agents/skills.js +614 -6
  58. package/extensions/dm-subagents/src/extension/config.js +35 -2
  59. package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
  60. package/extensions/dm-subagents/src/extension/doctor.js +172 -15
  61. package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
  62. package/extensions/dm-subagents/src/extension/index.js +521 -359
  63. package/extensions/dm-subagents/src/extension/rpc.js +266 -7
  64. package/extensions/dm-subagents/src/extension/schemas.js +275 -1
  65. package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
  66. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
  67. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
  68. package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
  69. package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
  70. package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
  71. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
  72. package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
  73. package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
  74. package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
  75. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
  76. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
  77. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
  78. package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
  79. package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
  80. package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
  81. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
  82. package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
  83. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
  84. package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
  85. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
  86. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
  87. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
  88. package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
  89. package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
  90. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
  91. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
  92. package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
  93. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
  94. package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
  95. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
  96. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
  97. package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
  98. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
  99. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
  100. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
  101. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
  102. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
  103. package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
  104. package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
  105. package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
  106. package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
  107. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
  108. package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
  109. package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
  110. package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
  111. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
  112. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
  113. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
  114. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
  115. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
  116. package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
  117. package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
  118. package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
  119. package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
  120. package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
  121. package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
  122. package/extensions/dm-subagents/src/shared/formatters.js +98 -7
  123. package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
  124. package/extensions/dm-subagents/src/shared/model-info.js +62 -1
  125. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
  126. package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
  127. package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
  128. package/extensions/dm-subagents/src/shared/settings.js +198 -11
  129. package/extensions/dm-subagents/src/shared/status-format.js +53 -1
  130. package/extensions/dm-subagents/src/shared/types.js +184 -6
  131. package/extensions/dm-subagents/src/shared/utils.js +462 -2
  132. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
  133. package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
  134. package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
  135. package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
  136. package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
  137. package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
  138. package/extensions/dm-subagents/src/tui/render.js +1542 -4
  139. package/extensions/dm-usage/index.js +1294 -9
  140. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
  141. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
  142. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
  143. package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
  144. package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
  145. package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
  146. package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
  147. package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
  148. package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
  149. package/extensions/greedysearch-dm/bin/search.mjs +620 -540
  150. package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
  151. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
  152. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
  153. package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
  154. package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
  155. package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
  156. package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
  157. package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
  158. package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
  159. package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
  160. package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
  161. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
  162. package/extensions/greedysearch-dm/index.js +123 -23
  163. package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
  164. package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
  165. package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
  166. package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
  167. package/extensions/greedysearch-dm/src/github.mjs +222 -7
  168. package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
  169. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
  170. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
  171. package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
  172. package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
  173. package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
  174. package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
  175. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
  176. package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
  177. package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
  178. package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
  179. package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
  180. package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
  181. package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
  182. package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
  183. package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
  184. package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
  185. package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
  186. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
  187. package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
  188. package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
  189. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
  190. package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
  191. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
  192. package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
  193. package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
  194. package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
  195. package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
  196. package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
  197. package/package.json +1 -1
@@ -1,3 +1,577 @@
1
- import{createRequire as y}from"node:module";var C=y(import.meta.url);var L=null;async function A(){if(L)return L;let[{Readability:K},{JSDOM:Y},{default:j}]=await Promise.all([import("@mozilla/readability"),import("jsdom"),import("turndown")]),Z=new j({headingStyle:"atx",bulletListMarker:"-",codeBlockStyle:"fenced"});return Z.addRule("removeDataUrls",{filter:($)=>$.tagName==="IMG"&&$.getAttribute("src")?.startsWith("data:"),replacement:()=>""}),L={Readability:K,JSDOM:Y,turndown:Z},L}var w="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",g={"user-agent":w,accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8","accept-language":"en-US,en;q=0.9","accept-encoding":"gzip, deflate, br","cache-control":"no-cache",pragma:"no-cache","sec-ch-ua":'"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',"sec-ch-ua-mobile":"?0","sec-ch-ua-platform":'"Windows"',"sec-fetch-dest":"document","sec-fetch-mode":"navigate","sec-fetch-site":"none","sec-fetch-user":"?1","upgrade-insecure-requests":"1"},k=[/^localhost$/i,/^127\.\d+\.\d+\.\d+$/,/^0\.0\.0\.0$/,/^\[::1\]$/,/^10\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^fc00:/i,/^fe80:/i,/\.local$/i,/\.internal$/i,/\.localhost$/i],P=/^::ffff:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/i,R=/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i,_=/^(?:0x[0-9a-f]+|0[0-7]*|[1-9][0-9]*)$/i;function M(K){if(!K||K.includes(":"))return null;let Y=K.split(".");if(Y.length<1||Y.length>4)return null;if(!Y.every(($)=>_.test($)))return null;let j=Y.map(($)=>{if(/^0x/i.test($))return parseInt($,16);if(/^0[0-7]+$/.test($))return parseInt($,8);return parseInt($,10)});if(j.some(($)=>!Number.isFinite($)||$<0))return null;let Z;if(j.length===1){if(j[0]>4294967295)return null;Z=[j[0]>>>24&255,j[0]>>>16&255,j[0]>>>8&255,j[0]&255]}else if(j.length===2){if(j[0]>255||j[1]>16777215)return null;Z=[j[0],j[1]>>>16&255,j[1]>>>8&255,j[1]&255]}else if(j.length===3){if(j[0]>255||j[1]>255||j[2]>65535)return null;Z=[j[0],j[1],j[2]>>>8&255,j[2]&255]}else{if(j.some(($)=>$>255))return null;Z=j}return Z.join(".")}function E(K){let Y=K.match(P);if(Y)return`${Y[1]}.${Y[2]}.${Y[3]}.${Y[4]}`;let j=K.match(R);if(j){let Z=parseInt(j[1],16),$=parseInt(j[2],16);if(Z>65535||$>65535)return null;return[Z>>>8&255,Z&255,$>>>8&255,$&255].join(".")}return null}function D(K){return k.some((Y)=>Y.test(K))}function o(K={}){return{...g,...K}}function b(K){try{if(typeof K!=="string"||!K.trim())return{blocked:!0,reason:"URL must be a non-empty string"};let Y=new URL(K);if(Y.protocol!=="http:"&&Y.protocol!=="https:")return{blocked:!0,reason:`Protocol not allowed: ${Y.protocol}`};let j=Y.hostname.toLowerCase();for(let $ of k)if($.test(j))return{blocked:!0,reason:`Private/internal address: ${j}`};if(j.startsWith("[")&&j.endsWith("]")){let $=j.slice(1,-1),W=E($);if(W&&D(W))return{blocked:!0,reason:`Private/internal address: ${j} (maps to ${W})`}}let Z=M(j);if(Z&&D(Z))return{blocked:!0,reason:`Private/internal address: ${j} (normalizes to ${Z})`};return{blocked:!1}}catch(Y){return{blocked:!0,reason:`Invalid URL: ${Y.message}`}}}function x(K){try{let Y=new URL(K);if(!(Y.hostname==="github.com"||Y.hostname.endsWith(".github.com")))return K;let j=Y.pathname.split("/").filter(Boolean);if(j.length<5)return K;let[Z,$,W,X,...z]=j;if(W!=="blob")return K;let J=z.join("/");return`https://raw.githubusercontent.com/${Z}/${$}/${X}/${J}`}catch{return K}}async function n(K,Y={}){let j=b(K);if(j.blocked)return{ok:!1,url:K,finalUrl:K,status:403,error:`Blocked: ${j.reason}`,needsBrowser:!1};let Z=K;if(K=x(K),K!==Z)console.error(`[fetcher] Rewrote GitHub URL: ${Z.slice(0,60)}... → raw.githubusercontent.com`);let{timeoutMs:$=15000,userAgent:W,signal:X}=Y,z=new AbortController,J=setTimeout(()=>z.abort(),$);if(X)X.addEventListener("abort",()=>z.abort(),{once:!0});try{let Q=await fetch(K,{method:"GET",headers:{...g,"user-agent":W||w},redirect:"follow",signal:z.signal});clearTimeout(J);let B=Q.headers.get("content-type")||"",V=Q.url,F=Q.headers.get("last-modified")||"",q=b(V);if(q.blocked)return{ok:!1,url:K,finalUrl:V,status:Q.status,error:`Blocked: ${q.reason}`,needsBrowser:!1};let H=!1;try{H=new URL(V).hostname.toLowerCase()==="raw.githubusercontent.com"}catch{}if(B.includes("text/plain")&&H){let O=await Q.text();return{ok:!0,url:Z,finalUrl:V,status:Q.status,title:V.split("/").pop()||"GitHub File",byline:"",siteName:"GitHub",lang:"",publishedTime:F,lastModified:F,markdown:O,contentLength:O.length,excerpt:O.slice(0,300).replaceAll(/\n/g," "),needsBrowser:!1}}if(!B.includes("text/html")&&!B.includes("application/xhtml"))return{ok:!1,url:K,finalUrl:V,status:Q.status,error:`Unsupported content type: ${B}`,needsBrowser:!1};let I=await Q.text(),N=T(Q.status,I,V,K);if(N.blocked)return{ok:!1,url:K,finalUrl:V,status:Q.status,error:`Blocked: ${N.reason}`,needsBrowser:!0};let G=await c(I,V),S=d(G);if(!S.ok)return{ok:!1,url:K,finalUrl:V,status:Q.status,error:`Low quality content: ${S.reason}`,needsBrowser:!0};return{ok:!0,url:K,finalUrl:V,status:Q.status,title:G.title,byline:G.byline,siteName:G.siteName,lang:G.lang,publishedTime:G.publishedTime||F,lastModified:F,markdown:G.markdown,excerpt:G.excerpt,contentLength:G.markdown.length,needsBrowser:!1}}catch(Q){clearTimeout(J);let B=m(Q);return{ok:!1,url:K,finalUrl:K,status:0,error:Q.message,needsBrowser:B}}}function T(K,Y,j,Z){let $=Y.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.toLowerCase()||"",W=Y.slice(0,30000).toLowerCase(),X=`${$} ${W}`;if(K===403||K===429||K===503)return{blocked:!0,reason:`HTTP ${K}`};let z=[{pattern:/class=["'][^"']*captcha["']|<div[^>]*id=["']captcha/i,reason:"captcha"},{pattern:/g-recaptcha|data-sitekey|i['"]m not a robot/i,reason:"captcha"},{pattern:/checking your browser.{0,100}please wait|cf-browser-verification/i,reason:"cloudflare challenge"},{pattern:/just a moment.{0,50}security check|ddos protection by cloudflare/i,reason:"cloudflare challenge"},{pattern:/unusual traffic.{0,50}from your computer network/i,reason:"unusual traffic"},{pattern:/bot detected|automated.{0,20}request/i,reason:"bot detection"},{pattern:/enable\s+javascript\s+to\s+view|javascript\s+is\s+required.{0,50}enabled/i,reason:"requires javascript"},{pattern:/access denied|accessdenied/i,reason:"access denied"},{pattern:/protected by anubis|anubis uses a proof-of-work/i,reason:"anubis challenge"}];for(let Q of z)if(Q.pattern.test(X))return{blocked:!0,reason:Q.reason};let J=p(Z,j,Y);if(J)return{blocked:!0,reason:J};return{blocked:!1}}var f=["accounts.google.com","login.microsoftonline.com","login.live.com","auth0.com","okta.com","auth.mozilla.auth0.com","id.atlassian.com"],U=["login.","signin.","auth.","sso.","accounts.","idp."],h=["sign in to continue","log in to continue","authentication required","create an account to continue","subscribe to continue reading","members only"];function p(K,Y,j){try{let Z=new URL(K),$=new URL(Y);if(Z.hostname.toLowerCase()===$.hostname.toLowerCase())return;let W=$.hostname.toLowerCase();if(f.some((z)=>W===z||W.endsWith(`.${z}`)))return`redirected to login (${$.hostname})`;if(U.some((z)=>W.startsWith(z)))return`redirected to login (${$.hostname})`;let X=j.slice(0,20000).toLowerCase();if(h.some((z)=>X.includes(z)))return`redirected to login page (${$.hostname})`}catch{}return}function m(K){let Y=K.message.toLowerCase();return Y.includes("fetch failed")||Y.includes("unable to verify")||Y.includes("certificate")||Y.includes("timeout")}function v(K){let Y=['meta[property="article:published_time"]','meta[name="article:published_time"]','meta[property="og:published_time"]','meta[name="publication_date"]','meta[name="date"]','meta[itemprop="datePublished"]','time[itemprop="datePublished"]','meta[name="DC.date"]'];for(let j of Y){let Z=K.querySelector(j),$=Z?.getAttribute("content")||Z?.getAttribute("datetime")||"";if($)return $}return""}async function c(K,Y){let{Readability:j,JSDOM:Z,turndown:$}=await A(),W=new Z(K,{url:Y});try{let X=W.window.document,J=new j(X).parse();if(J&&J.content){let V=$.turndown(J.content).replaceAll(/\n{3,}/g,`
1
+ let _deps = null;
2
+ async function loadDeps() {
3
+ if (_deps)
4
+ return _deps;
5
+ const [{ Readability }, { JSDOM }, { default: TurndownService }] = await Promise.all([
6
+ import("@mozilla/readability"),
7
+ import("jsdom"),
8
+ import("turndown")
9
+ ]);
10
+ const turndown = new TurndownService({
11
+ headingStyle: "atx",
12
+ bulletListMarker: "-",
13
+ codeBlockStyle: "fenced"
14
+ });
15
+ turndown.addRule("removeDataUrls", {
16
+ filter: (node) => node.tagName === "IMG" && node.getAttribute("src")?.startsWith("data:"),
17
+ replacement: () => ""
18
+ });
19
+ _deps = { Readability, JSDOM, turndown };
20
+ return _deps;
21
+ }
22
+ const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
23
+ const DEFAULT_HEADERS = {
24
+ "user-agent": DEFAULT_USER_AGENT,
25
+ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
26
+ "accept-language": "en-US,en;q=0.9",
27
+ "accept-encoding": "gzip, deflate, br",
28
+ "cache-control": "no-cache",
29
+ pragma: "no-cache",
30
+ "sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
31
+ "sec-ch-ua-mobile": "?0",
32
+ "sec-ch-ua-platform": '"Windows"',
33
+ "sec-fetch-dest": "document",
34
+ "sec-fetch-mode": "navigate",
35
+ "sec-fetch-site": "none",
36
+ "sec-fetch-user": "?1",
37
+ "upgrade-insecure-requests": "1"
38
+ };
39
+ const PRIVATE_URL_PATTERNS = [
40
+ /^localhost$/i,
41
+ /^127\.\d+\.\d+\.\d+$/,
42
+ /^0\.0\.0\.0$/,
43
+ /^\[::1\]$/,
44
+ /^10\./,
45
+ /^172\.(1[6-9]|2\d|3[01])\./,
46
+ /^192\.168\./,
47
+ /^169\.254\./,
48
+ /^fc00:/i,
49
+ /^fe80:/i,
50
+ /\.local$/i,
51
+ /\.internal$/i,
52
+ /\.localhost$/i
53
+ ];
54
+ const IPV4_MAPPED_DOTTED_RE = /^::ffff:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/i;
55
+ const IPV4_MAPPED_HEX_RE = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
56
+ const INET_ATON_SEGMENT_RE = /^(?:0x[0-9a-f]+|0[0-7]*|[1-9][0-9]*)$/i;
57
+ function normalizeNumericIPv4(hostname) {
58
+ if (!hostname || hostname.includes(":"))
59
+ return null;
60
+ const parts = hostname.split(".");
61
+ if (parts.length < 1 || parts.length > 4)
62
+ return null;
63
+ if (!parts.every((p) => INET_ATON_SEGMENT_RE.test(p)))
64
+ return null;
65
+ const nums = parts.map((p) => {
66
+ if (/^0x/i.test(p))
67
+ return parseInt(p, 16);
68
+ if (/^0[0-7]+$/.test(p))
69
+ return parseInt(p, 8);
70
+ return parseInt(p, 10);
71
+ });
72
+ if (nums.some((n) => !Number.isFinite(n) || n < 0))
73
+ return null;
74
+ let bytes;
75
+ if (nums.length === 1) {
76
+ if (nums[0] > 4294967295)
77
+ return null;
78
+ bytes = [
79
+ nums[0] >>> 24 & 255,
80
+ nums[0] >>> 16 & 255,
81
+ nums[0] >>> 8 & 255,
82
+ nums[0] & 255
83
+ ];
84
+ } else if (nums.length === 2) {
85
+ if (nums[0] > 255 || nums[1] > 16777215)
86
+ return null;
87
+ bytes = [nums[0], nums[1] >>> 16 & 255, nums[1] >>> 8 & 255, nums[1] & 255];
88
+ } else if (nums.length === 3) {
89
+ if (nums[0] > 255 || nums[1] > 255 || nums[2] > 65535)
90
+ return null;
91
+ bytes = [nums[0], nums[1], nums[2] >>> 8 & 255, nums[2] & 255];
92
+ } else {
93
+ if (nums.some((n) => n > 255))
94
+ return null;
95
+ bytes = nums;
96
+ }
97
+ return bytes.join(".");
98
+ }
99
+ function extractMappedIPv4(core) {
100
+ const dottedMatch = core.match(IPV4_MAPPED_DOTTED_RE);
101
+ if (dottedMatch) {
102
+ return `${dottedMatch[1]}.${dottedMatch[2]}.${dottedMatch[3]}.${dottedMatch[4]}`;
103
+ }
104
+ const hexMatch = core.match(IPV4_MAPPED_HEX_RE);
105
+ if (hexMatch) {
106
+ const hi = parseInt(hexMatch[1], 16);
107
+ const lo = parseInt(hexMatch[2], 16);
108
+ if (hi > 65535 || lo > 65535)
109
+ return null;
110
+ return [
111
+ hi >>> 8 & 255,
112
+ hi & 255,
113
+ lo >>> 8 & 255,
114
+ lo & 255
115
+ ].join(".");
116
+ }
117
+ return null;
118
+ }
119
+ function isPrivateIPv4(ip) {
120
+ return PRIVATE_URL_PATTERNS.some((pattern) => pattern.test(ip));
121
+ }
122
+ export function defaultFetchHeaders(overrides = {}) {
123
+ return { ...DEFAULT_HEADERS, ...overrides };
124
+ }
125
+ export function isPrivateUrl(url) {
126
+ try {
127
+ if (typeof url !== "string" || !url.trim()) {
128
+ return { blocked: true, reason: "URL must be a non-empty string" };
129
+ }
130
+ const parsed = new URL(url);
131
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
132
+ return {
133
+ blocked: true,
134
+ reason: `Protocol not allowed: ${parsed.protocol}`
135
+ };
136
+ }
137
+ const hostname = parsed.hostname.toLowerCase();
138
+ for (const pattern of PRIVATE_URL_PATTERNS) {
139
+ if (pattern.test(hostname)) {
140
+ return {
141
+ blocked: true,
142
+ reason: `Private/internal address: ${hostname}`
143
+ };
144
+ }
145
+ }
146
+ if (hostname.startsWith("[") && hostname.endsWith("]")) {
147
+ const core = hostname.slice(1, -1);
148
+ const mapped = extractMappedIPv4(core);
149
+ if (mapped && isPrivateIPv4(mapped)) {
150
+ return {
151
+ blocked: true,
152
+ reason: `Private/internal address: ${hostname} (maps to ${mapped})`
153
+ };
154
+ }
155
+ }
156
+ const normalized = normalizeNumericIPv4(hostname);
157
+ if (normalized && isPrivateIPv4(normalized)) {
158
+ return {
159
+ blocked: true,
160
+ reason: `Private/internal address: ${hostname} (normalizes to ${normalized})`
161
+ };
162
+ }
163
+ return { blocked: false };
164
+ } catch (error) {
165
+ return { blocked: true, reason: `Invalid URL: ${error.message}` };
166
+ }
167
+ }
168
+ export function rewriteGitHubUrl(url) {
169
+ try {
170
+ const parsed = new URL(url);
171
+ if (!(parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com"))) {
172
+ return url;
173
+ }
174
+ const parts = parsed.pathname.split("/").filter(Boolean);
175
+ if (parts.length < 5) {
176
+ return url;
177
+ }
178
+ const [owner, repo, type, ref, ...fileParts] = parts;
179
+ if (type !== "blob") {
180
+ return url;
181
+ }
182
+ const rawPath = fileParts.join("/");
183
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${rawPath}`;
184
+ return rawUrl;
185
+ } catch {
186
+ return url;
187
+ }
188
+ }
189
+ export async function fetchSourceHttp(url, options = {}) {
190
+ const privateCheck = isPrivateUrl(url);
191
+ if (privateCheck.blocked) {
192
+ return {
193
+ ok: false,
194
+ url,
195
+ finalUrl: url,
196
+ status: 403,
197
+ error: `Blocked: ${privateCheck.reason}`,
198
+ needsBrowser: false
199
+ };
200
+ }
201
+ const originalUrl = url;
202
+ url = rewriteGitHubUrl(url);
203
+ if (url !== originalUrl) {
204
+ console.error(`[fetcher] Rewrote GitHub URL: ${originalUrl.slice(0, 60)}... → raw.githubusercontent.com`);
205
+ }
206
+ const { timeoutMs = 15000, userAgent, signal } = options;
207
+ const controller = new AbortController;
208
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
209
+ if (signal) {
210
+ signal.addEventListener("abort", () => controller.abort(), { once: true });
211
+ }
212
+ try {
213
+ const response = await fetch(url, {
214
+ method: "GET",
215
+ headers: {
216
+ ...DEFAULT_HEADERS,
217
+ "user-agent": userAgent || DEFAULT_USER_AGENT
218
+ },
219
+ redirect: "follow",
220
+ signal: controller.signal
221
+ });
222
+ clearTimeout(timeoutId);
223
+ const contentType = response.headers.get("content-type") || "";
224
+ const finalUrl = response.url;
225
+ const lastModified = response.headers.get("last-modified") || "";
226
+ const finalPrivateCheck = isPrivateUrl(finalUrl);
227
+ if (finalPrivateCheck.blocked) {
228
+ return {
229
+ ok: false,
230
+ url,
231
+ finalUrl,
232
+ status: response.status,
233
+ error: `Blocked: ${finalPrivateCheck.reason}`,
234
+ needsBrowser: false
235
+ };
236
+ }
237
+ let isRawGitHub = false;
238
+ try {
239
+ const finalHost = new URL(finalUrl).hostname.toLowerCase();
240
+ isRawGitHub = finalHost === "raw.githubusercontent.com";
241
+ } catch {}
242
+ if (contentType.includes("text/plain") && isRawGitHub) {
243
+ const text = await response.text();
244
+ return {
245
+ ok: true,
246
+ url: originalUrl,
247
+ finalUrl,
248
+ status: response.status,
249
+ title: finalUrl.split("/").pop() || "GitHub File",
250
+ byline: "",
251
+ siteName: "GitHub",
252
+ lang: "",
253
+ publishedTime: lastModified,
254
+ lastModified,
255
+ markdown: text,
256
+ contentLength: text.length,
257
+ excerpt: text.slice(0, 300).replaceAll(/\n/g, " "),
258
+ needsBrowser: false
259
+ };
260
+ }
261
+ if (!contentType.includes("text/html") && !contentType.includes("application/xhtml")) {
262
+ return {
263
+ ok: false,
264
+ url,
265
+ finalUrl,
266
+ status: response.status,
267
+ error: `Unsupported content type: ${contentType}`,
268
+ needsBrowser: false
269
+ };
270
+ }
271
+ const html = await response.text();
272
+ const quickCheck = detectBotBlock(response.status, html, finalUrl, url);
273
+ if (quickCheck.blocked) {
274
+ return {
275
+ ok: false,
276
+ url,
277
+ finalUrl,
278
+ status: response.status,
279
+ error: `Blocked: ${quickCheck.reason}`,
280
+ needsBrowser: true
281
+ };
282
+ }
283
+ const extracted = await extractContent(html, finalUrl);
284
+ const quality = checkContentQuality(extracted);
285
+ if (!quality.ok) {
286
+ return {
287
+ ok: false,
288
+ url,
289
+ finalUrl,
290
+ status: response.status,
291
+ error: `Low quality content: ${quality.reason}`,
292
+ needsBrowser: true
293
+ };
294
+ }
295
+ return {
296
+ ok: true,
297
+ url,
298
+ finalUrl,
299
+ status: response.status,
300
+ title: extracted.title,
301
+ byline: extracted.byline,
302
+ siteName: extracted.siteName,
303
+ lang: extracted.lang,
304
+ publishedTime: extracted.publishedTime || lastModified,
305
+ lastModified,
306
+ markdown: extracted.markdown,
307
+ excerpt: extracted.excerpt,
308
+ contentLength: extracted.markdown.length,
309
+ needsBrowser: false
310
+ };
311
+ } catch (error) {
312
+ clearTimeout(timeoutId);
313
+ const needsBrowser = isNetworkErrorRetryableWithBrowser(error);
314
+ return {
315
+ ok: false,
316
+ url,
317
+ finalUrl: url,
318
+ status: 0,
319
+ error: error.message,
320
+ needsBrowser
321
+ };
322
+ }
323
+ }
324
+ export function detectBotBlock(status, html, finalUrl, originalUrl) {
325
+ const title = html.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.toLowerCase() || "";
326
+ const sample = html.slice(0, 30000).toLowerCase();
327
+ const combined = `${title} ${sample}`;
328
+ if (status === 403 || status === 429 || status === 503) {
329
+ return { blocked: true, reason: `HTTP ${status}` };
330
+ }
331
+ const blockSignals = [
332
+ {
333
+ pattern: /class=["'][^"']*captcha["']|<div[^>]*id=["']captcha/i,
334
+ reason: "captcha"
335
+ },
336
+ {
337
+ pattern: /g-recaptcha|data-sitekey|i['"]m not a robot/i,
338
+ reason: "captcha"
339
+ },
340
+ {
341
+ pattern: /checking your browser.{0,100}please wait|cf-browser-verification/i,
342
+ reason: "cloudflare challenge"
343
+ },
344
+ {
345
+ pattern: /just a moment.{0,50}security check|ddos protection by cloudflare/i,
346
+ reason: "cloudflare challenge"
347
+ },
348
+ {
349
+ pattern: /unusual traffic.{0,50}from your computer network/i,
350
+ reason: "unusual traffic"
351
+ },
352
+ {
353
+ pattern: /bot detected|automated.{0,20}request/i,
354
+ reason: "bot detection"
355
+ },
356
+ {
357
+ pattern: /enable\s+javascript\s+to\s+view|javascript\s+is\s+required.{0,50}enabled/i,
358
+ reason: "requires javascript"
359
+ },
360
+ { pattern: /access denied|accessdenied/i, reason: "access denied" },
361
+ {
362
+ pattern: /protected by anubis|anubis uses a proof-of-work/i,
363
+ reason: "anubis challenge"
364
+ }
365
+ ];
366
+ for (const signal of blockSignals) {
367
+ if (signal.pattern.test(combined)) {
368
+ return { blocked: true, reason: signal.reason };
369
+ }
370
+ }
371
+ const loginRedirect = detectLoginRedirect(originalUrl, finalUrl, html);
372
+ if (loginRedirect) {
373
+ return { blocked: true, reason: loginRedirect };
374
+ }
375
+ return { blocked: false };
376
+ }
377
+ const AUTH_DOMAINS = [
378
+ "accounts.google.com",
379
+ "login.microsoftonline.com",
380
+ "login.live.com",
381
+ "auth0.com",
382
+ "okta.com",
383
+ "auth.mozilla.auth0.com",
384
+ "id.atlassian.com"
385
+ ];
386
+ const AUTH_HOSTNAME_PREFIXES = [
387
+ "login.",
388
+ "signin.",
389
+ "auth.",
390
+ "sso.",
391
+ "accounts.",
392
+ "idp."
393
+ ];
394
+ const LOGIN_CONTENT_PATTERNS = [
395
+ "sign in to continue",
396
+ "log in to continue",
397
+ "authentication required",
398
+ "create an account to continue",
399
+ "subscribe to continue reading",
400
+ "members only"
401
+ ];
402
+ function detectLoginRedirect(requestedUrl, finalUrl, html) {
403
+ try {
404
+ const requested = new URL(requestedUrl);
405
+ const final = new URL(finalUrl);
406
+ if (requested.hostname.toLowerCase() === final.hostname.toLowerCase()) {
407
+ return;
408
+ }
409
+ const finalHost = final.hostname.toLowerCase();
410
+ if (AUTH_DOMAINS.some((d) => finalHost === d || finalHost.endsWith(`.${d}`))) {
411
+ return `redirected to login (${final.hostname})`;
412
+ }
413
+ if (AUTH_HOSTNAME_PREFIXES.some((p) => finalHost.startsWith(p))) {
414
+ return `redirected to login (${final.hostname})`;
415
+ }
416
+ const sample = html.slice(0, 20000).toLowerCase();
417
+ if (LOGIN_CONTENT_PATTERNS.some((p) => sample.includes(p))) {
418
+ return `redirected to login page (${final.hostname})`;
419
+ }
420
+ } catch {}
421
+ return;
422
+ }
423
+ function isNetworkErrorRetryableWithBrowser(error) {
424
+ const message = error.message.toLowerCase();
425
+ return message.includes("fetch failed") || message.includes("unable to verify") || message.includes("certificate") || message.includes("timeout");
426
+ }
427
+ function extractMetaDate(document) {
428
+ const selectors = [
429
+ 'meta[property="article:published_time"]',
430
+ 'meta[name="article:published_time"]',
431
+ 'meta[property="og:published_time"]',
432
+ 'meta[name="publication_date"]',
433
+ 'meta[name="date"]',
434
+ 'meta[itemprop="datePublished"]',
435
+ 'time[itemprop="datePublished"]',
436
+ 'meta[name="DC.date"]'
437
+ ];
438
+ for (const sel of selectors) {
439
+ const el = document.querySelector(sel);
440
+ const val = el?.getAttribute("content") || el?.getAttribute("datetime") || "";
441
+ if (val)
442
+ return val;
443
+ }
444
+ return "";
445
+ }
446
+ export async function extractContent(html, url) {
447
+ const { Readability, JSDOM, turndown } = await loadDeps();
448
+ const dom = new JSDOM(html, { url });
449
+ try {
450
+ const document = dom.window.document;
451
+ const reader = new Readability(document);
452
+ const article = reader.parse();
453
+ if (article && article.content) {
454
+ const markdown = turndown.turndown(article.content);
455
+ const cleanMarkdown = markdown.replaceAll(/\n{3,}/g, `
2
456
 
3
- `).trim(),F=J.publishedTime||v(X)||"";return{title:J.title||X.title||Y,byline:J.byline||"",siteName:J.siteName||"",lang:J.lang||"",publishedTime:F,markdown:V,excerpt:V.slice(0,300).replaceAll(/\n/g," ")}}let Q=X.body;if(Q){let B=Q.cloneNode(!0);B.querySelectorAll("script, style, nav, footer, header, aside").forEach((q)=>q.remove());let F=(B.textContent||"").replaceAll(/\s+/g," ").trim();return{title:X.title||Y,byline:"",siteName:"",lang:"",publishedTime:v(X),markdown:F,excerpt:F.slice(0,300)}}return{title:Y,byline:"",siteName:"",lang:"",publishedTime:"",markdown:"",excerpt:""}}finally{W.window.close()}}function d(K){let Y=K.markdown.trim().toLowerCase(),j=(K.title||"").toLowerCase();if(K.markdown.trim().length<100)return{ok:!1,reason:"content too short (< 100 chars)"};let Z=Y.toLowerCase(),$=[{check:()=>Z.includes("loading")&&Z.includes("please wait"),desc:"loading page"},{check:()=>Z.includes("please ensure javascript is enabled"),desc:"requires javascript"},{check:()=>Z.includes("enable javascript to view"),desc:"requires javascript"},{check:()=>Z.includes("just a moment"),desc:"cloudflare challenge detected in content"},{check:()=>Z.includes("verify you are human"),desc:"human verification"},{check:()=>Z.includes("captcha required"),desc:"captcha in extracted content"},{check:()=>Z.includes("access denied"),desc:"access denied in content"},{check:()=>/^\s{0,10}sign\s{1,5}in\s{0,10}$|^\s{0,10}log\s{1,5}in\s{0,10}$/im.test(Y),desc:"login form only"}];for(let{check:W,desc:X}of $)if(W())return{ok:!1,reason:X};if(j.includes("just a moment")||j.includes("checking your browser"))return{ok:!1,reason:"cloudflare challenge page detected in title"};return{ok:!0}}function s(K){try{let Y=new URL(K),j=Y.hostname.toLowerCase(),Z=Y.pathname.toLowerCase();if(["react.dev","nextjs.org","vuejs.org","angular.io","svelte.dev","docs.expo.dev","tailwindcss.com","storybook.js.org"].some((W)=>j===W||j.endsWith(`.${W}`)))return!0;if(Z.includes("/playground")||Z.includes("/demo")||Z.includes("/app"))return!0;if(Y.hash&&Y.hash.length>1)return!0;return!1}catch{return!1}}export{s as shouldUseBrowser,x as rewriteGitHubUrl,b as isPrivateUrl,n as fetchSourceHttp,c as extractContent,T as detectBotBlock,o as defaultFetchHeaders,d as checkContentQuality};
457
+ `).trim();
458
+ const publishedTime = article.publishedTime || extractMetaDate(document) || "";
459
+ return {
460
+ title: article.title || document.title || url,
461
+ byline: article.byline || "",
462
+ siteName: article.siteName || "",
463
+ lang: article.lang || "",
464
+ publishedTime,
465
+ markdown: cleanMarkdown,
466
+ excerpt: cleanMarkdown.slice(0, 300).replaceAll(/\n/g, " ")
467
+ };
468
+ }
469
+ const body = document.body;
470
+ if (body) {
471
+ const clone = body.cloneNode(true);
472
+ clone.querySelectorAll("script, style, nav, footer, header, aside").forEach((el) => el.remove());
473
+ const text = clone.textContent || "";
474
+ const cleanText = text.replaceAll(/\s+/g, " ").trim();
475
+ return {
476
+ title: document.title || url,
477
+ byline: "",
478
+ siteName: "",
479
+ lang: "",
480
+ publishedTime: extractMetaDate(document),
481
+ markdown: cleanText,
482
+ excerpt: cleanText.slice(0, 300)
483
+ };
484
+ }
485
+ return {
486
+ title: url,
487
+ byline: "",
488
+ siteName: "",
489
+ lang: "",
490
+ publishedTime: "",
491
+ markdown: "",
492
+ excerpt: ""
493
+ };
494
+ } finally {
495
+ dom.window.close();
496
+ }
497
+ }
498
+ export function checkContentQuality(extracted) {
499
+ const markdown = extracted.markdown.trim().toLowerCase();
500
+ const title = (extracted.title || "").toLowerCase();
501
+ if (extracted.markdown.trim().length < 100) {
502
+ return { ok: false, reason: "content too short (< 100 chars)" };
503
+ }
504
+ const lc = markdown.toLowerCase();
505
+ const suspiciousChecks = [
506
+ {
507
+ check: () => lc.includes("loading") && lc.includes("please wait"),
508
+ desc: "loading page"
509
+ },
510
+ {
511
+ check: () => lc.includes("please ensure javascript is enabled"),
512
+ desc: "requires javascript"
513
+ },
514
+ {
515
+ check: () => lc.includes("enable javascript to view"),
516
+ desc: "requires javascript"
517
+ },
518
+ {
519
+ check: () => lc.includes("just a moment"),
520
+ desc: "cloudflare challenge detected in content"
521
+ },
522
+ {
523
+ check: () => lc.includes("verify you are human"),
524
+ desc: "human verification"
525
+ },
526
+ {
527
+ check: () => lc.includes("captcha required"),
528
+ desc: "captcha in extracted content"
529
+ },
530
+ {
531
+ check: () => lc.includes("access denied"),
532
+ desc: "access denied in content"
533
+ },
534
+ {
535
+ check: () => /^\s{0,10}sign\s{1,5}in\s{0,10}$|^\s{0,10}log\s{1,5}in\s{0,10}$/im.test(markdown),
536
+ desc: "login form only"
537
+ }
538
+ ];
539
+ for (const { check, desc } of suspiciousChecks) {
540
+ if (check()) {
541
+ return { ok: false, reason: desc };
542
+ }
543
+ }
544
+ if (title.includes("just a moment") || title.includes("checking your browser")) {
545
+ return { ok: false, reason: "cloudflare challenge page detected in title" };
546
+ }
547
+ return { ok: true };
548
+ }
549
+ export function shouldUseBrowser(url) {
550
+ try {
551
+ const parsed = new URL(url);
552
+ const hostname = parsed.hostname.toLowerCase();
553
+ const pathname = parsed.pathname.toLowerCase();
554
+ const jsHeavyDomains = [
555
+ "react.dev",
556
+ "nextjs.org",
557
+ "vuejs.org",
558
+ "angular.io",
559
+ "svelte.dev",
560
+ "docs.expo.dev",
561
+ "tailwindcss.com",
562
+ "storybook.js.org"
563
+ ];
564
+ if (jsHeavyDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`))) {
565
+ return true;
566
+ }
567
+ if (pathname.includes("/playground") || pathname.includes("/demo") || pathname.includes("/app")) {
568
+ return true;
569
+ }
570
+ if (parsed.hash && parsed.hash.length > 1) {
571
+ return true;
572
+ }
573
+ return false;
574
+ } catch {
575
+ return false;
576
+ }
577
+ }