pi-harness-runtime 1.1.80 → 1.1.82
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/harness/browser-scrapers/base-browser-scraper.d.ts +91 -0
- package/harness/browser-scrapers/chrome-profile-manager.d.ts +80 -0
- package/harness/browser-scrapers/codex-browser-scraper.d.ts +60 -0
- package/harness/browser-scrapers/index.d.ts +29 -0
- package/harness/browser-scrapers/zai-browser-scraper.d.ts +79 -0
- package/harness/e2e/browser-scrapers/base-browser-scraper.d.ts +92 -0
- package/harness/e2e/browser-scrapers/base-browser-scraper.ts +244 -0
- package/harness/e2e/browser-scrapers/chrome-profile-manager.d.ts +81 -0
- package/harness/e2e/browser-scrapers/chrome-profile-manager.ts +297 -0
- package/harness/e2e/browser-scrapers/codex-browser-scraper.d.ts +61 -0
- package/harness/e2e/browser-scrapers/codex-browser-scraper.ts +343 -0
- package/harness/e2e/browser-scrapers/index.d.ts +30 -0
- package/harness/e2e/browser-scrapers/index.ts +48 -0
- package/harness/e2e/browser-scrapers/zai-browser-scraper.d.ts +80 -0
- package/harness/e2e/browser-scrapers/zai-browser-scraper.ts +392 -0
- package/harness/e2e/glm-quota-scraper.ts +106 -15
- package/harness/e2e/index.ts +29 -0
- package/harness/e2e/unified-quota-manager.ts +364 -0
- package/index.ts +72 -11
- package/mirror.ts +32 -28
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/dist/provider-id.d.ts.map +1 -1
- package/packages/providers/dist/provider-id.js +1 -2
- package/packages/providers/dist/provider-id.js.map +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/providers/src/provider-id.ts +1 -2
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Browser Scraper — Abstract class for browser-based quota scraping
|
|
3
|
+
*
|
|
4
|
+
* Uses Playwright with Chrome profiles to scrape JavaScript-rendered pages
|
|
5
|
+
* that require authentication. Similar approach to google-flow-mcp.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page, Browser, BrowserContext } from "playwright";
|
|
8
|
+
export interface BrowserScraperConfig {
|
|
9
|
+
/** Headless mode (default: true) */
|
|
10
|
+
headless?: boolean;
|
|
11
|
+
/** Page load timeout in ms (default: 30000) */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** Viewport dimensions */
|
|
14
|
+
viewport?: {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
/** Slow down operations by ms (for debugging) */
|
|
19
|
+
slowMo?: number;
|
|
20
|
+
/** Provider name for logging */
|
|
21
|
+
provider: string;
|
|
22
|
+
/** URL to scrape */
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface QuotaScrapingResult<T> {
|
|
26
|
+
/** Whether scraping was successful */
|
|
27
|
+
success: boolean;
|
|
28
|
+
/** The scraped data */
|
|
29
|
+
data?: T;
|
|
30
|
+
/** Error message if failed */
|
|
31
|
+
error?: string;
|
|
32
|
+
/** Timestamp of scrape */
|
|
33
|
+
scrapedAt: string;
|
|
34
|
+
/** Screenshot path if debug screenshot was taken */
|
|
35
|
+
screenshotPath?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Abstract base class for browser-based quota scrapers
|
|
39
|
+
*/
|
|
40
|
+
export declare abstract class BaseBrowserScraper<T> {
|
|
41
|
+
protected config: Required<BrowserScraperConfig>;
|
|
42
|
+
protected browser: Browser | null;
|
|
43
|
+
protected context: BrowserContext | null;
|
|
44
|
+
protected page: Page | null;
|
|
45
|
+
protected screenshotsEnabled: boolean;
|
|
46
|
+
protected screenshotsDir: string;
|
|
47
|
+
constructor(config: BrowserScraperConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Initialize Playwright and launch browser
|
|
50
|
+
*/
|
|
51
|
+
protected abstract createBrowser(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Take a debug screenshot
|
|
54
|
+
*/
|
|
55
|
+
takeScreenshot(name: string): Promise<string | undefined>;
|
|
56
|
+
/**
|
|
57
|
+
* Wait for a selector with retry logic
|
|
58
|
+
*/
|
|
59
|
+
protected waitForSelector(selector: string, options?: {
|
|
60
|
+
timeout?: number;
|
|
61
|
+
state?: "attached" | "detached" | "visible" | "hidden";
|
|
62
|
+
}): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Extract text content from page
|
|
65
|
+
*/
|
|
66
|
+
protected extractText(selector: string): Promise<string | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Extract number from text (handles various formats like "1.5 GB", "$10.00", etc.)
|
|
69
|
+
*/
|
|
70
|
+
protected extractNumber(text: string | null, pattern?: RegExp): number | null;
|
|
71
|
+
/**
|
|
72
|
+
* Main scraping method — navigates and extracts data
|
|
73
|
+
*/
|
|
74
|
+
scrape(): Promise<QuotaScrapingResult<T>>;
|
|
75
|
+
/**
|
|
76
|
+
* Check if current page is a login page
|
|
77
|
+
*/
|
|
78
|
+
protected abstract isLoginPage(): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Extract quota data from the page
|
|
81
|
+
*/
|
|
82
|
+
protected abstract extractData(): Promise<T | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Cleanup browser resources
|
|
85
|
+
*/
|
|
86
|
+
cleanup(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Enable screenshot capture for debugging
|
|
89
|
+
*/
|
|
90
|
+
enableScreenshots(dir?: string): void;
|
|
91
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome Profile Manager — Browser-based quota scraping via Playwright
|
|
3
|
+
*
|
|
4
|
+
* Key concepts from google-flow-mcp:
|
|
5
|
+
* - Uses Playwright to connect to existing Chrome profile with stored cookies
|
|
6
|
+
* - Maintains persistent session across runs
|
|
7
|
+
* - Bypasses anti-bot measures by launching Chrome directly
|
|
8
|
+
*
|
|
9
|
+
* This enables scraping JavaScript-rendered pages that require authentication:
|
|
10
|
+
* - chatgpt.com/codex/cloud/settings/analytics
|
|
11
|
+
* - z.ai/manage-apikey/coding-plan/personal/usage
|
|
12
|
+
* - etc.
|
|
13
|
+
*/
|
|
14
|
+
export interface ChromeProfileConfig {
|
|
15
|
+
/** Provider name (e.g., "codex", "z-ai") */
|
|
16
|
+
provider: string;
|
|
17
|
+
/** Path to Chrome user data directory (auto-detected if not provided) */
|
|
18
|
+
userDataDir?: string;
|
|
19
|
+
/** Profile directory name within userDataDir */
|
|
20
|
+
profileName?: string;
|
|
21
|
+
/** Path to browser binary (auto-detected if not provided) */
|
|
22
|
+
binaryPath?: string;
|
|
23
|
+
/** Arguments to pass to Chrome */
|
|
24
|
+
extraArgs?: string[];
|
|
25
|
+
/** Headless mode (default: true for scraping) */
|
|
26
|
+
headless?: boolean;
|
|
27
|
+
/** Launch timeout in ms */
|
|
28
|
+
launchTimeout?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface BrowserProfile {
|
|
31
|
+
/** Path to the profile directory */
|
|
32
|
+
profilePath: string;
|
|
33
|
+
/** Provider name */
|
|
34
|
+
provider: string;
|
|
35
|
+
/** Whether the profile exists */
|
|
36
|
+
exists: boolean;
|
|
37
|
+
/** Last used timestamp */
|
|
38
|
+
lastUsed?: number;
|
|
39
|
+
}
|
|
40
|
+
/** Auto-detect Chrome user data directory based on OS */
|
|
41
|
+
export declare function detectChromeUserDataDir(): string | null;
|
|
42
|
+
/** Auto-detect Chrome binary path */
|
|
43
|
+
export declare function detectChromeBinary(): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Chrome Profile Manager for browser-based scraping
|
|
46
|
+
*
|
|
47
|
+
* Manages Chrome profiles for different providers to maintain
|
|
48
|
+
* authenticated sessions across scraping runs.
|
|
49
|
+
*/
|
|
50
|
+
export declare class ChromeProfileManager {
|
|
51
|
+
private profilesDir;
|
|
52
|
+
private profiles;
|
|
53
|
+
constructor(profilesDir?: string);
|
|
54
|
+
private ensureProfilesDir;
|
|
55
|
+
private getProfileMetaPath;
|
|
56
|
+
private loadProfiles;
|
|
57
|
+
/**
|
|
58
|
+
* Get profile path for a provider
|
|
59
|
+
*/
|
|
60
|
+
getProfilePath(provider: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Ensure profile directory exists
|
|
63
|
+
*/
|
|
64
|
+
ensureProfile(provider: string): BrowserProfile;
|
|
65
|
+
private saveProfileMeta;
|
|
66
|
+
/**
|
|
67
|
+
* List all managed profiles
|
|
68
|
+
*/
|
|
69
|
+
listProfiles(): BrowserProfile[];
|
|
70
|
+
/**
|
|
71
|
+
* Delete a profile
|
|
72
|
+
*/
|
|
73
|
+
deleteProfile(provider: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Get Chrome launch arguments for anti-bot evasion
|
|
76
|
+
* Based on google-flow-mcp approach
|
|
77
|
+
*/
|
|
78
|
+
getLaunchArgs(config: ChromeProfileConfig): string[];
|
|
79
|
+
}
|
|
80
|
+
export declare function getChromeProfileManager(): ChromeProfileManager;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex Browser Scraper — Scrapes chatgpt.com/codex/cloud/settings/analytics
|
|
3
|
+
*
|
|
4
|
+
* Uses Playwright with Chrome profile to access the authenticated page
|
|
5
|
+
* and extract usage/quota data.
|
|
6
|
+
*/
|
|
7
|
+
import { BaseBrowserScraper } from "./base-browser-scraper.js";
|
|
8
|
+
export interface CodexQuotaData {
|
|
9
|
+
provider: "openai-codex";
|
|
10
|
+
/** Plan type (e.g., "pro", "plus") */
|
|
11
|
+
planType?: string;
|
|
12
|
+
/** Current usage percentage (0-100) */
|
|
13
|
+
usagePercent: number;
|
|
14
|
+
/** Total quota amount */
|
|
15
|
+
totalQuota?: string;
|
|
16
|
+
/** Used quota amount */
|
|
17
|
+
usedQuota?: string;
|
|
18
|
+
/** Remaining quota */
|
|
19
|
+
remainingQuota?: string;
|
|
20
|
+
/** Reset date/time */
|
|
21
|
+
resetAt?: string;
|
|
22
|
+
/** Usage period description */
|
|
23
|
+
period?: string;
|
|
24
|
+
/** Raw data for debugging */
|
|
25
|
+
rawData?: Record<string, unknown>;
|
|
26
|
+
/** Timestamp */
|
|
27
|
+
scrapedAt: string;
|
|
28
|
+
}
|
|
29
|
+
interface CodexScraperConfig {
|
|
30
|
+
provider?: string;
|
|
31
|
+
url?: string;
|
|
32
|
+
headless?: boolean;
|
|
33
|
+
timeout?: number;
|
|
34
|
+
viewport?: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
slowMo?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Codex Browser Scraper
|
|
42
|
+
*
|
|
43
|
+
* Scrapes usage data from chatgpt.com/codex/cloud/settings/analytics
|
|
44
|
+
*/
|
|
45
|
+
export declare class CodexBrowserScraper extends BaseBrowserScraper<CodexQuotaData> {
|
|
46
|
+
constructor(config?: Partial<CodexScraperConfig>);
|
|
47
|
+
protected createBrowser(): Promise<void>;
|
|
48
|
+
private getProfileDir;
|
|
49
|
+
protected isLoginPage(): Promise<boolean>;
|
|
50
|
+
protected extractData(): Promise<CodexQuotaData | null>;
|
|
51
|
+
private extractUsageData;
|
|
52
|
+
private extractAdditionalData;
|
|
53
|
+
private extractFromHeadings;
|
|
54
|
+
private extractChartData;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Quick scrape function
|
|
58
|
+
*/
|
|
59
|
+
export declare function scrapeCodexQuota(): Promise<CodexQuotaData | null>;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Scrapers — Browser-based quota scraping via Playwright
|
|
3
|
+
*
|
|
4
|
+
* Authentication strategies by provider:
|
|
5
|
+
*
|
|
6
|
+
* | Provider | Auth Method | Cookies Work? | Notes |
|
|
7
|
+
* |-----------|----------------------|---------------|--------------------------|
|
|
8
|
+
* | MiniMax | Browser cookies | Yes | Direct browser scraping |
|
|
9
|
+
* | ChatGPT | OAuth tokens | No | Use ~/.codex/auth.json |
|
|
10
|
+
* | Codex | OAuth tokens | No | Use ~/.codex/auth.json |
|
|
11
|
+
* | Z.ai | Browser cookies | Unknown | Try browser profile |
|
|
12
|
+
*
|
|
13
|
+
* For providers where browser cookies don't work for automation:
|
|
14
|
+
* - Use API tokens (OAuth, API keys)
|
|
15
|
+
* - Export cookies from DevTools (may not work due to HttpOnly, Secure flags)
|
|
16
|
+
* - Manual authentication via headful browser with user interaction
|
|
17
|
+
*/
|
|
18
|
+
export { ChromeProfileManager, getChromeProfileManager, detectChromeUserDataDir, detectChromeBinary, type ChromeProfileConfig, type BrowserProfile, } from "./chrome-profile-manager.js";
|
|
19
|
+
export { BaseBrowserScraper, type BrowserScraperConfig, type QuotaScrapingResult, } from "./base-browser-scraper.js";
|
|
20
|
+
export { CodexBrowserScraper, scrapeCodexQuota, type CodexQuotaData } from "./codex-browser-scraper.js";
|
|
21
|
+
export { ZaiBrowserScraper, scrapeZaiQuota, checkZaiCookies, type ZaiQuotaData } from "./zai-browser-scraper.js";
|
|
22
|
+
/**
|
|
23
|
+
* All available browser scrapers
|
|
24
|
+
*/
|
|
25
|
+
export declare const BROWSER_SCRAPERS: {
|
|
26
|
+
readonly "openai-codex": () => Promise<typeof import("./codex-browser-scraper.js").CodexBrowserScraper>;
|
|
27
|
+
readonly "z-ai": () => Promise<typeof import("./zai-browser-scraper.js").ZaiBrowserScraper>;
|
|
28
|
+
};
|
|
29
|
+
export type BrowserScraperProvider = keyof typeof BROWSER_SCRAPERS;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z.ai Browser Scraper — Scrapes z.ai usage data
|
|
3
|
+
*
|
|
4
|
+
* Authentication approach:
|
|
5
|
+
* - Z.ai uses cookies from browser login (different from ChatGPT)
|
|
6
|
+
* - Cookies from ~/.config/google-chrome or browser export need to be tested
|
|
7
|
+
* - Fallback: Manual cookie export from browser DevTools
|
|
8
|
+
*
|
|
9
|
+
* Note: If cookies don't work for browser automation (like Codex),
|
|
10
|
+
* we may need to find alternative auth methods (OAuth, API keys, etc.)
|
|
11
|
+
*/
|
|
12
|
+
import { BaseBrowserScraper } from "./base-browser-scraper.js";
|
|
13
|
+
export interface ZaiQuotaData {
|
|
14
|
+
provider: "z-ai";
|
|
15
|
+
/** Plan type (e.g., "Pro", "Team", "Enterprise") */
|
|
16
|
+
planType?: string;
|
|
17
|
+
/** Current usage percentage (0-100) */
|
|
18
|
+
usagePercent: number;
|
|
19
|
+
/** Total quota (e.g., "5 GB") */
|
|
20
|
+
totalQuota?: string;
|
|
21
|
+
/** Used quota (e.g., "1.5 GB") */
|
|
22
|
+
usedQuota?: string;
|
|
23
|
+
/** Remaining quota */
|
|
24
|
+
remainingQuota?: string;
|
|
25
|
+
/** Reset date/time */
|
|
26
|
+
resetAt?: string;
|
|
27
|
+
/** Billing period */
|
|
28
|
+
period?: string;
|
|
29
|
+
/** Timestamp */
|
|
30
|
+
scrapedAt: string;
|
|
31
|
+
}
|
|
32
|
+
interface ZaiScraperConfig {
|
|
33
|
+
provider?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
headless?: boolean;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
viewport?: {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
};
|
|
41
|
+
slowMo?: number;
|
|
42
|
+
/** Path to cookies file (Netscape format) */
|
|
43
|
+
cookiesFile?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Z.ai Browser Scraper
|
|
47
|
+
*/
|
|
48
|
+
export declare class ZaiBrowserScraper extends BaseBrowserScraper<ZaiQuotaData> {
|
|
49
|
+
private cookiesFile?;
|
|
50
|
+
constructor(config?: Partial<ZaiScraperConfig>);
|
|
51
|
+
/**
|
|
52
|
+
* Find available cookies file
|
|
53
|
+
*/
|
|
54
|
+
private findCookiesFile;
|
|
55
|
+
protected createBrowser(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Load cookies from file and convert to headers (fallback auth method)
|
|
58
|
+
* Note: This won't work for all sites - some require full cookie jar
|
|
59
|
+
*/
|
|
60
|
+
private loadCookiesAsHeaders;
|
|
61
|
+
protected isLoginPage(): Promise<boolean>;
|
|
62
|
+
protected extractData(): Promise<ZaiQuotaData | null>;
|
|
63
|
+
private extractUsagePercent;
|
|
64
|
+
private extractPlanType;
|
|
65
|
+
private extractQuotaData;
|
|
66
|
+
private extractResetDate;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Quick scrape function
|
|
70
|
+
*/
|
|
71
|
+
export declare function scrapeZaiQuota(cookiesFile?: string): Promise<ZaiQuotaData | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Check if z.ai cookies are available
|
|
74
|
+
*/
|
|
75
|
+
export declare function checkZaiCookies(): {
|
|
76
|
+
available: boolean;
|
|
77
|
+
path?: string;
|
|
78
|
+
};
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Browser Scraper — Abstract class for browser-based quota scraping
|
|
3
|
+
*
|
|
4
|
+
* Uses Playwright with Chrome profiles to scrape JavaScript-rendered pages
|
|
5
|
+
* that require authentication. Similar approach to google-flow-mcp.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page, Browser, BrowserContext } from "playwright";
|
|
8
|
+
export interface BrowserScraperConfig {
|
|
9
|
+
/** Headless mode (default: true) */
|
|
10
|
+
headless?: boolean;
|
|
11
|
+
/** Page load timeout in ms (default: 30000) */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** Viewport dimensions */
|
|
14
|
+
viewport?: {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
/** Slow down operations by ms (for debugging) */
|
|
19
|
+
slowMo?: number;
|
|
20
|
+
/** Provider name for logging */
|
|
21
|
+
provider: string;
|
|
22
|
+
/** URL to scrape */
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface QuotaScrapingResult<T> {
|
|
26
|
+
/** Whether scraping was successful */
|
|
27
|
+
success: boolean;
|
|
28
|
+
/** The scraped data */
|
|
29
|
+
data?: T;
|
|
30
|
+
/** Error message if failed */
|
|
31
|
+
error?: string;
|
|
32
|
+
/** Timestamp of scrape */
|
|
33
|
+
scrapedAt: string;
|
|
34
|
+
/** Screenshot path if debug screenshot was taken */
|
|
35
|
+
screenshotPath?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Abstract base class for browser-based quota scrapers
|
|
39
|
+
*/
|
|
40
|
+
export declare abstract class BaseBrowserScraper<T> {
|
|
41
|
+
protected config: Required<BrowserScraperConfig>;
|
|
42
|
+
protected browser: Browser | null;
|
|
43
|
+
protected context: BrowserContext | null;
|
|
44
|
+
protected page: Page | null;
|
|
45
|
+
protected screenshotsEnabled: boolean;
|
|
46
|
+
protected screenshotsDir: string;
|
|
47
|
+
constructor(config: BrowserScraperConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Initialize Playwright and launch browser
|
|
50
|
+
*/
|
|
51
|
+
protected abstract createBrowser(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Take a debug screenshot
|
|
54
|
+
*/
|
|
55
|
+
takeScreenshot(name: string): Promise<string | undefined>;
|
|
56
|
+
/**
|
|
57
|
+
* Wait for a selector with retry logic
|
|
58
|
+
*/
|
|
59
|
+
protected waitForSelector(selector: string, options?: {
|
|
60
|
+
timeout?: number;
|
|
61
|
+
state?: "attached" | "detached" | "visible" | "hidden";
|
|
62
|
+
}): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Extract text content from page
|
|
65
|
+
*/
|
|
66
|
+
protected extractText(selector: string): Promise<string | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Extract number from text (handles various formats like "1.5 GB", "$10.00", etc.)
|
|
69
|
+
*/
|
|
70
|
+
protected extractNumber(text: string | null, pattern?: RegExp): number | null;
|
|
71
|
+
/**
|
|
72
|
+
* Main scraping method — navigates and extracts data
|
|
73
|
+
*/
|
|
74
|
+
scrape(): Promise<QuotaScrapingResult<T>>;
|
|
75
|
+
/**
|
|
76
|
+
* Check if current page is a login page
|
|
77
|
+
*/
|
|
78
|
+
protected abstract isLoginPage(): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Extract quota data from the page
|
|
81
|
+
*/
|
|
82
|
+
protected abstract extractData(): Promise<T | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Cleanup browser resources
|
|
85
|
+
*/
|
|
86
|
+
cleanup(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Enable screenshot capture for debugging
|
|
89
|
+
*/
|
|
90
|
+
enableScreenshots(dir?: string): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=base-browser-scraper.d.ts.map
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Browser Scraper — Abstract class for browser-based quota scraping
|
|
3
|
+
*
|
|
4
|
+
* Uses Playwright with Chrome profiles to scrape JavaScript-rendered pages
|
|
5
|
+
* that require authentication. Similar approach to google-flow-mcp.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Page, Browser, BrowserContext } from "playwright";
|
|
9
|
+
|
|
10
|
+
export interface BrowserScraperConfig {
|
|
11
|
+
/** Headless mode (default: true) */
|
|
12
|
+
headless?: boolean;
|
|
13
|
+
/** Page load timeout in ms (default: 30000) */
|
|
14
|
+
timeout?: number;
|
|
15
|
+
/** Viewport dimensions */
|
|
16
|
+
viewport?: { width: number; height: number };
|
|
17
|
+
/** Slow down operations by ms (for debugging) */
|
|
18
|
+
slowMo?: number;
|
|
19
|
+
/** Provider name for logging */
|
|
20
|
+
provider: string;
|
|
21
|
+
/** URL to scrape */
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface QuotaScrapingResult<T> {
|
|
26
|
+
/** Whether scraping was successful */
|
|
27
|
+
success: boolean;
|
|
28
|
+
/** The scraped data */
|
|
29
|
+
data?: T;
|
|
30
|
+
/** Error message if failed */
|
|
31
|
+
error?: string;
|
|
32
|
+
/** Timestamp of scrape */
|
|
33
|
+
scrapedAt: string;
|
|
34
|
+
/** Screenshot path if debug screenshot was taken */
|
|
35
|
+
screenshotPath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Abstract base class for browser-based quota scrapers
|
|
40
|
+
*/
|
|
41
|
+
export abstract class BaseBrowserScraper<T> {
|
|
42
|
+
protected config: Required<BrowserScraperConfig>;
|
|
43
|
+
protected browser: Browser | null = null;
|
|
44
|
+
protected context: BrowserContext | null = null;
|
|
45
|
+
protected page: Page | null = null;
|
|
46
|
+
protected screenshotsEnabled = false;
|
|
47
|
+
protected screenshotsDir = "/tmp/pi-harness-screenshots";
|
|
48
|
+
|
|
49
|
+
constructor(config: BrowserScraperConfig) {
|
|
50
|
+
this.config = {
|
|
51
|
+
headless: config.headless ?? true,
|
|
52
|
+
timeout: config.timeout ?? 30000,
|
|
53
|
+
viewport: config.viewport ?? { width: 1280, height: 720 },
|
|
54
|
+
slowMo: config.slowMo ?? 0,
|
|
55
|
+
provider: config.provider,
|
|
56
|
+
url: config.url,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize Playwright and launch browser
|
|
62
|
+
*/
|
|
63
|
+
protected abstract createBrowser(): Promise<void>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Take a debug screenshot
|
|
67
|
+
*/
|
|
68
|
+
async takeScreenshot(name: string): Promise<string | undefined> {
|
|
69
|
+
if (!this.page) return undefined;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const { existsSync, mkdirSync } = require("node:fs");
|
|
73
|
+
const path = require("node:path");
|
|
74
|
+
|
|
75
|
+
if (!existsSync(this.screenshotsDir)) {
|
|
76
|
+
mkdirSync(this.screenshotsDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const filePath = path.join(
|
|
80
|
+
this.screenshotsDir,
|
|
81
|
+
`${this.config.provider}-${name}-${Date.now()}.png`,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await this.page.screenshot({ path: filePath, fullPage: true });
|
|
85
|
+
console.log(`[${this.config.provider}] Screenshot saved: ${filePath}`);
|
|
86
|
+
return filePath;
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Wait for a selector with retry logic
|
|
94
|
+
*/
|
|
95
|
+
protected async waitForSelector(
|
|
96
|
+
selector: string,
|
|
97
|
+
options?: { timeout?: number; state?: "attached" | "detached" | "visible" | "hidden" },
|
|
98
|
+
): Promise<boolean> {
|
|
99
|
+
if (!this.page) return false;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await this.page.waitForSelector(selector, {
|
|
103
|
+
timeout: options?.timeout ?? this.config.timeout,
|
|
104
|
+
state: options?.state ?? "visible",
|
|
105
|
+
});
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract text content from page
|
|
114
|
+
*/
|
|
115
|
+
protected async extractText(selector: string): Promise<string | null> {
|
|
116
|
+
if (!this.page) return null;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const element = await this.page.$(selector);
|
|
120
|
+
if (!element) return null;
|
|
121
|
+
return await element.textContent();
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extract number from text (handles various formats like "1.5 GB", "$10.00", etc.)
|
|
129
|
+
*/
|
|
130
|
+
protected extractNumber(text: string | null, pattern?: RegExp): number | null {
|
|
131
|
+
if (!text) return null;
|
|
132
|
+
|
|
133
|
+
const cleaned = pattern ? text.match(pattern)?.[0] ?? text : text;
|
|
134
|
+
const match = cleaned.match(/[\d,.]+/);
|
|
135
|
+
if (!match) return null;
|
|
136
|
+
|
|
137
|
+
const num = parseFloat(match[0].replace(/,/g, ""));
|
|
138
|
+
return isNaN(num) ? null : num;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Main scraping method — navigates and extracts data
|
|
143
|
+
*/
|
|
144
|
+
async scrape(): Promise<QuotaScrapingResult<T>> {
|
|
145
|
+
const result: QuotaScrapingResult<T> = {
|
|
146
|
+
success: false,
|
|
147
|
+
scrapedAt: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Initialize browser
|
|
152
|
+
await this.createBrowser();
|
|
153
|
+
|
|
154
|
+
if (!this.page) {
|
|
155
|
+
result.error = "Failed to create browser page";
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Navigate to URL
|
|
160
|
+
console.log(`[${this.config.provider}] Navigating to ${this.config.url}`);
|
|
161
|
+
await this.page.goto(this.config.url, {
|
|
162
|
+
waitUntil: "networkidle",
|
|
163
|
+
timeout: this.config.timeout,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Wait for page to settle
|
|
167
|
+
await this.page.waitForTimeout(2000);
|
|
168
|
+
|
|
169
|
+
// Take screenshot for debugging if enabled
|
|
170
|
+
if (this.screenshotsEnabled) {
|
|
171
|
+
await this.takeScreenshot("initial-load");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if login is required
|
|
175
|
+
if (await this.isLoginPage()) {
|
|
176
|
+
result.error = "Login required - browser session not authenticated";
|
|
177
|
+
await this.takeScreenshot("login-required");
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Extract data using provider-specific logic
|
|
182
|
+
const data = await this.extractData();
|
|
183
|
+
if (!data) {
|
|
184
|
+
result.error = "Failed to extract quota data from page";
|
|
185
|
+
await this.takeScreenshot("extraction-failed");
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
result.success = true;
|
|
190
|
+
result.data = data;
|
|
191
|
+
|
|
192
|
+
} catch (error) {
|
|
193
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
194
|
+
await this.takeScreenshot("error");
|
|
195
|
+
|
|
196
|
+
} finally {
|
|
197
|
+
await this.cleanup();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if current page is a login page
|
|
205
|
+
*/
|
|
206
|
+
protected abstract isLoginPage(): Promise<boolean>;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Extract quota data from the page
|
|
210
|
+
*/
|
|
211
|
+
protected abstract extractData(): Promise<T | null>;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Cleanup browser resources
|
|
215
|
+
*/
|
|
216
|
+
async cleanup(): Promise<void> {
|
|
217
|
+
try {
|
|
218
|
+
if (this.page) {
|
|
219
|
+
await this.page.close();
|
|
220
|
+
this.page = null;
|
|
221
|
+
}
|
|
222
|
+
if (this.context) {
|
|
223
|
+
await this.context.close();
|
|
224
|
+
this.context = null;
|
|
225
|
+
}
|
|
226
|
+
if (this.browser) {
|
|
227
|
+
await this.browser.close();
|
|
228
|
+
this.browser = null;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Ignore cleanup errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Enable screenshot capture for debugging
|
|
237
|
+
*/
|
|
238
|
+
enableScreenshots(dir?: string): void {
|
|
239
|
+
this.screenshotsEnabled = true;
|
|
240
|
+
if (dir) {
|
|
241
|
+
this.screenshotsDir = dir;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|