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,343 @@
|
|
|
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
|
+
|
|
8
|
+
import { BaseBrowserScraper } from "./base-browser-scraper.js";
|
|
9
|
+
|
|
10
|
+
export interface CodexQuotaData {
|
|
11
|
+
provider: "openai-codex";
|
|
12
|
+
/** Plan type (e.g., "pro", "plus") */
|
|
13
|
+
planType?: string;
|
|
14
|
+
/** Current usage percentage (0-100) */
|
|
15
|
+
usagePercent: number;
|
|
16
|
+
/** Total quota amount */
|
|
17
|
+
totalQuota?: string;
|
|
18
|
+
/** Used quota amount */
|
|
19
|
+
usedQuota?: string;
|
|
20
|
+
/** Remaining quota */
|
|
21
|
+
remainingQuota?: string;
|
|
22
|
+
/** Reset date/time */
|
|
23
|
+
resetAt?: string;
|
|
24
|
+
/** Usage period description */
|
|
25
|
+
period?: string;
|
|
26
|
+
/** Raw data for debugging */
|
|
27
|
+
rawData?: Record<string, unknown>;
|
|
28
|
+
/** Timestamp */
|
|
29
|
+
scrapedAt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CodexScraperConfig {
|
|
33
|
+
provider?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
headless?: boolean;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
viewport?: { width: number; height: number };
|
|
38
|
+
slowMo?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Codex Browser Scraper
|
|
43
|
+
*
|
|
44
|
+
* Scrapes usage data from chatgpt.com/codex/cloud/settings/analytics
|
|
45
|
+
*/
|
|
46
|
+
export class CodexBrowserScraper extends BaseBrowserScraper<CodexQuotaData> {
|
|
47
|
+
constructor(config: Partial<CodexScraperConfig> = {}) {
|
|
48
|
+
super({
|
|
49
|
+
provider: "openai-codex",
|
|
50
|
+
url: config.url ?? "https://chatgpt.com/codex/cloud/settings/analytics",
|
|
51
|
+
headless: config.headless ?? true,
|
|
52
|
+
timeout: config.timeout ?? 30000,
|
|
53
|
+
viewport: config.viewport,
|
|
54
|
+
slowMo: config.slowMo,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected async createBrowser(): Promise<void> {
|
|
59
|
+
const { chromium } = await import("playwright");
|
|
60
|
+
const profileDir = this.getProfileDir();
|
|
61
|
+
|
|
62
|
+
this.browser = await chromium.launch({
|
|
63
|
+
headless: this.config.headless,
|
|
64
|
+
slowMo: this.config.slowMo,
|
|
65
|
+
args: [
|
|
66
|
+
`--user-data-dir=${profileDir}`,
|
|
67
|
+
"--disable-blink-features=AutomationControlled",
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.context = await this.browser.newContext({
|
|
72
|
+
viewport: this.config.viewport,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.page = await this.context.newPage();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getProfileDir(): string {
|
|
79
|
+
const { join } = require("node:path");
|
|
80
|
+
const { homedir } = require("node:os");
|
|
81
|
+
return join(homedir(), ".pi-harness-runtime", "browser-profiles", "codex");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected async isLoginPage(): Promise<boolean> {
|
|
85
|
+
if (!this.page) return false;
|
|
86
|
+
|
|
87
|
+
// Check for common login indicators
|
|
88
|
+
const loginSelectors = [
|
|
89
|
+
'button[data-testid="sign-in"]',
|
|
90
|
+
'button:has-text("Sign in")',
|
|
91
|
+
'input[name="email"]',
|
|
92
|
+
'input[placeholder*="email" i]',
|
|
93
|
+
'[data-testid="login"]',
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const selector of loginSelectors) {
|
|
97
|
+
try {
|
|
98
|
+
const element = await this.page.$(selector);
|
|
99
|
+
if (element != null) {
|
|
100
|
+
const text = await element.textContent();
|
|
101
|
+
if (text && /sign\s*in|log\s*in/i.test(text)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Selector might not exist, continue
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check URL for login-related paths
|
|
111
|
+
const url = this.page.url();
|
|
112
|
+
if (/signin|login|auth/i.test(url)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected async extractData(): Promise<CodexQuotaData | null> {
|
|
120
|
+
if (!this.page) return null;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Wait for the analytics page to load
|
|
124
|
+
await this.page.waitForTimeout(3000);
|
|
125
|
+
|
|
126
|
+
// Try multiple selectors for usage data
|
|
127
|
+
const usageData = await this.extractUsageData();
|
|
128
|
+
if (!usageData) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
provider: "openai-codex",
|
|
134
|
+
...usageData,
|
|
135
|
+
scrapedAt: new Date().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`[codex] Extraction error: ${error}`);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async extractUsageData(): Promise<Omit<CodexQuotaData, "provider" | "scrapedAt"> | null> {
|
|
144
|
+
if (!this.page) return null;
|
|
145
|
+
|
|
146
|
+
// Strategy 1: Look for percentage display
|
|
147
|
+
const percentSelectors = [
|
|
148
|
+
// Common patterns for usage percentage
|
|
149
|
+
'[class*="usage"] [class*="percent"]',
|
|
150
|
+
'[data-testid*="usage"] [class*="progress"]',
|
|
151
|
+
'[class*="quota"] [class*="percent"]',
|
|
152
|
+
// Text content patterns
|
|
153
|
+
'text=/\\d+%/',
|
|
154
|
+
// SVG progress circles
|
|
155
|
+
'svg[class*="progress"]',
|
|
156
|
+
// Heading patterns
|
|
157
|
+
'h1:has-text("Usage") + *',
|
|
158
|
+
'h2:has-text("Usage") + *',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
for (const selector of percentSelectors) {
|
|
162
|
+
try {
|
|
163
|
+
const element = await this.page.$(selector);
|
|
164
|
+
if (element != null) {
|
|
165
|
+
const text = await element.textContent();
|
|
166
|
+
const percentMatch = text?.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
167
|
+
if (percentMatch) {
|
|
168
|
+
const usagePercent = parseFloat(percentMatch[1]);
|
|
169
|
+
if (!isNaN(usagePercent)) {
|
|
170
|
+
// Try to get additional data
|
|
171
|
+
const additionalData = await this.extractAdditionalData();
|
|
172
|
+
return {
|
|
173
|
+
usagePercent,
|
|
174
|
+
...additionalData,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Continue to next selector
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Strategy 2: Look for numbers in heading/title
|
|
185
|
+
const headingData = await this.extractFromHeadings();
|
|
186
|
+
if (headingData) {
|
|
187
|
+
return headingData;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Strategy 3: Look for chart/graph data (often contains raw numbers)
|
|
191
|
+
const chartData = await this.extractChartData();
|
|
192
|
+
if (chartData) {
|
|
193
|
+
return chartData;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async extractAdditionalData(): Promise<Partial<CodexQuotaData>> {
|
|
200
|
+
const result: Partial<CodexQuotaData> = {};
|
|
201
|
+
|
|
202
|
+
// Try to extract plan type
|
|
203
|
+
const planSelectors = [
|
|
204
|
+
'[class*="plan"]',
|
|
205
|
+
'[data-testid*="plan"]',
|
|
206
|
+
'text=/Pro|Plus|Team|Enterprise/i',
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const selector of planSelectors) {
|
|
210
|
+
try {
|
|
211
|
+
if (!this.page) continue;
|
|
212
|
+
const text = await this.page.locator(selector).first().textContent({ timeout: 2000 }).catch(() => null);
|
|
213
|
+
if (text && /Pro|Plus|Team|Enterprise/i.test(text)) {
|
|
214
|
+
result.planType = text.trim();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// Continue
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Try to extract quota amounts (e.g., "1.5 GB of 5 GB used")
|
|
223
|
+
const quotaText = await this.extractText('text=/\\d+.*\\/(?:of\\s+)?\\d+/');
|
|
224
|
+
if (quotaText) {
|
|
225
|
+
const match = quotaText.match(/([\d,.]+)\s*(?:GB|MB|TB)?\s*(?:\/|of)\s*([\d,.]+)\s*(?:GB|MB|TB)?/i);
|
|
226
|
+
if (match) {
|
|
227
|
+
result.usedQuota = match[1];
|
|
228
|
+
result.totalQuota = match[2];
|
|
229
|
+
result.remainingQuota = String(parseFloat(match[2]) - parseFloat(match[1]));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Try to extract reset date
|
|
234
|
+
const resetSelectors = [
|
|
235
|
+
'text=/resets?\\s+(at|on|in)/i',
|
|
236
|
+
'[class*="reset"]',
|
|
237
|
+
'[class*="renews"]',
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
for (const selector of resetSelectors) {
|
|
241
|
+
try {
|
|
242
|
+
if (!this.page) continue;
|
|
243
|
+
const text = await this.page.locator(selector).first().textContent({ timeout: 2000 }).catch(() => null);
|
|
244
|
+
if (text) {
|
|
245
|
+
result.resetAt = text.trim();
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
// Continue
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async extractFromHeadings(): Promise<Omit<CodexQuotaData, "provider" | "scrapedAt"> | null> {
|
|
257
|
+
if (!this.page) return null;
|
|
258
|
+
|
|
259
|
+
// Get all headings and look for ones containing numbers
|
|
260
|
+
const headings = await this.page.$$("h1, h2, h3, h4");
|
|
261
|
+
for (const heading of headings) {
|
|
262
|
+
const text = await heading.textContent();
|
|
263
|
+
if (text) {
|
|
264
|
+
const percentMatch = text.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
265
|
+
if (percentMatch) {
|
|
266
|
+
return {
|
|
267
|
+
usagePercent: parseFloat(percentMatch[1]),
|
|
268
|
+
period: text.replace(percentMatch[0], "").trim() || undefined,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async extractChartData(): Promise<Omit<CodexQuotaData, "provider" | "scrapedAt"> | null> {
|
|
278
|
+
if (!this.page) return null;
|
|
279
|
+
|
|
280
|
+
// Look for canvas or SVG charts
|
|
281
|
+
const charts = await this.page.$$("canvas, svg[class*='chart'], svg[class*='graph']");
|
|
282
|
+
if (charts.length > 0) {
|
|
283
|
+
// Try to get data from aria-labels or data attributes
|
|
284
|
+
for (const chart of charts) {
|
|
285
|
+
const ariaLabel = await chart.getAttribute("aria-label");
|
|
286
|
+
if (ariaLabel) {
|
|
287
|
+
const percentMatch = ariaLabel.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
288
|
+
if (percentMatch) {
|
|
289
|
+
return {
|
|
290
|
+
usagePercent: parseFloat(percentMatch[1]),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Try to extract from JavaScript data
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
298
|
+
const chartData = await this.page.evaluate(() => {
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
300
|
+
const win = (globalThis as any);
|
|
301
|
+
const doc = win.document;
|
|
302
|
+
if (!doc) return null;
|
|
303
|
+
const canvases = doc.querySelectorAll("canvas");
|
|
304
|
+
const foundResult: { percent: number } | null = null;
|
|
305
|
+
for (const canvas of canvases) {
|
|
306
|
+
const parent = canvas.parentElement;
|
|
307
|
+
if (parent) {
|
|
308
|
+
const attrNames: string[] = Array.from(parent.getAttributeNames());
|
|
309
|
+
for (const attrName of attrNames) {
|
|
310
|
+
if (attrName.startsWith("data-") || attrName.includes("chart")) {
|
|
311
|
+
const value = parent.getAttribute(attrName);
|
|
312
|
+
const match = value?.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
313
|
+
if (match) {
|
|
314
|
+
return { percent: parseFloat(match[1]) };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return foundResult;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (chartData?.percent) {
|
|
324
|
+
return { usagePercent: chartData.percent };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Quick scrape function
|
|
334
|
+
*/
|
|
335
|
+
export async function scrapeCodexQuota(): Promise<CodexQuotaData | null> {
|
|
336
|
+
const scraper = new CodexBrowserScraper({ headless: true });
|
|
337
|
+
try {
|
|
338
|
+
const result = await scraper.scrape();
|
|
339
|
+
return result.data ?? null;
|
|
340
|
+
} finally {
|
|
341
|
+
await scraper.cleanup();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
|
|
19
|
+
// Chrome profile management for persistent sessions
|
|
20
|
+
export {
|
|
21
|
+
ChromeProfileManager,
|
|
22
|
+
getChromeProfileManager,
|
|
23
|
+
detectChromeUserDataDir,
|
|
24
|
+
detectChromeBinary,
|
|
25
|
+
type ChromeProfileConfig,
|
|
26
|
+
type BrowserProfile,
|
|
27
|
+
} from "./chrome-profile-manager.js";
|
|
28
|
+
|
|
29
|
+
// Base scraper class
|
|
30
|
+
export {
|
|
31
|
+
BaseBrowserScraper,
|
|
32
|
+
type BrowserScraperConfig,
|
|
33
|
+
type QuotaScrapingResult,
|
|
34
|
+
} from "./base-browser-scraper.js";
|
|
35
|
+
|
|
36
|
+
// Provider-specific scrapers
|
|
37
|
+
export { CodexBrowserScraper, scrapeCodexQuota, type CodexQuotaData } from "./codex-browser-scraper.js";
|
|
38
|
+
export { ZaiBrowserScraper, scrapeZaiQuota, checkZaiCookies, type ZaiQuotaData } from "./zai-browser-scraper.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* All available browser scrapers
|
|
42
|
+
*/
|
|
43
|
+
export const BROWSER_SCRAPERS = {
|
|
44
|
+
"openai-codex": () => import("./codex-browser-scraper.js").then((m) => m.CodexBrowserScraper),
|
|
45
|
+
"z-ai": () => import("./zai-browser-scraper.js").then((m) => m.ZaiBrowserScraper),
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
export type BrowserScraperProvider = keyof typeof BROWSER_SCRAPERS;
|
|
@@ -0,0 +1,80 @@
|
|
|
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 {};
|
|
80
|
+
//# sourceMappingURL=zai-browser-scraper.d.ts.map
|