website-api 1.0.6 → 1.1.2
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/dist/bin/cli.js +1 -1
- package/dist/src/capabilities/browser.d.ts +23 -0
- package/dist/src/capabilities/browser.js +1 -0
- package/dist/src/capabilities/cookies.d.ts +24 -0
- package/dist/src/capabilities/cookies.js +1 -0
- package/dist/src/capabilities/download.d.ts +11 -0
- package/dist/src/capabilities/download.js +1 -0
- package/dist/src/capabilities/fingerprint.d.ts +14 -0
- package/dist/src/capabilities/fingerprint.js +1 -0
- package/dist/src/capabilities/http.d.ts +18 -0
- package/dist/src/capabilities/http.js +1 -0
- package/dist/src/{util → capabilities/login}/login-helper.d.ts +12 -5
- package/dist/src/capabilities/login/login-helper.js +1 -0
- package/dist/src/capabilities/login/login-strategy.d.ts +15 -0
- package/dist/src/capabilities/login/login-strategy.js +1 -0
- package/dist/src/cli/ext.d.ts +3 -0
- package/dist/src/cli/ext.js +1 -0
- package/dist/src/core/context.d.ts +24 -0
- package/dist/src/core/context.js +1 -0
- package/dist/src/core/define-site.d.ts +9 -0
- package/dist/src/core/define-site.js +1 -0
- package/dist/src/core/loader.d.ts +21 -0
- package/dist/src/core/loader.js +1 -0
- package/dist/src/core/registry.d.ts +116 -0
- package/dist/src/core/registry.js +1 -0
- package/dist/src/core/runtime.d.ts +20 -0
- package/dist/src/core/runtime.js +1 -0
- package/dist/src/{website → sites}/chase.com/download-helper.d.ts +5 -4
- package/dist/src/sites/chase.com/download-helper.js +1 -0
- package/dist/src/sites/chase.com/index.d.ts +2 -0
- package/dist/src/sites/chase.com/index.js +1 -0
- package/dist/src/sites/chatgpt.com/index.d.ts +10 -0
- package/dist/src/sites/chatgpt.com/index.js +1 -0
- package/dist/src/sites/cursor.com/index.d.ts +6 -0
- package/dist/src/sites/cursor.com/index.js +1 -0
- package/dist/src/sites/gemini.google.com/index.d.ts +5 -0
- package/dist/src/sites/gemini.google.com/index.js +1 -0
- package/dist/src/sites/google.com/google-helpers.d.ts +24 -0
- package/dist/src/sites/google.com/google-helpers.js +1 -0
- package/dist/src/sites/google.com/index.d.ts +2 -0
- package/dist/src/sites/google.com/index.js +1 -0
- package/dist/src/sites/ollama.com/index.d.ts +9 -0
- package/dist/src/sites/ollama.com/index.js +1 -0
- package/dist/src/sites/perplexity.ai/index.d.ts +50 -0
- package/dist/src/sites/perplexity.ai/index.js +1 -0
- package/dist/src/sites/pseg.com/index.d.ts +2 -0
- package/dist/src/sites/pseg.com/index.js +1 -0
- package/dist/src/sites/pseg.com/pseg-helpers.d.ts +13 -0
- package/dist/src/sites/pseg.com/pseg-helpers.js +1 -0
- package/dist/src/types.d.ts +194 -46
- package/dist/src/util/args-parser.js +1 -1
- package/dist/src/website-api.d.ts +9 -34
- package/dist/src/website-api.js +1 -1
- package/package.json +10 -1
- package/dist/src/adapter/base-adapter.d.ts +0 -41
- package/dist/src/adapter/base-adapter.js +0 -1
- package/dist/src/adapter/playwright-attatch-chrome-adapter.d.ts +0 -16
- package/dist/src/adapter/playwright-attatch-chrome-adapter.js +0 -1
- package/dist/src/adapter/playwright-core.d.ts +0 -35
- package/dist/src/adapter/playwright-core.js +0 -1
- package/dist/src/adapter/universal-adapter.d.ts +0 -10
- package/dist/src/adapter/universal-adapter.js +0 -1
- package/dist/src/util/login-helper.js +0 -1
- package/dist/src/website/chase.com/account-helper.d.ts +0 -20
- package/dist/src/website/chase.com/account-helper.js +0 -1
- package/dist/src/website/chase.com/chase-adapter.d.ts +0 -43
- package/dist/src/website/chase.com/chase-adapter.js +0 -1
- package/dist/src/website/chase.com/download-helper.js +0 -1
- package/dist/src/website/chatgpt.com/chatgpt-adapter.d.ts +0 -11
- package/dist/src/website/chatgpt.com/chatgpt-adapter.js +0 -1
- package/dist/src/website/cursor.com/cursor-adapter.d.ts +0 -6
- package/dist/src/website/cursor.com/cursor-adapter.js +0 -1
- package/dist/src/website/example.com/example-adapter.d.ts +0 -12
- package/dist/src/website/example.com/example-adapter.js +0 -1
- package/dist/src/website/gemini.google.com/gemini-adapter.d.ts +0 -12
- package/dist/src/website/gemini.google.com/gemini-adapter.js +0 -1
- package/dist/src/website/google.com/google-adapter.d.ts +0 -62
- package/dist/src/website/google.com/google-adapter.js +0 -1
- package/dist/src/website/ollama.com/ollama-adapter.d.ts +0 -2
- package/dist/src/website/ollama.com/ollama-adapter.js +0 -1
- package/dist/src/website/perplexity.ai/perplexity-adapter.d.ts +0 -2
- package/dist/src/website/perplexity.ai/perplexity-adapter.js +0 -1
- package/dist/src/website/pseg.com/pseg-adapter.d.ts +0 -45
- package/dist/src/website/pseg.com/pseg-adapter.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function formEncode(t){let e="";const r=String(t??"");for(let t=0;t<r.length;t++){const n=r.codePointAt(t);if(void 0===n)continue;const o=String.fromCodePoint(n);if(n>65535&&t++," "===o)e+="+";else if("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~".includes(o))e+=o;else for(const t of utf8Bytes(n))e+=`%${t.toString(16).toUpperCase().padStart(2,"0")}`}return e}export function utf8Bytes(t){return t<=127?[t]:t<=2047?[192|t>>6,128|63&t]:t<=65535?[224|t>>12,128|t>>6&63,128|63&t]:[240|t>>18,128|t>>12&63,128|t>>6&63,128|63&t]}export function utf8String(t){let e="";for(let r=0;r<t.length;r++){const n=t[r];let o=n;192==(224&n)?o=(31&n)<<6|63&t[++r]:224==(240&n)?o=(15&n)<<12|(63&t[++r])<<6|63&t[++r]:240==(248&n)&&(o=(7&n)<<18|(63&t[++r])<<12|(63&t[++r])<<6|63&t[++r]),e+=String.fromCodePoint(o)}return e}export function percentDecode(t){const e=String(t??"").replace(/\+/g," ");let r="";for(let t=0;t<e.length;t++){if("%"!==e[t]||!/[0-9a-fA-F]{2}/.test(e.slice(t+1,t+3))){r+=e[t];continue}const n=[];for(;"%"===e[t]&&/[0-9a-fA-F]{2}/.test(e.slice(t+1,t+3));)n.push(Number.parseInt(e.slice(t+1,t+3),16)),t+=3;t--;try{r+=utf8String(n)}catch{r+=n.map(t=>`%${t.toString(16).toUpperCase().padStart(2,"0")}`).join("")}}return r}export function parseQueryString(t){const e={};for(const r of String(t??"").replace(/^\?/,"").split("&")){if(!r)continue;const t=r.indexOf("="),n=-1===t?r:r.slice(0,t),o=-1===t?"":r.slice(t+1);e[percentDecode(n)]=percentDecode(o)}return e}export function buildQueryString(t){return Object.entries(t).filter(([,t])=>null!=t&&""!==t).map(([t,e])=>`${formEncode(t)}=${formEncode(e)}`).join("&")}export function googlePath(t){const e=/^https?:\/\/([^/]+)(\/[^?#]*)?/i.exec(String(t??""));return e&&/(^|\.)google\.[^/]+$/i.test(e[1])?e[2]||"/":null}export function stripXssi(t){return t.replace(/^\s*\)\]\}'\s*\n?/,"")}export function parseJsonMaybe(t){try{return JSON.parse(t)}catch{return}}export function parseGoogleRecordStream(t){const e=[];for(const r of stripXssi(t).split(/\r?\n/)){const t=r.trim();if(!t)continue;const n=/^([a-zA-Z0-9_-]+);(.*)$/.exec(t);if(!n)continue;const o=n[2].trim();e.push({id:n[1],value:parseJsonMaybe(o)??o})}return e}export function decodeGoogleBody({body:t,contentType:e=""}){const r=String(t??""),n=stripXssi(r).trimStart(),o=/^\s*\)\]\}'/.test(r),i=/^[\[{]/.test(n)?parseJsonMaybe(n):void 0,s=void 0===i?parseGoogleRecordStream(r):[];let c="text";return(e.includes("html")||/^\s*</.test(n))&&(c="html"),o&&void 0!==i?c="google-xssi-json":o&&s.length?c="google-xssi-record-stream":void 0!==i?c="json":s.length&&(c="google-record-stream"),{format:c,xssiPrefixed:o,parsed:i,records:s}}export function cleanText(t,e=4e3){return String(t??"").replace(/\u0000/g,"").replace(/[ \t]+\n/g,"\n").trim().slice(0,e)}export function extractAnswerFromText(t){const e=String(t??"").split("\n").map(t=>t.trim()).filter(Boolean),r=e.findIndex(t=>/AI Mode response is ready/i.test(t));if(r>0)return e[r-1];const n=/^(Skip to main content|Accessibility help|Accessibility feedback|AI Mode|All|Images|Videos|News|More|Search Results|Sources|Related)$/i;return e.find(t=>!n.test(t))??null}export function cleanHtml(t){let e=t.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").replace(/ /g," ");return e=function(t){let e="",r=0;for(;r<t.length;){const n=t.toLowerCase().indexOf("<style",r);if(-1===n){e+=t.slice(r);break}e+=t.slice(r,n);const o=t.toLowerCase().indexOf("</style>",n);if(-1===o)break;r=o+8}let n="";for(r=0;r<e.length;){const t=e.toLowerCase().indexOf("<script",r);if(-1===t){n+=e.slice(r);break}n+=e.slice(r,t);const o=e.toLowerCase().indexOf("<\/script>",t);if(-1===o)break;r=o+9}return n}(e),e=e.replace(/<[^>]+>/g," "),e.replace(/\s+/g," ").trim()}export function findHtmlInObject(t){if(!t)return null;if("string"==typeof t){const e=t.trim();return e.startsWith("<")||e.includes("class=")||e.includes("id=")?t:null}if(Array.isArray(t))for(const e of t){const t=findHtmlInObject(e);if(t)return t}else if("object"==typeof t){if("string"==typeof t.html)return t.html;if("string"==typeof t.aimc_block?.html)return t.aimc_block.html;if("string"==typeof t.value&&(t.value.startsWith("<")||t.value.includes("class=")))return t.value;for(const e of Object.values(t)){const t=findHtmlInObject(e);if(t)return t}}return null}export function extractAnswerFromRecordStream(t){try{const e=stripXssi(t).trimStart();if(e.startsWith("<")||e.includes("class=")||e.includes("id="))return cleanHtml(e);for(const e of parseGoogleRecordStream(t)){const t=findHtmlInObject(e.value);if(t)return cleanHtml(t)}}catch{}return null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineSite as e}from"../../core/define-site.js";import{buildQueryString as t,cleanText as o,decodeGoogleBody as n,extractAnswerFromRecordStream as r,extractAnswerFromText as s,formEncode as i,googlePath as a,parseQueryString as l}from"./google-helpers.js";function u(e,t){return e.map(e=>{const o=null==e.body?null:n({body:e.body,contentType:e.mimeType});return{method:e.method,type:e.type,url:e.url,status:e.status,mimeType:e.mimeType,decodedFormat:o?.format??null,recordCount:o?.records?.length??0,bodyPrefix:null==e.body?null:e.body.slice(0,t)}})}export default e({id:"google-ai",name:"Google AI Overview",domain:"google.com",description:"Fetches Google's AI Overview and AI Mode answers using browser-attached Playwright.",transport:"browser",cookies:"optional",endpoints:[{url:"https://www.google.com"}],positionals:[{name:"question",description:"The search query or question to ask Google",required:!0,variadic:!0}],parameters:[{name:"raw-limit",type:"number",description:"Max raw response chars to include",default:12e3},{name:"timeout",type:"number",description:"Playwright timeout in milliseconds",default:9e4},{name:"text",type:"boolean",description:"Print only the extracted AI Overview answer text",short:"t"}],run:async e=>{const d=e.options.question,c=void 0!==e.options.rawLimit?Number(e.options.rawLimit):12e3,m=void 0!==e.options.timeout?Number(e.options.timeout):9e4,p=await e.browser(),y=await p.context().newCDPSession(p);await y.send("Network.enable",{maxTotalBufferSize:1e8,maxResourceBufferSize:1e8});const w=[],g=new Map;y.on("Network.requestWillBeSent",e=>{const t=e.request||{};(e=>{const t=a(e);return"/search"===t||t?.startsWith("/async/")||t?.includes("batchexecute")})(t.url)&&(g.set(e.requestId,w.length),w.push({id:e.requestId,type:e.type,method:t.method,url:t.url,postData:t.postData||null,status:null,mimeType:null,body:null}))}),y.on("Network.responseReceived",e=>{const t=g.get(e.requestId);null!=t&&(w[t].status=e.response.status,w[t].mimeType=e.response.mimeType)}),y.on("Network.loadingFinished",async e=>{const t=g.get(e.requestId);if(null!=t)try{const o=w[t].mimeType||"";if(!/text|json|html|javascript|x-protobuf/.test(o))return;const n=await y.send("Network.getResponseBody",{requestId:e.requestId});w[t].body=n.base64Encoded?null:(n.body||"").slice(0,c)}catch{}});const f=`https://www.google.com/search?${t({q:d,udm:"50"})}`;e.debug&&console.log(`Navigating to Google Search: ${f}`),await p.goto(f,{waitUntil:"domcontentloaded"});const h=await async function(e,t){const o=Date.now();for(;Date.now()-o<Math.min(t,45e3);){await e.waitForTimeout(750);const t=await e.evaluate(()=>{const e=Array.from(document.querySelectorAll('[jsname="KFl8ub"], [data-attrid], .kp-wholepage')).map(e=>e.innerText?.trim()).filter(Boolean).find(e=>!/^(Sources|Related|AI Mode response is ready)$/i.test(e));if(e)return e;const t=(document.body?.innerText||"").split("\n").map(e=>e.trim()).filter(Boolean),o=t.findIndex(e=>/AI Mode response is ready/i.test(e));return o>0?t[o-1]:null});if(t)return t}return null}(p,m),b=await async function(e,t){return e.evaluate(e=>{const t=document.body?.innerText||"",o=document.documentElement?.outerHTML||"";return{title:document.title,url:document.location.href,bodyText:t.slice(0,e),htmlPrefix:o.slice(0,e)}},t)}(p,c),x=await async function(e){const o=await e.evaluate(()=>{const e=document.querySelector("[data-garc][data-lro-token][data-lro-signature][data-ei]");if(!e)return{url:null,error:"Missing AI Mode token container"};const t=document.getElementById("rKxeg")?.getAttribute("data-stkp")||null;return{origin:document.location.origin,search:document.location.search,stkp:t,fmt:document.querySelector("[data-madl]")?"madl":"adl",tokens:{ei:e.dataset.ei,garc:e.dataset.garc,lroToken:e.dataset.lroToken,lroSignature:e.dataset.lroSignature,xsrfFolwrToken:e.dataset.xsrfFolwrToken||null,srtst:e.dataset.srtst||null}}});if(!o?.origin)return o;const n=l(o.search),r={},s=["q","udm","mstk","csuir","mtid","ved","vet","sei","dpr","hl","gl","source","vsrid","lns_img","cinpts"];for(const e of s)n[e]&&(r[e]=n[e]);o.tokens.srtst&&(r.srtst=o.tokens.srtst),r.garc=o.tokens.garc,r.mlro=o.tokens.lroToken,r.mlros=o.tokens.lroSignature,r.ei=o.tokens.ei,o.stkp&&(r.stkp=o.stkp);const a={_fmt:o.fmt};o.tokens.xsrfFolwrToken&&(a._xsrf=o.tokens.xsrfFolwrToken);const u=t(r),d=Object.entries(a).map(([e,t])=>`${i(e)}:${i(t)}`).join(",");return{url:`${o.origin}/async/folwr?${u}&async=${d}`,tokens:o.tokens}}(p);let k=null;if(x?.url){e.debug&&console.log(`Discovered folwr endpoint: ${x.url}`);try{const e=await p.evaluate(async e=>(await fetch(e)).text(),x.url),t=r(e),o=s(e),i=!o||o.includes("<")||o.includes("class=")?null:o;k={title:b.title,url:x.url,bodyText:e,htmlPrefix:e.slice(0,c),answer:t||i,decoded:n({body:e,contentType:"text/plain"})}}catch(t){e.debug&&console.warn("Failed to query folwr endpoint in-page:",t)}}await p.waitForTimeout(500);const T=k?.answer||h||s(b.bodyText);return{question:d,answer:o(T,c)||null,finalUrl:p.url(),endpoint:x||null,searchPage:{title:b.title,url:b.url,bodyText:o(b.bodyText,c)},endpointResult:k?{title:k.title,url:k.url,bodyText:o(k.bodyText,c),htmlPrefix:k.htmlPrefix,decoded:k.decoded}:null,requests:u(w,c)}}});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function extractPlan(html: string): string;
|
|
2
|
+
export declare function extractUsage(html: string, label: string): {
|
|
3
|
+
usage: string;
|
|
4
|
+
reset: string;
|
|
5
|
+
};
|
|
6
|
+
/** Parses the Ollama settings HTML into a usage summary. Pure + testable. */
|
|
7
|
+
export declare function parseOllamaUsage(html: string): Record<string, string>;
|
|
8
|
+
declare const _default: import("../../types.js").Site;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineSite as e}from"../../core/define-site.js";export function extractPlan(e){return e.match(/Cloud Usage[\s\S]*?<\/span>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span/i)?.[1]?.trim()??"unknown"}export function extractUsage(e,a){const s=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const t=new RegExp(`<div[\\s\\S]*?<span[^>]*>\\s*${s}\\s*<\\/span>[\\s\\S]*?aria-label="${s}\\s+([^"]+)"[\\s\\S]*?data-time="([^"]+)"`,"i"),n=e.match(t);return{usage:n?.[1]?.replace(/\s+used$/i,"").trim()??"unknown",reset:n?.[2]?.trim()??"unknown"}}export function parseOllamaUsage(e){const a=extractUsage(e,"Session usage"),s=extractUsage(e,"Weekly usage");return{time:(new Date).toISOString(),Plan:extractPlan(e),"Session Usage":a.usage,"Session Reset":a.reset,"Weekly Usage":s.usage,"Weekly Reset":s.reset}}export default e({id:"ollama-usage",name:"Ollama Usage",domain:"ollama.com",description:"Fetches Ollama plan and usage details from the authenticated settings page.",endpoints:[{url:"https://ollama.com/settings",responseType:"html",transform:e=>parseOllamaUsage("string"==typeof e?e:String(e))}]});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare function applyPatchOne(doc: any, patch: any): any;
|
|
2
|
+
export declare function applyBlockState(state: any, data: any): void;
|
|
3
|
+
export declare function extractAnswer(final: any, state: any): any;
|
|
4
|
+
/** Reduces a list of parsed SSE frames into the final answer + state. */
|
|
5
|
+
export declare function reduceFrames(frames: any[]): {
|
|
6
|
+
state: any;
|
|
7
|
+
final: any;
|
|
8
|
+
answer: any;
|
|
9
|
+
chunks: any[];
|
|
10
|
+
};
|
|
11
|
+
export declare function makeDefaultBody(): {
|
|
12
|
+
params: {
|
|
13
|
+
attachments: never[];
|
|
14
|
+
language: string;
|
|
15
|
+
timezone: string;
|
|
16
|
+
search_focus: string;
|
|
17
|
+
sources: string[];
|
|
18
|
+
frontend_uuid: `${string}-${string}-${string}-${string}-${string}`;
|
|
19
|
+
mode: string;
|
|
20
|
+
model_preference: string;
|
|
21
|
+
is_related_query: boolean;
|
|
22
|
+
is_sponsored: boolean;
|
|
23
|
+
frontend_context_uuid: `${string}-${string}-${string}-${string}-${string}`;
|
|
24
|
+
prompt_source: string;
|
|
25
|
+
query_source: string;
|
|
26
|
+
is_incognito: boolean;
|
|
27
|
+
time_from_first_type: number;
|
|
28
|
+
local_search_enabled: boolean;
|
|
29
|
+
use_schematized_api: boolean;
|
|
30
|
+
send_back_text_in_streaming_api: boolean;
|
|
31
|
+
supported_block_use_cases: string[];
|
|
32
|
+
client_coordinates: null;
|
|
33
|
+
mentions: never[];
|
|
34
|
+
dsl_query: string;
|
|
35
|
+
skip_search_enabled: boolean;
|
|
36
|
+
is_nav_suggestions_disabled: boolean;
|
|
37
|
+
source: string;
|
|
38
|
+
always_search_override: boolean;
|
|
39
|
+
override_no_search: boolean;
|
|
40
|
+
should_ask_for_mcp_tool_confirmation: boolean;
|
|
41
|
+
browser_agent_allow_once_from_toggle: boolean;
|
|
42
|
+
force_enable_browser_agent: boolean;
|
|
43
|
+
supported_features: string[];
|
|
44
|
+
extended_context: boolean;
|
|
45
|
+
version: string;
|
|
46
|
+
};
|
|
47
|
+
query_str: string;
|
|
48
|
+
};
|
|
49
|
+
declare const _default: import("../../types.js").Site;
|
|
50
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{defineSite as t}from"../../core/define-site.js";const r="https://www.perplexity.ai/rest/sse/perplexity_ask";function n(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}export function applyPatchOne(e,t){const r=t.op,o=t.path??"";if(""===o){if("replace"===r||"add"===r)return t.value;if("remove"===r)return null}const s=o.split("/").slice(1).map(n);null==e&&(e=/^\d+$/.test(s[0]??"")?[]:{});let i=e;for(let e=0;e<s.length-1;e++){const t=s[e],r=/^\d+$/.test(s[e+1]??"");if(Array.isArray(i)){const e=Number(t);for(;i.length<=e;)i.push(r?[]:{});i=i[e]}else i[t]??=r?[]:{},i=i[t]}const a=s.at(-1);if(null==a)return e;if(Array.isArray(i)){const e="-"===a?i.length:Number(a);"add"===r?i.splice(e,0,t.value):"replace"===r?i[e]=t.value:"remove"===r&&i.splice(e,1)}else"remove"===r?delete i[a]:"add"!==r&&"replace"!==r||(i[a]=t.value);return e}export function applyBlockState(e,t){for(const r of t?.blocks??[]){const t=r.intended_usage;for(const[n,o]of Object.entries(r))"intended_usage"!==n&&"diff_block"!==n&&t&&(e[t]=o);const n=r.diff_block;if(n?.field){e[n.field]??=null;for(const t of n.patches??[])e[n.field]=applyPatchOne(e[n.field],t)}}}export function extractAnswer(e,t){for(const t of e?.blocks??[]){const e=t.markdown_block;if(null!=e?.answer)return e.answer;if(Array.isArray(e?.chunks))return e.chunks.join("")}for(const e of["ask_text","ask_text_0_markdown","markdown_block"]){const r=t[e];if(null!=r?.answer)return r.answer;if(Array.isArray(r?.chunks))return r.chunks.join("")}return null}export function reduceFrames(e){const t={};let r=null;for(const n of e)applyBlockState(t,n),(n.final||"COMPLETED"===n.status||n.text_completed)&&(r=n);return!r&&e.length&&(r=e[e.length-1]),{state:t,final:r,answer:extractAnswer(r,t),chunks:e}}export function makeDefaultBody(){return{params:{attachments:[],language:"en-US",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"America/New_York",search_focus:"internet",sources:["web"],frontend_uuid:e(),mode:"copilot",model_preference:"claude46sonnet",is_related_query:!1,is_sponsored:!1,frontend_context_uuid:e(),prompt_source:"user",query_source:"home",is_incognito:!1,time_from_first_type:1,local_search_enabled:!1,use_schematized_api:!0,send_back_text_in_streaming_api:!1,supported_block_use_cases:["answer_modes","media_items","knowledge_cards","inline_entity_cards","place_widgets","finance_widgets","prediction_market_widgets","sports_widgets","flight_status_widgets","news_widgets","shopping_widgets","jobs_widgets","search_result_widgets","inline_images","inline_assets","placeholder_cards","diff_blocks","inline_knowledge_cards","entity_group_v2","refinement_filters","canvas_mode","maps_preview","answer_tabs","price_comparison_widgets","preserve_latex","generic_onboarding_widgets","in_context_suggestions","pending_followups","inline_claims","unified_assets","workflow_steps","background_agents"],client_coordinates:null,mentions:[],dsl_query:"",skip_search_enabled:!0,is_nav_suggestions_disabled:!1,source:"default",always_search_override:!1,override_no_search:!1,should_ask_for_mcp_tool_confirmation:!0,browser_agent_allow_once_from_toggle:!1,force_enable_browser_agent:!1,supported_features:["browser_agent_permission_banner_v1.1"],extended_context:!1,version:"2.18"},query_str:""}}export default t({id:"perplexity",name:"Perplexity AI Ask",domain:"perplexity.ai",description:"Fetches live streaming answers from Perplexity AI using its private REST/SSE API.",positionals:[{name:"question",description:"The query or question to ask Perplexity AI",required:!0,variadic:!0}],parameters:[{name:"model",type:"string",description:"Model preference (e.g. 'claude46sonnet')",default:"claude46sonnet",short:"m"},{name:"out",type:"string",description:"Write decoded response JSON to file instead of stdout"},{name:"timeout",type:"number",description:"Request timeout in milliseconds",default:75e3},{name:"text",type:"boolean",description:"Print only the extracted text answer",short:"t"}],run:async t=>{const n=t.options.question,o=t.options.model||"claude46sonnet",s=void 0!==t.options.timeout?Number(t.options.timeout):75e3;if(0===t.cookies().length)throw new Error("No login found in browser. Please log in to perplexity.ai in Google Chrome.");const i=makeDefaultBody();let a;i.params.frontend_uuid=e(),i.params.frontend_context_uuid=e(),i.params.model_preference=o,i.params.dsl_query=n,i.params.time_from_first_type=1,i.query_str=n;try{a=await t.http.sse(r,{method:"POST",headers:{"content-type":"application/json",origin:"https://www.perplexity.ai",referer:"https://www.perplexity.ai/","x-perplexity-request-endpoint":r,"x-request-id":i.params.frontend_uuid},body:JSON.stringify(i),signal:AbortSignal.timeout(s)})}catch(e){if(e instanceof Error&&("AbortError"===e.name||"TimeoutError"===e.name))throw new Error(`Perplexity request timed out after ${s}ms.`);throw e}const l=reduceFrames(a.frames);return{endpoint:r,http_code:a.status,content_type:a.contentType,request:{query:n,model_preference:o,frontend_uuid:i.params.frontend_uuid,frontend_context_uuid:i.params.frontend_context_uuid},answer:l.answer,state:l.state,final:l.final,chunks:l.chunks}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineSite as t}from"../../core/define-site.js";import{formatPropertyLabel as e,inferServiceTypeFromPropertyTitle as o,intervalToValue as r,normalizeInterval as a}from"./pseg-helpers.js";const i="https://mysmartenergy.nj.pseg.com/Dashboard";async function n(t,e){if(!e.url().toLowerCase().startsWith("https://mysmartenergy.nj.pseg.com/dashboard")){t.debug&&console.log(`Navigating to Dashboard from '${e.url()}'...`),await e.goto(i,{waitUntil:"domcontentloaded"});await e.locator("#LoginEmail").isVisible().catch(()=>!1)&&(t.debug&&console.log("Session expired or redirected to login. Re-authenticating..."),t.site.auth&&await t.site.auth.ensureLoggedIn({page:e,debug:t.debug,getCredentials:t.credentials}))}}async function s(t,e){return await async function(t,e){if(await n(t,e),!await e.locator('.selectPropertyContainer input[placeholder="Search"]').isVisible().catch(()=>!1)){const t=e.getByRole("link",{name:"Select Property"});await t.waitFor({state:"visible",timeout:5e3}).catch(()=>{});try{await t.click()}catch{await e.locator('a:has-text("Select Property")').first().click()}await e.waitForSelector(".selectPropertyContainer",{state:"visible",timeout:5e3})}}(t,e),e.evaluate(()=>Array.from(document.querySelectorAll(".selectPropertyContainer li")).map(t=>{const e=Array.from(t.querySelectorAll("h4"));return{propertyId:t.getAttribute("data-property-id")||"",propertyType:t.getAttribute("data-property-type")||"",title:t.querySelector("h2")?.textContent?.trim()||"",owner:e[0]?.textContent?.trim()||"",address:e[1]?.textContent?.trim()||"",isCurrent:t.classList.contains("current")}}))}async function l(t,e,o){return t.evaluate(({selector:t,value:e})=>{const o=document.querySelector(t);if(!o)throw new Error(`${t} select not found`);return o.value=e,o.dispatchEvent(new Event("change",{bubbles:!0})),{value:o.value}},{selector:`select[name="${e}"]`,value:o})}async function c(t,e,o){const a="Gas"===e?"4":"1";if("Gas"===e&&"Billing"!==o)throw new Error("Gas usage only supports the Billing interval.");const i=r(o);if(!i)throw new Error(`Unsupported interval mapping: ${o}`);await t.waitForSelector('select[name="SelectedServiceType"]',{state:"attached",timeout:5e3});const n=await l(t,"SelectedServiceType",a);if(n.value!==a)throw new Error(`Failed to set service type: ${JSON.stringify(n)}`);await t.waitForTimeout(500),await t.waitForSelector('select[name="SelectedInterval"]',{state:"attached",timeout:5e3});const s=await async function(t,e){return t.evaluate(t=>{const e=document.querySelector(t);if(!e)throw new Error(`${t} select not found`);return{value:e.value,options:Array.from(e.options).map(t=>({value:t.value,text:t.textContent?.trim()||""}))}},`select[name="${e}"]`)}(t,"SelectedInterval");if(!new Set(s.options.map(t=>t.value)).has(i)){const t=s.options.map(t=>t.text).join(", ");throw new Error(`Interval ${o} is not available for ${e}. Available: ${t}`)}const c=s.value===i?s:await l(t,"SelectedInterval",i);if(c.value!==i)throw new Error(`Failed to set interval: ${JSON.stringify(c)}`);return o}export default t({id:"pseg-usage",name:"PSEG Usage",domain:"mysmartenergy.nj.pseg.com",description:"Downloads PSEG Smart Energy usage data (CSV) or lists available properties.",transport:"browser",cookies:"optional",keepBrowserOpen:!0,auth:{intendedUrl:i,emailSelector:"#LoginEmail",passwordSelector:"#LoginPassword",submitButtonSelector:"button.loginBtn",delayMs:1e3},positionals:[{name:"property",description:"Property name (e.g. '100 Electric') or 1-based index.",required:!1}],parameters:[{name:"interval",type:"string",description:"15, 30, hourly, daily, weekly, monthly, billing",default:"billing",short:"i"},{name:"list",type:"boolean",description:"List all downloadable properties instead of downloading",short:"l"}],run:async t=>{const r=await t.browser();if(t.options.list){const o=await s(t,r),a=["Downloadable properties:"];for(const t of o){const o=t.isCurrent?" [current]":"";a.push(`${e(t)}${o} | ${t.address} | ${t.owner}`)}return a.join("\n")}const i=t.options.property;if(!i)throw new Error("Missing required argument: <property> or --list");const l=a(t.options.interval),u=await s(t,r),d=String(i).trim();let w;if(/^\d+$/.test(d)){const t=Number(d);if(t<1||t>u.length)throw new Error(`Property index ${t} is out of range. Use 1-${u.length}.`);w=u[t-1]}else{const t=d.toLowerCase();if(w=u.find(e=>e.title.trim().toLowerCase()===t)??u.find(o=>e(o).toLowerCase()===t),!w){const t=u.map(t=>e(t)).filter(Boolean).join(", ");throw new Error(`Property not found: ${i}. Available properties: ${t}`)}}const p=await async function(t,r,a){return await r.goto(`https://mysmartenergy.nj.pseg.com/Dashboard/SetMeterGroup?meterGroupId=${a.propertyId}`,{waitUntil:"domcontentloaded"}),await r.waitForTimeout(1e3),{label:e(a),title:a.title,serviceType:o(a.title)}}(0,r,w);return t.debug&&console.log(`Downloading ${p.label} usage CSV for ${l}...`),await n(t,r),await async function(t,e){const o="#downloadOptions";if(!await t.locator(o).isVisible().catch(()=>!1)){e&&console.log("Clicking 'Data' link...");try{await t.getByRole("link",{name:"Data"}).click()}catch{await t.locator('a:has-text("Data")').first().click()}try{return void await t.waitForSelector(o,{state:"visible",timeout:5e3})}catch{}try{await t.getByRole("link",{name:"download"}).click()}catch{await t.locator('a:has-text("download")').first().click()}await t.waitForSelector(o,{state:"visible",timeout:1e4})}}(r,t.debug),await c(r,p.serviceType,l),async function(t){return t.evaluate(async()=>{const t=document.querySelector("#downloadOptions");if(!t)throw new Error("Download form not found");const e=await fetch(t.action,{method:"POST",body:new FormData(t),credentials:"same-origin"});if(!e.ok)throw new Error(`Download request failed with status ${e.status} ${e.statusText}`);return e.text()})}(r)}});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface PsegProperty {
|
|
2
|
+
propertyId: string;
|
|
3
|
+
propertyType: string;
|
|
4
|
+
title: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
address: string;
|
|
7
|
+
isCurrent: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function normalizeInterval(value: string): string;
|
|
10
|
+
export declare function intervalToValue(interval: string): string;
|
|
11
|
+
export declare function inferServiceTypeFromPropertyTitle(title: string): "Gas" | "Electric";
|
|
12
|
+
export declare function extractAddressNumber(address: string): string;
|
|
13
|
+
export declare function formatPropertyLabel(property: Omit<PsegProperty, "isCurrent">): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={15:"15-Minute","15-minute":"15-Minute",30:"30-Minute","30-minute":"30-Minute",hourly:"Hourly",daily:"Daily",weekly:"Weekly",monthly:"Monthly",billing:"Billing"},r={"15-Minute":"3","30-Minute":"4",Hourly:"5",Daily:"6",Weekly:"8",Monthly:"9",Billing:"7"};export function normalizeInterval(r){const t=String(r||"").trim().toLowerCase(),n=e[t];if(!n)throw new Error(`Invalid interval: ${r}. Use 15, 30, hourly, daily, weekly, monthly, or billing.`);return n}export function intervalToValue(e){return r[e]}export function inferServiceTypeFromPropertyTitle(e){if(/\bgas\b/i.test(e))return"Gas";if(/\belectric\b/i.test(e))return"Electric";throw new Error(`Could not infer service type from property title: ${e}`)}export function extractAddressNumber(e){const r=String(e||"").match(/^(\d+)/);return r?r[1]:""}export function formatPropertyLabel(e){const r=String(e.title||"").trim();if(r)return r;const t=e.propertyType||inferServiceTypeFromPropertyTitle(e.title);return`${extractAddressNumber(e.address)||e.propertyId||"Unknown"} ${t}`}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CookieEntry } from "chrome-tools";
|
|
2
|
+
import type { Page } from "playwright-core";
|
|
2
3
|
export interface ParameterDefinition {
|
|
3
4
|
/** Option name (e.g. "model", "timeout"). Matches option flag `--<name>`. */
|
|
4
5
|
name: string;
|
|
@@ -23,8 +24,137 @@ export interface PositionalDefinition {
|
|
|
23
24
|
/** If true, collects all remaining positional arguments. */
|
|
24
25
|
variadic?: boolean;
|
|
25
26
|
}
|
|
27
|
+
/** Resolved Chrome credentials for the target site. */
|
|
28
|
+
export interface Credentials {
|
|
29
|
+
username: string;
|
|
30
|
+
password: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Context handed to a login strategy by the framework.
|
|
34
|
+
*
|
|
35
|
+
* `getCredentials` is a lazy thunk: it is only invoked once the strategy decides
|
|
36
|
+
* a login is actually required, so an already-authenticated session never
|
|
37
|
+
* triggers a credential lookup (and never fails when no password is saved).
|
|
38
|
+
*/
|
|
39
|
+
export interface LoginContext {
|
|
40
|
+
page: Page;
|
|
41
|
+
debug?: boolean;
|
|
42
|
+
getCredentials: () => Credentials;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A pluggable authentication approach. Implementations must be idempotent:
|
|
46
|
+
* detect the current session state and only log in when needed.
|
|
47
|
+
*/
|
|
48
|
+
export interface LoginStrategy {
|
|
49
|
+
/** @returns true if a login was performed, false if already logged in. */
|
|
50
|
+
ensureLoggedIn(ctx: LoginContext): Promise<boolean>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Declarative configuration for a standard single-page username/password form.
|
|
54
|
+
* Only selectors / URLs — no credentials, no business logic. A site may pass
|
|
55
|
+
* this plain object as `auth`; the framework wraps it in a FormLoginStrategy.
|
|
56
|
+
*/
|
|
57
|
+
export interface FormLoginConfig {
|
|
58
|
+
/** Discriminator so a config object is distinguishable from a strategy. */
|
|
59
|
+
type?: "form";
|
|
60
|
+
/** URL to land on before checking session state (e.g. the dashboard). */
|
|
61
|
+
intendedUrl?: string;
|
|
62
|
+
/** Optional explicit login page URL. */
|
|
63
|
+
loginUrl?: string;
|
|
64
|
+
/** Primary username/email input selector. */
|
|
65
|
+
emailSelector: string;
|
|
66
|
+
/** Primary password input selector. */
|
|
67
|
+
passwordSelector: string;
|
|
68
|
+
/** Submit button selector. */
|
|
69
|
+
submitButtonSelector: string;
|
|
70
|
+
/** Site-specific fallback selectors, tried after the primary if not visible. */
|
|
71
|
+
usernameSelectors?: string[];
|
|
72
|
+
passwordSelectors?: string[];
|
|
73
|
+
submitSelectors?: string[];
|
|
74
|
+
/** Selector used to detect a visible login form (defaults to a password input). */
|
|
75
|
+
pwdSelector?: string;
|
|
76
|
+
/** Selectors that, when visible, indicate an already-authenticated dashboard. */
|
|
77
|
+
dashboardSelectors?: string[];
|
|
78
|
+
/** Delay before submitting, in ms. */
|
|
79
|
+
delayMs?: number;
|
|
80
|
+
/** URL pattern to await after submit (defaults to intendedUrl). */
|
|
81
|
+
expectedRedirectUrlPattern?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface FingerprintConfig {
|
|
84
|
+
/** Override navigator.userAgent / UA string used by the browser session. */
|
|
85
|
+
userAgent?: string;
|
|
86
|
+
/** navigator.languages (e.g. ["en-US", "en"]). */
|
|
87
|
+
languages?: string[];
|
|
88
|
+
/** navigator.platform (e.g. "MacIntel"). */
|
|
89
|
+
platform?: string;
|
|
90
|
+
/** Hardware concurrency reported to the page. */
|
|
91
|
+
hardwareConcurrency?: number;
|
|
92
|
+
/** Device memory (GB) reported to the page. */
|
|
93
|
+
deviceMemory?: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* `"stealth"` applies the default anti-bot evasion profile, `false` disables all
|
|
97
|
+
* fingerprint shaping, and an object customizes individual fields.
|
|
98
|
+
*/
|
|
99
|
+
export type FingerprintOption = "stealth" | false | FingerprintConfig;
|
|
100
|
+
export interface SSEResult {
|
|
101
|
+
status: number;
|
|
102
|
+
contentType: string;
|
|
103
|
+
/** Parsed JSON payload of every `data:` frame (non-JSON frames are skipped). */
|
|
104
|
+
frames: any[];
|
|
105
|
+
/** The raw, undecoded stream body. */
|
|
106
|
+
raw: string;
|
|
107
|
+
}
|
|
108
|
+
export interface HttpCapability {
|
|
109
|
+
/** GET (or `init.method`) and parse JSON. Cookie + User-Agent auto-injected. */
|
|
110
|
+
json(url: string, init?: RequestInit): Promise<any>;
|
|
111
|
+
/** Fetch and return the response body as text. */
|
|
112
|
+
text(url: string, init?: RequestInit): Promise<string>;
|
|
113
|
+
/** Alias of `text`, semantically for HTML documents. */
|
|
114
|
+
html(url: string, init?: RequestInit): Promise<string>;
|
|
115
|
+
/** Fetch a Server-Sent-Events stream and collect its frames. */
|
|
116
|
+
sse(url: string, init?: RequestInit): Promise<SSEResult>;
|
|
117
|
+
/** Escape hatch: the raw Response plus its already-read text body. */
|
|
118
|
+
raw(url: string, init?: RequestInit): Promise<{
|
|
119
|
+
response: Response;
|
|
120
|
+
text: string;
|
|
121
|
+
}>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The single object every site's `run()` receives. Each capability is lazy:
|
|
125
|
+
* touch `ctx.browser()` and Chrome is launched; never touch it and it isn't.
|
|
126
|
+
* The runtime owns setup and teardown — sites never manage lifecycle.
|
|
127
|
+
*/
|
|
128
|
+
export interface SiteContext {
|
|
129
|
+
/** The site being run. */
|
|
130
|
+
readonly site: Site;
|
|
131
|
+
/** Cookie/credential domain for this site. */
|
|
132
|
+
readonly domain: string;
|
|
133
|
+
/** The parsed query options (CLI flags + positionals). */
|
|
134
|
+
readonly options: QueryOptions;
|
|
135
|
+
/** Convenience for `!!options.debug`. */
|
|
136
|
+
readonly debug: boolean;
|
|
137
|
+
/** Resolved output directory for `save()`, or null to use cwd. */
|
|
138
|
+
readonly outDir: string | null;
|
|
139
|
+
/** Resolve decrypted Chrome cookies for `domain` (lazy, memoized). */
|
|
140
|
+
cookies(): CookieEntry[];
|
|
141
|
+
/** Cookies as a `name=value; ...` header string. */
|
|
142
|
+
cookieString(): string;
|
|
143
|
+
/** Resolve a saved Chrome username/password for `domain`. */
|
|
144
|
+
credentials(): Credentials;
|
|
145
|
+
/** Resolve the User-Agent (options → env → default). */
|
|
146
|
+
userAgent(): string;
|
|
147
|
+
/** HTTP capability with cookie + User-Agent auto-injection. */
|
|
148
|
+
readonly http: HttpCapability;
|
|
149
|
+
/** Connect to Chrome over CDP, applying fingerprint + auth. Memoized. */
|
|
150
|
+
browser(): Promise<Page>;
|
|
151
|
+
/** Sugar for `(await browser()).evaluate(fn)`. */
|
|
152
|
+
eval<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
153
|
+
/** Write a file to `outDir` (or cwd) and return its absolute path. */
|
|
154
|
+
save(filename: string, content: string | Buffer): Promise<string>;
|
|
155
|
+
}
|
|
26
156
|
/**
|
|
27
|
-
* Defines an API endpoint
|
|
157
|
+
* Defines an API endpoint for the default declarative fetch flow.
|
|
28
158
|
*/
|
|
29
159
|
export interface Endpoint {
|
|
30
160
|
/** Full URL of the API endpoint. */
|
|
@@ -33,72 +163,90 @@ export interface Endpoint {
|
|
|
33
163
|
method?: string;
|
|
34
164
|
/** Additional headers to include in the request. */
|
|
35
165
|
headers?: Record<string, string>;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
* Use "html" for text/html responses that need parsing.
|
|
39
|
-
* Use "sse" for server-sent event stream responses.
|
|
40
|
-
*/
|
|
41
|
-
responseType?: "auto" | "json" | "text" | "html" | "sse";
|
|
166
|
+
/** Expected response body type. Defaults to auto-detect from content-type. */
|
|
167
|
+
responseType?: "auto" | "json" | "text" | "html";
|
|
42
168
|
/** Optional post-processing step for the parsed response body. */
|
|
43
|
-
transform?: (
|
|
169
|
+
transform?: (body: unknown, ctx: SiteContext) => unknown | Promise<unknown>;
|
|
44
170
|
}
|
|
45
171
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
172
|
+
* The minimal, declarative shape an author writes. A site is either:
|
|
173
|
+
* • declarative — set `endpoints` and get an automatic fetch flow, or
|
|
174
|
+
* • imperative — set `run(ctx)` and drive capabilities directly.
|
|
175
|
+
*
|
|
176
|
+
* External sites in the user's extensions folder export a plain object of this
|
|
177
|
+
* shape as their default export — no imports from the package are required.
|
|
49
178
|
*/
|
|
50
|
-
export interface
|
|
51
|
-
/** Unique identifier, typically
|
|
179
|
+
export interface SiteDef {
|
|
180
|
+
/** Unique identifier, typically a slug (e.g. "cursor-usage"). */
|
|
52
181
|
id: string;
|
|
53
|
-
/** Human-readable name
|
|
182
|
+
/** Human-readable name. */
|
|
54
183
|
name: string;
|
|
55
|
-
/** Cookie domain
|
|
184
|
+
/** Cookie/credential domain (e.g. "cursor.com"). */
|
|
56
185
|
domain: string;
|
|
57
|
-
/** Short description of what data this
|
|
186
|
+
/** Short description of what data this site fetches. */
|
|
58
187
|
description: string;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
/**
|
|
188
|
+
/** Transport used to reach the site. Defaults to "http". */
|
|
189
|
+
transport?: "http" | "browser";
|
|
190
|
+
/** Whether valid cookies are required. Defaults to "required". */
|
|
191
|
+
cookies?: "required" | "optional";
|
|
192
|
+
/** Browser fingerprint profile (browser transport only). Defaults to "stealth". */
|
|
193
|
+
fingerprint?: FingerprintOption;
|
|
194
|
+
/**
|
|
195
|
+
* Keep a tab opened by this site open after the run finishes, instead of
|
|
196
|
+
* closing it on teardown. Use for login sites so the authenticated session
|
|
197
|
+
* (and any 2FA state) stays warm for the next command. Defaults to false.
|
|
198
|
+
* The `--keep-open` CLI flag overrides this per-invocation.
|
|
199
|
+
*/
|
|
200
|
+
keepBrowserOpen?: boolean;
|
|
201
|
+
/** Declarative login config or a custom strategy (browser transport). */
|
|
202
|
+
auth?: LoginStrategy | FormLoginConfig;
|
|
203
|
+
/** Declares custom CLI flags for this site. */
|
|
66
204
|
parameters?: ParameterDefinition[];
|
|
67
|
-
/** Declares expected positional arguments for this
|
|
205
|
+
/** Declares expected positional arguments for this site. */
|
|
68
206
|
positionals?: PositionalDefinition[];
|
|
207
|
+
/** Declarative single-fetch flow. First endpoint is used. */
|
|
208
|
+
endpoints?: Endpoint[];
|
|
209
|
+
/** Imperative flow. Receives the full capability context. */
|
|
210
|
+
run?: (ctx: SiteContext) => unknown | Promise<unknown>;
|
|
69
211
|
}
|
|
70
212
|
/**
|
|
71
|
-
* A fully-
|
|
213
|
+
* A normalized, fully-defaulted site produced by `defineSite()`.
|
|
72
214
|
*/
|
|
73
|
-
export interface
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
215
|
+
export interface Site {
|
|
216
|
+
id: string;
|
|
217
|
+
name: string;
|
|
218
|
+
domain: string;
|
|
219
|
+
description: string;
|
|
220
|
+
transport: "http" | "browser";
|
|
221
|
+
cookies: "required" | "optional";
|
|
222
|
+
fingerprint: FingerprintOption;
|
|
223
|
+
keepBrowserOpen: boolean;
|
|
224
|
+
auth?: LoginStrategy;
|
|
225
|
+
parameters: ParameterDefinition[];
|
|
226
|
+
positionals: PositionalDefinition[];
|
|
227
|
+
/** URL the browser transport lands on (from endpoints[0] or https://domain). */
|
|
228
|
+
landingUrl: string;
|
|
229
|
+
run: (ctx: SiteContext) => Promise<unknown>;
|
|
230
|
+
/** Set by the loader: where this site was discovered. */
|
|
231
|
+
origin?: "bundled" | "extension";
|
|
232
|
+
/** Marker used to detect an already-normalized site. */
|
|
233
|
+
readonly __site: true;
|
|
91
234
|
}
|
|
92
235
|
/**
|
|
93
|
-
* Options passed when querying a
|
|
236
|
+
* Options passed when querying a site. CLI flags and positionals land here as
|
|
237
|
+
* camelCase keys (e.g. `--out-dir` → `outDir`).
|
|
94
238
|
*/
|
|
95
239
|
export interface QueryOptions {
|
|
96
240
|
/** Chrome profile directory name (e.g. "Default", "Profile 1"). */
|
|
97
241
|
profile?: string;
|
|
242
|
+
/** Chrome profile path override. */
|
|
243
|
+
profilePath?: string;
|
|
98
244
|
/** Custom User-Agent header for HTTP requests. */
|
|
99
245
|
userAgent?: string;
|
|
100
|
-
/** When true, print full HTTP request
|
|
246
|
+
/** When true, print full HTTP request/response details for debugging. */
|
|
101
247
|
debug?: boolean;
|
|
102
|
-
/**
|
|
248
|
+
/** Directory to write downloads to. */
|
|
249
|
+
outDir?: string;
|
|
250
|
+
/** Allows passing custom site-specific parameters. */
|
|
103
251
|
[key: string]: any;
|
|
104
252
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=[{name:"profile",type:"string",description:"specific Chrome profile directory (e.g., 'Default')"},{name:"user-agent",type:"string",description:"custom User-Agent header for HTTP requests",short:"u"},{name:"debug",type:"boolean",description:"Print full HTTP request and response bodies for debugging"},{name:"out",type:"string",description:"Write decoded response JSON/text to file instead of stdout",short:"o"}];export function parseArgsForWebsite(r=[],n=[],o){let i=!1;const s={},a=[...n,...e];for(const e of a){const r=t(e.name);void 0!==e.default?s[r]=e.default:"boolean"===e.type&&(s[r]=!1)}const c={};for(const e of r)c[e.name]=[];let
|
|
1
|
+
const e=[{name:"profile",type:"string",description:"specific Chrome profile directory (e.g., 'Default')"},{name:"user-agent",type:"string",description:"custom User-Agent header for HTTP requests",short:"u"},{name:"debug",type:"boolean",description:"Print full HTTP request and response bodies for debugging"},{name:"keep-open",type:"boolean",description:"Leave the browser tab open after running (preserve the logged-in session)"},{name:"out",type:"string",description:"Write decoded response JSON/text to file instead of stdout",short:"o"}];export function parseArgsForWebsite(r=[],n=[],o){let i=!1;const s={},a=[...n,...e];for(const e of a){const r=t(e.name);void 0!==e.default?s[r]=e.default:"boolean"===e.type&&(s[r]=!1)}const c={};for(const e of r)c[e.name]=[];let p=0;for(let e=0;e<o.length;e++){const n=o[e];if("--help"!==n&&"-h"!==n)if(n.startsWith("-")){let r;const i=!n.startsWith("--"),c=i?n.slice(1):n.slice(2);if(r=i?a.find(e=>e.short===c):a.find(e=>e.name===c),!r)throw new Error(`Unknown option: ${n}`);const p=t(r.name);if("boolean"===r.type)s[p]=!0;else if("string"===r.type){const t=o[e+1];if(void 0===t||t.startsWith("-"))throw new Error(`Option ${n} requires a value`);s[p]=t,e++}else if("number"===r.type){const t=o[e+1];if(void 0===t||t.startsWith("-"))throw new Error(`Option ${n} requires a numeric value`);const r=Number(t);if(isNaN(r))throw new Error(`Option ${n} requires a valid numeric value, received: "${t}"`);s[p]=r,e++}}else if(p<r.length){const e=r[p];c[e.name].push(n),e.variadic||p++}else{const e=r[r.length-1];if(!e?.variadic)throw new Error(`Unexpected extra argument: "${n}"`);c[e.name].push(n)}else i=!0}for(const e of r){const r=c[e.name];if(e.required&&0===r.length&&!i)throw new Error(`Missing required argument: <${e.name}>`);s[t(e.name)]=r.length>0?r.join(" "):null}for(const e of a){const r=t(e.name);if(e.required&&void 0===s[r]&&!i)throw new Error(`Missing required option: --${e.name}`)}return{options:s,helpRequested:i}}function t(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}
|
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* imports it. Each adapter module must have a default export that is a
|
|
11
|
-
* WebsiteAdapter (created via `defineAdapter()`).
|
|
12
|
-
*
|
|
13
|
-
* This is called once on first use — no manual imports needed when adding
|
|
14
|
-
* new adapters.
|
|
15
|
-
*/
|
|
16
|
-
export declare function loadAdapters(): Promise<void>;
|
|
17
|
-
/**
|
|
18
|
-
* Finds an adapter by ID or partial domain match.
|
|
19
|
-
*
|
|
20
|
-
* @param id - Website identifier (e.g. "chatgpt.com", "chatgpt", "cursor")
|
|
21
|
-
* @returns The matching adapter or null
|
|
22
|
-
*/
|
|
23
|
-
export declare function getWebsite(id: string): WebsiteAdapter | null;
|
|
24
|
-
/**
|
|
25
|
-
* Queries a registered website API using decrypted Google Chrome cookies on macOS.
|
|
26
|
-
*
|
|
27
|
-
* Cookie retrieval is simplified to a single `getCookies({ domain })` call —
|
|
28
|
-
* no manual extraction or domain matching needed.
|
|
29
|
-
*
|
|
30
|
-
* @param websiteId - The domain or identifier of the website (e.g. "chatgpt.com")
|
|
31
|
-
* @param options - Retrieval options (profile, userAgent)
|
|
32
|
-
* @returns The structured website API response
|
|
33
|
-
*/
|
|
34
|
-
export declare function queryWebsite(websiteId: string, options?: QueryOptions): Promise<unknown>;
|
|
1
|
+
export { defineSite, isSite } from "./core/define-site.js";
|
|
2
|
+
export type { Site, SiteDef, SiteContext, Endpoint, QueryOptions, ParameterDefinition, PositionalDefinition, FingerprintOption, FingerprintConfig, FormLoginConfig, LoginStrategy, LoginContext, Credentials, HttpCapability, SSEResult, } from "./types.js";
|
|
3
|
+
export { FormLoginStrategy } from "./capabilities/login/login-strategy.js";
|
|
4
|
+
export { sites, loadSites, setSites, getSite, queryWebsite, createUniversalSite, } from "./core/runtime.js";
|
|
5
|
+
export { discoverSites, extensionRoots, BUNDLED_SITES_DIR } from "./core/loader.js";
|
|
6
|
+
export { createContext } from "./core/context.js";
|
|
7
|
+
export type { ContextProviders, ManagedContext } from "./core/context.js";
|
|
8
|
+
export { DEFAULT_REGISTRY, resolveRegistries, addRegistry, removeRegistry, parseRepoSpec, loadIndex, searchRegistries, resolveEntry, installEntry, listInstalled, removeInstalled, configPath, } from "./core/registry.js";
|
|
9
|
+
export type { RegistrySource, RegistryFile, RegistrySiteEntry, RegistryIndex, InstalledRecord, FoundEntry, } from "./core/registry.js";
|
package/dist/src/website-api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{loadEnv as e}from"./env.js";e();export{defineSite,isSite}from"./core/define-site.js";export{FormLoginStrategy}from"./capabilities/login/login-strategy.js";export{sites,loadSites,setSites,getSite,queryWebsite,createUniversalSite}from"./core/runtime.js";export{discoverSites,extensionRoots,BUNDLED_SITES_DIR}from"./core/loader.js";export{createContext}from"./core/context.js";export{DEFAULT_REGISTRY,resolveRegistries,addRegistry,removeRegistry,parseRepoSpec,loadIndex,searchRegistries,resolveEntry,installEntry,listInstalled,removeInstalled,configPath}from"./core/registry.js";
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "website-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CLI and library to fetch website API data",
|
|
5
5
|
"main": "./dist/src/website-api.js",
|
|
6
|
+
"types": "./dist/src/website-api.d.ts",
|
|
6
7
|
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/website-api.d.ts",
|
|
11
|
+
"default": "./dist/src/website-api.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
15
|
"website-api": "./dist/bin/cli.js",
|
|
9
16
|
"chrome-website-api": "./dist/bin/cli.js"
|
|
@@ -36,6 +43,8 @@
|
|
|
36
43
|
],
|
|
37
44
|
"scripts": {
|
|
38
45
|
"build": "node scripts/build.mjs",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"test": "tsc -p tsconfig.test.json && node --test \"dist/test/**/*.test.js\"",
|
|
39
48
|
"check:secrets": "node scripts/check-secrets.mjs",
|
|
40
49
|
"release": "pnpm version patch && pnpm publish --access public",
|
|
41
50
|
"start": "node dist/bin/cli.js"
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type CookieEntry } from "chrome-tools";
|
|
2
|
-
import type { AdapterConfig, QueryOptions, WebsiteAdapter, Endpoint } from "../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Base class for all website adapters, enabling class-based inheritance and clean reuse.
|
|
5
|
-
*/
|
|
6
|
-
export declare class BaseAdapter implements WebsiteAdapter {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
domain: string;
|
|
10
|
-
description: string;
|
|
11
|
-
endpoints?: Endpoint[];
|
|
12
|
-
optionalCookies?: boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Internal reference to transient options. Used to check the debug flag during fetch calls.
|
|
15
|
-
*/
|
|
16
|
-
_lastQueryOptions?: QueryOptions;
|
|
17
|
-
buildCookieString(cookies: CookieEntry[]): string;
|
|
18
|
-
resolveUserAgent(options: QueryOptions): string;
|
|
19
|
-
resolveCredentials(options: QueryOptions): {
|
|
20
|
-
username: string;
|
|
21
|
-
password: string;
|
|
22
|
-
};
|
|
23
|
-
resolveCookies(options: QueryOptions): CookieEntry[];
|
|
24
|
-
/**
|
|
25
|
-
* Centralized HTTP fetch handler.
|
|
26
|
-
* Unifies request setup, response parsing, debug logging, and error handling.
|
|
27
|
-
*/
|
|
28
|
-
private _fetch;
|
|
29
|
-
fetchText(url: string, init?: RequestInit): Promise<string>;
|
|
30
|
-
fetchHtml(url: string, init?: RequestInit): Promise<string>;
|
|
31
|
-
fetchJson(url: string, init?: RequestInit): Promise<any>;
|
|
32
|
-
/**
|
|
33
|
-
* Internal helper to fetch a specific endpoint with type-based accepting headers and content-type parsing.
|
|
34
|
-
*/
|
|
35
|
-
private _fetchEndpoint;
|
|
36
|
-
fetchData(cookies: CookieEntry[], options: QueryOptions): Promise<unknown>;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Factory function to create a fully-equipped WebsiteAdapter from a minimal config.
|
|
40
|
-
*/
|
|
41
|
-
export declare function defineAdapter(config: AdapterConfig): WebsiteAdapter;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{getCookies as e,getPasswords as t}from"chrome-tools";export class BaseAdapter{id;name;domain;description;endpoints;optionalCookies;_lastQueryOptions;buildCookieString(e){return e.map(e=>`${e.name}=${e.value}`).join("; ")}resolveUserAgent(e){return e.userAgent||process.env.userAgent||process.env.USER_AGENT||"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"}resolveCredentials(e){const o=e.profilePath||process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,s=e.profile||process.env.PROFILE_NAME||void 0;let r=t({chromeDir:o,profile:s,search:this.domain});if(!r||0===r.length){const e=this.domain.split("."),n=e[e.length-2]||this.domain;r=t({chromeDir:o,profile:s,search:n})}if(!r||0===r.length)throw new Error(`No saved passwords found in Chrome for '${this.domain}'`);const{username:n,password:i}=r[0];if(!n||!i)throw new Error(`Found credentials for '${this.domain}' but username or password was empty`);return{username:n,password:i}}resolveCookies(t){const o=t.profilePath||process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,s=t.profile||process.env.PROFILE_NAME||void 0;let r=[];try{r=e({chromeDir:o,profile:s,domain:this.domain,decrypt:!0})}catch(e){if(!this.optionalCookies)throw new Error("No login found in browser")}if(!(r&&0!==r.length||this.optionalCookies))throw new Error("No login found in browser");return r}async _fetch(e,t){const o=this._lastQueryOptions?.debug;o&&console.log("[debug] Request:",{url:e,init:t});const s=await fetch(e,t),r=await s.text();if(o&&console.log("[debug] Response:",{url:e,status:s.status,statusText:s.statusText,headers:Array.from(s.headers.entries()),body:r}),!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return{response:s,text:r}}async fetchText(e,t){const{text:o}=await this._fetch(e,t);return o}async fetchHtml(e,t){return this.fetchText(e,t)}async fetchJson(e,t){const{text:o}=await this._fetch(e,t);try{return JSON.parse(o)}catch{return{response:o}}}async _fetchEndpoint(e,t,o){const s="html"===e.responseType?"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8":"text"===e.responseType?"text/plain,*/*;q=0.8":"application/json, text/plain, */*",{response:r,text:n}=await this._fetch(e.url,{method:e.method||"GET",headers:{Cookie:t,"User-Agent":o,Accept:s,...e.headers}}),i=r.headers.get("content-type")?.toLowerCase()??"";if("json"===e.responseType||"text"!==e.responseType&&"html"!==e.responseType&&(i.includes("application/json")||i.includes("+json")))try{return JSON.parse(n)}catch(t){throw new Error(`Expected JSON from ${e.url}, but received invalid JSON: ${t instanceof Error?t.message:String(t)}`)}return n}async fetchData(e,t){if(!this.endpoints||0===this.endpoints.length)throw new Error(`Adapter "${this.id}" has no endpoints defined and no fetchData override`);const o=this.endpoints[0],s=this.buildCookieString(e),r=this.resolveUserAgent(t),n=await this._fetchEndpoint(o,s,r);return o.transform?o.transform.call(this,n,e,t):n}}export function defineAdapter(e){const t=new BaseAdapter;return Object.assign(t,e),e.fetchData&&(t.fetchData=e.fetchData.bind(t)),t}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { BaseAdapter } from "./base-adapter.js";
|
|
2
|
-
import type { Page } from "playwright-core";
|
|
3
|
-
import type { QueryOptions } from "../types.js";
|
|
4
|
-
/**
|
|
5
|
-
* Specialized base class for adapters that use Playwright CDP tab reuse.
|
|
6
|
-
* Provides a clean `connect(options)` method returning a disposable Page proxy.
|
|
7
|
-
*/
|
|
8
|
-
export declare class PlaywrightAdapter extends BaseAdapter {
|
|
9
|
-
/**
|
|
10
|
-
* Connects to Chrome over CDP using the first endpoint's URL, returning a disposable Page proxy
|
|
11
|
-
* which automatically captures HTML and disposes of the connection upon scope exit.
|
|
12
|
-
*
|
|
13
|
-
* @param options The adapter options containing the debug flag.
|
|
14
|
-
*/
|
|
15
|
-
connect(options: QueryOptions): Promise<Page>;
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{BaseAdapter as t}from"./base-adapter.js";import{runWithPlaywright as r}from"./playwright-core.js";export class PlaywrightAdapter extends t{async connect(t){const e=await r(this.endpoints,t);return await e.addInitScript(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),e}}
|