axusage 3.0.0 → 3.2.0
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/README.md +78 -194
- package/dist/adapters/claude.js +9 -8
- package/dist/adapters/coalesce-claude-usage-response.js +5 -7
- package/dist/adapters/{chatgpt.d.ts → codex.d.ts} +1 -1
- package/dist/adapters/{chatgpt.js → codex.js} +5 -5
- package/dist/adapters/copilot.d.ts +7 -0
- package/dist/adapters/copilot.js +58 -0
- package/dist/adapters/{parse-chatgpt-usage.d.ts → parse-codex-usage.d.ts} +3 -3
- package/dist/adapters/parse-copilot-usage.d.ts +15 -0
- package/dist/adapters/parse-copilot-usage.js +61 -0
- package/dist/cli.js +4 -21
- package/dist/commands/auth-setup-command.d.ts +1 -2
- package/dist/commands/auth-setup-command.js +44 -67
- package/dist/commands/auth-status-command.js +18 -38
- package/dist/commands/fetch-service-usage.d.ts +0 -1
- package/dist/commands/fetch-service-usage.js +1 -2
- package/dist/commands/run-auth-setup.d.ts +0 -10
- package/dist/commands/run-auth-setup.js +3 -80
- package/dist/commands/usage-command.d.ts +2 -7
- package/dist/commands/usage-command.js +7 -39
- package/dist/config/credential-sources.d.ts +12 -12
- package/dist/config/credential-sources.js +15 -2
- package/dist/services/get-service-access-token.d.ts +3 -3
- package/dist/services/get-service-access-token.js +11 -11
- package/dist/services/service-adapter-registry.d.ts +2 -2
- package/dist/services/service-adapter-registry.js +4 -4
- package/dist/services/supported-service.d.ts +6 -2
- package/dist/services/supported-service.js +2 -6
- package/dist/types/{chatgpt.d.ts → codex.d.ts} +4 -4
- package/dist/types/{chatgpt.js → codex.js} +6 -6
- package/dist/types/copilot.d.ts +14 -0
- package/dist/types/copilot.js +21 -0
- package/dist/utils/check-cli-dependency.d.ts +2 -4
- package/dist/utils/check-cli-dependency.js +7 -4
- package/dist/utils/copilot-gh-token.d.ts +1 -0
- package/dist/utils/copilot-gh-token.js +38 -0
- package/dist/utils/validate-root-options.d.ts +0 -3
- package/dist/utils/validate-root-options.js +2 -6
- package/package.json +15 -19
- package/dist/adapters/github-copilot.d.ts +0 -6
- package/dist/adapters/github-copilot.js +0 -57
- package/dist/adapters/parse-github-copilot-usage.d.ts +0 -23
- package/dist/adapters/parse-github-copilot-usage.js +0 -78
- package/dist/commands/auth-clear-command.d.ts +0 -7
- package/dist/commands/auth-clear-command.js +0 -84
- package/dist/commands/fetch-service-usage-with-reauth.d.ts +0 -7
- package/dist/commands/fetch-service-usage-with-reauth.js +0 -45
- package/dist/services/app-paths.d.ts +0 -9
- package/dist/services/app-paths.js +0 -39
- package/dist/services/auth-storage-path.d.ts +0 -3
- package/dist/services/auth-storage-path.js +0 -7
- package/dist/services/auth-timeouts.d.ts +0 -4
- package/dist/services/auth-timeouts.js +0 -4
- package/dist/services/browser-auth-manager.d.ts +0 -49
- package/dist/services/browser-auth-manager.js +0 -113
- package/dist/services/create-auth-context.d.ts +0 -8
- package/dist/services/create-auth-context.js +0 -35
- package/dist/services/do-setup-auth.d.ts +0 -3
- package/dist/services/do-setup-auth.js +0 -19
- package/dist/services/fetch-json-with-context.d.ts +0 -5
- package/dist/services/fetch-json-with-context.js +0 -37
- package/dist/services/launch-chromium.d.ts +0 -6
- package/dist/services/launch-chromium.js +0 -20
- package/dist/services/persist-storage-state.d.ts +0 -6
- package/dist/services/persist-storage-state.js +0 -16
- package/dist/services/request-service.d.ts +0 -3
- package/dist/services/request-service.js +0 -4
- package/dist/services/service-auth-configs.d.ts +0 -15
- package/dist/services/service-auth-configs.js +0 -26
- package/dist/services/setup-auth-flow.d.ts +0 -3
- package/dist/services/setup-auth-flow.js +0 -67
- package/dist/services/shared-browser-auth-manager.d.ts +0 -4
- package/dist/services/shared-browser-auth-manager.js +0 -80
- package/dist/services/verify-session.d.ts +0 -2
- package/dist/services/verify-session.js +0 -25
- package/dist/services/wait-for-login.d.ts +0 -6
- package/dist/services/wait-for-login.js +0 -115
- package/dist/types/github-copilot.d.ts +0 -21
- package/dist/types/github-copilot.js +0 -27
- package/dist/utils/resolve-prompt-capability.d.ts +0 -1
- package/dist/utils/resolve-prompt-capability.js +0 -3
- package/dist/utils/write-atomic-json.d.ts +0 -1
- package/dist/utils/write-atomic-json.js +0 -56
- /package/dist/adapters/{parse-chatgpt-usage.js → parse-codex-usage.js} +0 -0
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ServiceResult } from "../types/domain.js";
|
|
2
|
-
/**
|
|
3
|
-
* Fetch usage for a service, with automatic re-authentication on auth errors.
|
|
4
|
-
* Prompts the user to re-authenticate if the initial fetch fails with an auth error,
|
|
5
|
-
* then retries the fetch. Returns the original result if re-authentication fails.
|
|
6
|
-
*/
|
|
7
|
-
export declare function fetchServiceUsageWithAutoReauth(serviceName: string, interactive: boolean): Promise<ServiceResult>;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { fetchServiceUsage } from "./fetch-service-usage.js";
|
|
2
|
-
import { isAuthFailure, runAuthSetup } from "./run-auth-setup.js";
|
|
3
|
-
import { validateService } from "../services/supported-service.js";
|
|
4
|
-
import { chalk } from "../utils/color.js";
|
|
5
|
-
/**
|
|
6
|
-
* Fetch usage for a service, with automatic re-authentication on auth errors.
|
|
7
|
-
* Prompts the user to re-authenticate if the initial fetch fails with an auth error,
|
|
8
|
-
* then retries the fetch. Returns the original result if re-authentication fails.
|
|
9
|
-
*/
|
|
10
|
-
export async function fetchServiceUsageWithAutoReauth(serviceName, interactive) {
|
|
11
|
-
const result = await fetchServiceUsage(serviceName);
|
|
12
|
-
if (!interactive) {
|
|
13
|
-
return { service: serviceName, result };
|
|
14
|
-
}
|
|
15
|
-
// If auth error, try to re-authenticate and retry
|
|
16
|
-
if (isAuthFailure(result)) {
|
|
17
|
-
console.error(chalk.yellow(`⚠ Authentication failed for ${serviceName}. Attempting to re-authenticate...`));
|
|
18
|
-
try {
|
|
19
|
-
const service = validateService(serviceName);
|
|
20
|
-
const authSuccess = await runAuthSetup(service);
|
|
21
|
-
if (authSuccess) {
|
|
22
|
-
if (process.stderr.isTTY) {
|
|
23
|
-
console.error(chalk.blue(`Retrying ${serviceName} usage fetch...\n`));
|
|
24
|
-
}
|
|
25
|
-
const retryResult = await fetchServiceUsage(serviceName);
|
|
26
|
-
return { service: serviceName, result: retryResult };
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
console.error(chalk.red(`Re-authentication failed for ${serviceName}.`));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
34
|
-
// Distinguish validation errors from auth setup errors
|
|
35
|
-
const isValidationError = errorMessage.includes("Unsupported service") ||
|
|
36
|
-
errorMessage.includes("Service is required");
|
|
37
|
-
const prefix = isValidationError
|
|
38
|
-
? "Invalid service"
|
|
39
|
-
: "Failed to re-authenticate";
|
|
40
|
-
console.error(chalk.red(`${prefix}: ${errorMessage}`));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// Return original result if re-authentication failed or was not attempted
|
|
44
|
-
return { service: serviceName, result };
|
|
45
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Directory for storing browser authentication contexts
|
|
3
|
-
*/
|
|
4
|
-
export declare function getBrowserContextsDirectory(): string;
|
|
5
|
-
/**
|
|
6
|
-
* Ensure a directory exists with restricted permissions (owner-only access).
|
|
7
|
-
* Creates the directory recursively if needed and sets mode 0o700.
|
|
8
|
-
*/
|
|
9
|
-
export declare function ensureSecureDirectory(directoryPath: string): Promise<void>;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import envPaths from "env-paths";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { mkdir, chmod } from "node:fs/promises";
|
|
4
|
-
/**
|
|
5
|
-
* env-paths resolves directories during module initialization, so changes to
|
|
6
|
-
* environment variables (like XDG_DATA_HOME) after the first import will not
|
|
7
|
-
* be picked up without restarting the process.
|
|
8
|
-
*/
|
|
9
|
-
const paths = envPaths("axusage", { suffix: "" });
|
|
10
|
-
/**
|
|
11
|
-
* Directory for storing browser authentication contexts
|
|
12
|
-
*/
|
|
13
|
-
export function getBrowserContextsDirectory() {
|
|
14
|
-
return path.join(paths.data, "browser-contexts");
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Ensure a directory exists with restricted permissions (owner-only access).
|
|
18
|
-
* Creates the directory recursively if needed and sets mode 0o700.
|
|
19
|
-
*/
|
|
20
|
-
export async function ensureSecureDirectory(directoryPath) {
|
|
21
|
-
try {
|
|
22
|
-
await mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
// Only ignore EEXIST; re-throw everything else
|
|
26
|
-
// mkdir may ignore mode due to umask; we'll enforce via chmod below
|
|
27
|
-
const isEexist = error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
28
|
-
if (!isEexist) {
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
await chmod(directoryPath, 0o700);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// Best effort: some filesystems (network mounts, containers) may not
|
|
37
|
-
// support chmod or have different permission models
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
export function getStorageStatePathFor(dataDirectory, service) {
|
|
3
|
-
return path.join(dataDirectory, `${service}-auth.json`);
|
|
4
|
-
}
|
|
5
|
-
export function getAuthMetaPathFor(dataDirectory, service) {
|
|
6
|
-
return path.join(dataDirectory, `${service}-auth.meta.json`);
|
|
7
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { BrowserContext } from "playwright";
|
|
2
|
-
import type { SupportedService } from "./supported-service.js";
|
|
3
|
-
/**
|
|
4
|
-
* Configuration for browser authentication manager
|
|
5
|
-
*/
|
|
6
|
-
type BrowserAuthConfig = {
|
|
7
|
-
readonly dataDir?: string;
|
|
8
|
-
readonly headless?: boolean;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Manages browser contexts for persistent authentication across services
|
|
12
|
-
*/
|
|
13
|
-
export declare class BrowserAuthManager {
|
|
14
|
-
private readonly dataDir;
|
|
15
|
-
private readonly headless;
|
|
16
|
-
private browser;
|
|
17
|
-
private browserPromise;
|
|
18
|
-
constructor(config?: BrowserAuthConfig);
|
|
19
|
-
/**
|
|
20
|
-
* Get the storage state file path for a service
|
|
21
|
-
*/
|
|
22
|
-
private getStorageStatePath;
|
|
23
|
-
/**
|
|
24
|
-
* Check if a service has saved authentication
|
|
25
|
-
*/
|
|
26
|
-
hasAuth(service: SupportedService): boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Ensure a Chromium browser instance is available
|
|
29
|
-
*/
|
|
30
|
-
private ensureBrowser;
|
|
31
|
-
private launchAndStoreBrowser;
|
|
32
|
-
/**
|
|
33
|
-
* Set up authentication for a service by launching a browser for the user to log in
|
|
34
|
-
*/
|
|
35
|
-
setupAuth(service: SupportedService): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Get a browser context with saved authentication for a service
|
|
38
|
-
*/
|
|
39
|
-
getAuthContext(service: SupportedService): Promise<BrowserContext>;
|
|
40
|
-
/**
|
|
41
|
-
* Make an authenticated request to a URL using the browser context
|
|
42
|
-
*/
|
|
43
|
-
makeAuthenticatedRequest(service: SupportedService, url: string): Promise<string>;
|
|
44
|
-
/**
|
|
45
|
-
* Close the browser and clean up resources
|
|
46
|
-
*/
|
|
47
|
-
close(): Promise<void>;
|
|
48
|
-
}
|
|
49
|
-
export {};
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { getServiceAuthConfig } from "./service-auth-configs.js";
|
|
3
|
-
import { launchChromium } from "./launch-chromium.js";
|
|
4
|
-
import { requestService } from "./request-service.js";
|
|
5
|
-
import { doSetupAuth } from "./do-setup-auth.js";
|
|
6
|
-
import { getStorageStatePathFor } from "./auth-storage-path.js";
|
|
7
|
-
import { createAuthContext, loadStoredUserAgent, } from "./create-auth-context.js";
|
|
8
|
-
import { getBrowserContextsDirectory, ensureSecureDirectory, } from "./app-paths.js";
|
|
9
|
-
import { persistStorageState } from "./persist-storage-state.js";
|
|
10
|
-
/**
|
|
11
|
-
* Manages browser contexts for persistent authentication across services
|
|
12
|
-
*/
|
|
13
|
-
export class BrowserAuthManager {
|
|
14
|
-
dataDir;
|
|
15
|
-
headless;
|
|
16
|
-
browser = undefined;
|
|
17
|
-
browserPromise = undefined;
|
|
18
|
-
constructor(config = {}) {
|
|
19
|
-
this.dataDir = config.dataDir || getBrowserContextsDirectory();
|
|
20
|
-
// Default to headless for non-interactive usage flows; auth setup passes headless: false
|
|
21
|
-
this.headless = config.headless ?? true;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Get the storage state file path for a service
|
|
25
|
-
*/
|
|
26
|
-
getStorageStatePath(service) {
|
|
27
|
-
return getStorageStatePathFor(this.dataDir, service);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Check if a service has saved authentication
|
|
31
|
-
*/
|
|
32
|
-
hasAuth(service) {
|
|
33
|
-
return existsSync(this.getStorageStatePath(service));
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Ensure a Chromium browser instance is available
|
|
37
|
-
*/
|
|
38
|
-
async ensureBrowser() {
|
|
39
|
-
if (!this.browserPromise) {
|
|
40
|
-
this.browserPromise = this.launchAndStoreBrowser();
|
|
41
|
-
}
|
|
42
|
-
return this.browserPromise;
|
|
43
|
-
}
|
|
44
|
-
async launchAndStoreBrowser() {
|
|
45
|
-
try {
|
|
46
|
-
const browser = await launchChromium(this.headless);
|
|
47
|
-
this.browser = browser;
|
|
48
|
-
return browser;
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
// Allow retries on subsequent calls if the launch fails
|
|
52
|
-
this.browserPromise = undefined;
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Set up authentication for a service by launching a browser for the user to log in
|
|
58
|
-
*/
|
|
59
|
-
async setupAuth(service) {
|
|
60
|
-
const config = getServiceAuthConfig(service);
|
|
61
|
-
await ensureSecureDirectory(this.dataDir);
|
|
62
|
-
const browser = await this.ensureBrowser();
|
|
63
|
-
const storagePath = this.getStorageStatePath(service);
|
|
64
|
-
// Load existing storage state if available - this gives the browser a chance
|
|
65
|
-
// to refresh expired cookies/tokens during the login flow
|
|
66
|
-
const storageState = existsSync(storagePath) ? storagePath : undefined;
|
|
67
|
-
const userAgent = await loadStoredUserAgent(this.dataDir, service);
|
|
68
|
-
let context;
|
|
69
|
-
try {
|
|
70
|
-
context = await browser.newContext({ storageState, userAgent });
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// Corrupted storage state - fall back to fresh context
|
|
74
|
-
context = await browser.newContext({ userAgent });
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
await doSetupAuth(service, context, this.getStorageStatePath(service), config.instructions);
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
await context.close();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get a browser context with saved authentication for a service
|
|
85
|
-
*/
|
|
86
|
-
async getAuthContext(service) {
|
|
87
|
-
const browser = await this.ensureBrowser();
|
|
88
|
-
return createAuthContext(browser, this.dataDir, service);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Make an authenticated request to a URL using the browser context
|
|
92
|
-
*/
|
|
93
|
-
async makeAuthenticatedRequest(service, url) {
|
|
94
|
-
const context = await this.getAuthContext(service);
|
|
95
|
-
try {
|
|
96
|
-
return await requestService(service, url, () => Promise.resolve(context));
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
await persistStorageState(context, this.getStorageStatePath(service));
|
|
100
|
-
await context.close();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Close the browser and clean up resources
|
|
105
|
-
*/
|
|
106
|
-
async close() {
|
|
107
|
-
if (this.browser) {
|
|
108
|
-
await this.browser.close();
|
|
109
|
-
this.browser = undefined;
|
|
110
|
-
this.browserPromise = undefined;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Browser, BrowserContext } from "playwright";
|
|
2
|
-
import type { SupportedService } from "./supported-service.js";
|
|
3
|
-
/**
|
|
4
|
-
* Load the stored userAgent from the auth meta file for a service.
|
|
5
|
-
* Returns undefined if meta file doesn't exist or is invalid.
|
|
6
|
-
*/
|
|
7
|
-
export declare function loadStoredUserAgent(dataDirectory: string, service: SupportedService): Promise<string | undefined>;
|
|
8
|
-
export declare function createAuthContext(browser: Browser, dataDirectory: string, service: SupportedService): Promise<BrowserContext>;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { getAuthMetaPathFor, getStorageStatePathFor, } from "./auth-storage-path.js";
|
|
4
|
-
/**
|
|
5
|
-
* Load the stored userAgent from the auth meta file for a service.
|
|
6
|
-
* Returns undefined if meta file doesn't exist or is invalid.
|
|
7
|
-
*/
|
|
8
|
-
export async function loadStoredUserAgent(dataDirectory, service) {
|
|
9
|
-
try {
|
|
10
|
-
const metaPath = getAuthMetaPathFor(dataDirectory, service);
|
|
11
|
-
const metaRaw = await readFile(metaPath, "utf8");
|
|
12
|
-
const meta = JSON.parse(metaRaw);
|
|
13
|
-
// Validate the parsed structure at runtime
|
|
14
|
-
if (meta &&
|
|
15
|
-
typeof meta === "object" &&
|
|
16
|
-
"userAgent" in meta &&
|
|
17
|
-
typeof meta.userAgent === "string") {
|
|
18
|
-
return meta.userAgent;
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// Meta file missing, unreadable, or contains invalid JSON
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export async function createAuthContext(browser, dataDirectory, service) {
|
|
28
|
-
const storageStatePath = getStorageStatePathFor(dataDirectory, service);
|
|
29
|
-
if (!existsSync(storageStatePath)) {
|
|
30
|
-
throw new Error(`No saved authentication for ${service}. ` +
|
|
31
|
-
`Run 'axusage --auth-setup ${service} --interactive' first.`);
|
|
32
|
-
}
|
|
33
|
-
const userAgent = await loadStoredUserAgent(dataDirectory, service);
|
|
34
|
-
return browser.newContext({ storageState: storageStatePath, userAgent });
|
|
35
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { setupAuthInContext } from "./setup-auth-flow.js";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { getAuthMetaPathFor } from "./auth-storage-path.js";
|
|
4
|
-
import { writeAtomicJson } from "../utils/write-atomic-json.js";
|
|
5
|
-
export async function doSetupAuth(service, context, storagePath, instructions) {
|
|
6
|
-
console.error(`\n${instructions}`);
|
|
7
|
-
console.error("Waiting for login to complete (or press Enter to continue)\n");
|
|
8
|
-
const userAgent = await setupAuthInContext(service, context, storagePath);
|
|
9
|
-
try {
|
|
10
|
-
if (userAgent) {
|
|
11
|
-
const metaPath = getAuthMetaPathFor(path.dirname(storagePath), service);
|
|
12
|
-
await writeAtomicJson(metaPath, { userAgent }, 0o600);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
// ignore errors when writing meta; not critical
|
|
17
|
-
}
|
|
18
|
-
console.error(`\n✓ Authentication saved for ${service}. You can now close the browser.`);
|
|
19
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fetch JSON using the page's fetch API so cookies/session are included without navigation.
|
|
3
|
-
*/
|
|
4
|
-
export async function fetchJsonWithContext(context, url) {
|
|
5
|
-
const page = await context.newPage();
|
|
6
|
-
try {
|
|
7
|
-
const origin = new URL(url).origin;
|
|
8
|
-
await page.goto(origin + "/", { waitUntil: "domcontentloaded" });
|
|
9
|
-
const result = await page.evaluate(async (targetUrl) => {
|
|
10
|
-
const response = await fetch(targetUrl, {
|
|
11
|
-
credentials: "include",
|
|
12
|
-
headers: {
|
|
13
|
-
Accept: "application/json, text/plain, */*",
|
|
14
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
const text = await response.text();
|
|
18
|
-
return {
|
|
19
|
-
ok: response.ok,
|
|
20
|
-
status: response.status,
|
|
21
|
-
statusText: response.statusText,
|
|
22
|
-
contentType: response.headers.get("content-type") || "",
|
|
23
|
-
text,
|
|
24
|
-
};
|
|
25
|
-
}, url);
|
|
26
|
-
if (!result.ok) {
|
|
27
|
-
throw new Error(`Request failed: ${String(result.status)} ${result.statusText}`);
|
|
28
|
-
}
|
|
29
|
-
if (!result.contentType.toLowerCase().startsWith("application/json")) {
|
|
30
|
-
throw new Error(`Expected JSON response, got ${result.contentType}`);
|
|
31
|
-
}
|
|
32
|
-
return result.text;
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
await page.close();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { chromium } from "playwright";
|
|
2
|
-
/**
|
|
3
|
-
* Launch Chromium with automation indicators disabled to reduce Cloudflare bot detection
|
|
4
|
-
* during the authentication flow.
|
|
5
|
-
*/
|
|
6
|
-
export async function launchChromium(headless) {
|
|
7
|
-
try {
|
|
8
|
-
return await chromium.launch({
|
|
9
|
-
headless,
|
|
10
|
-
args: ["--disable-blink-features=AutomationControlled"],
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
-
if (/Executable doesn't exist|playwright\s+install/iu.test(message)) {
|
|
16
|
-
throw new Error("Playwright browsers are not installed. This is usually handled automatically by the postinstall script in package.json. Please try reinstalling the package or check if the postinstall script ran successfully. If the problem persists, you can manually run `pnpm exec playwright install chromium` or `npx playwright install chromium`, then retry.");
|
|
17
|
-
}
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { BrowserContext } from "playwright";
|
|
2
|
-
/**
|
|
3
|
-
* Persist context storage state to disk with secure permissions (0o600).
|
|
4
|
-
* Errors are logged as warnings to avoid blocking the main operation.
|
|
5
|
-
*/
|
|
6
|
-
export declare function persistStorageState(context: BrowserContext, storagePath: string): Promise<void>;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { writeAtomicJson } from "../utils/write-atomic-json.js";
|
|
2
|
-
import { chalk } from "../utils/color.js";
|
|
3
|
-
/**
|
|
4
|
-
* Persist context storage state to disk with secure permissions (0o600).
|
|
5
|
-
* Errors are logged as warnings to avoid blocking the main operation.
|
|
6
|
-
*/
|
|
7
|
-
export async function persistStorageState(context, storagePath) {
|
|
8
|
-
try {
|
|
9
|
-
const state = await context.storageState();
|
|
10
|
-
await writeAtomicJson(storagePath, state, 0o600);
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
const details = error instanceof Error ? error.message : String(error);
|
|
14
|
-
console.error(chalk.yellow(`Warning: Failed to persist auth state to ${storagePath} (${details}).`));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { SupportedService } from "./supported-service.js";
|
|
2
|
-
import type { BrowserContext } from "playwright";
|
|
3
|
-
/**
|
|
4
|
-
* Service-specific configuration for authentication
|
|
5
|
-
*/
|
|
6
|
-
type ServiceAuthConfig = {
|
|
7
|
-
readonly url: string;
|
|
8
|
-
readonly waitForSelector?: string;
|
|
9
|
-
readonly waitForSelectors?: readonly string[];
|
|
10
|
-
readonly verifyUrl?: string;
|
|
11
|
-
readonly verifyFunction?: (context: BrowserContext, url: string) => Promise<boolean>;
|
|
12
|
-
readonly instructions: string;
|
|
13
|
-
};
|
|
14
|
-
export declare function getServiceAuthConfig(service: SupportedService): ServiceAuthConfig;
|
|
15
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const SERVICE_AUTH_CONFIGS = {
|
|
2
|
-
// Claude uses CLI-based auth via Claude Code credentials
|
|
3
|
-
claude: {
|
|
4
|
-
url: "",
|
|
5
|
-
instructions: "Claude uses Claude Code authentication. Run 'claude' in your terminal to authenticate.",
|
|
6
|
-
},
|
|
7
|
-
// ChatGPT uses CLI-based auth via Codex credentials
|
|
8
|
-
chatgpt: {
|
|
9
|
-
url: "",
|
|
10
|
-
instructions: "ChatGPT uses Codex CLI authentication. Run 'codex' in your terminal to authenticate.",
|
|
11
|
-
},
|
|
12
|
-
"github-copilot": {
|
|
13
|
-
url: "https://github.com/login",
|
|
14
|
-
waitForSelector: 'img[alt*="@"]',
|
|
15
|
-
verifyUrl: "https://github.com/github-copilot/chat/entitlement",
|
|
16
|
-
instructions: "Please log in to your GitHub account in the browser window.",
|
|
17
|
-
},
|
|
18
|
-
// Gemini uses CLI-based auth, not browser auth. This entry exists only to satisfy the Record type.
|
|
19
|
-
gemini: {
|
|
20
|
-
url: "",
|
|
21
|
-
instructions: "Gemini uses CLI-based authentication. Run 'gemini' in your terminal.",
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
export function getServiceAuthConfig(service) {
|
|
25
|
-
return SERVICE_AUTH_CONFIGS[service];
|
|
26
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { getServiceAuthConfig } from "./service-auth-configs.js";
|
|
2
|
-
import { waitForLogin } from "./wait-for-login.js";
|
|
3
|
-
import { verifySessionByFetching } from "./verify-session.js";
|
|
4
|
-
import { writeAtomicJson } from "../utils/write-atomic-json.js";
|
|
5
|
-
function describeLoginOutcome(outcome) {
|
|
6
|
-
switch (outcome) {
|
|
7
|
-
case "manual": {
|
|
8
|
-
return "after manual continuation";
|
|
9
|
-
}
|
|
10
|
-
case "timeout": {
|
|
11
|
-
return "after login timeout";
|
|
12
|
-
}
|
|
13
|
-
case "closed": {
|
|
14
|
-
return "after the browser window closed";
|
|
15
|
-
}
|
|
16
|
-
case "aborted": {
|
|
17
|
-
return "after prompt cancellation";
|
|
18
|
-
}
|
|
19
|
-
case "selector": {
|
|
20
|
-
return "after detecting a login signal";
|
|
21
|
-
}
|
|
22
|
-
case "skipped": {
|
|
23
|
-
return "without waiting for a login signal";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export async function setupAuthInContext(service, context, storagePath) {
|
|
28
|
-
const page = await context.newPage();
|
|
29
|
-
try {
|
|
30
|
-
const config = getServiceAuthConfig(service);
|
|
31
|
-
await page.goto(config.url);
|
|
32
|
-
const selectors = config.waitForSelectors ??
|
|
33
|
-
(config.waitForSelector ? [config.waitForSelector] : []);
|
|
34
|
-
const loginOutcome = await waitForLoginForService(page, selectors);
|
|
35
|
-
const outcomeLabel = describeLoginOutcome(loginOutcome);
|
|
36
|
-
if (loginOutcome === "aborted") {
|
|
37
|
-
throw new Error("Authentication was canceled. Authentication was not saved.");
|
|
38
|
-
}
|
|
39
|
-
if (config.verifyUrl) {
|
|
40
|
-
const ok = config.verifyFunction
|
|
41
|
-
? await config.verifyFunction(context, config.verifyUrl)
|
|
42
|
-
: await verifySessionByFetching(context, config.verifyUrl);
|
|
43
|
-
if (!ok) {
|
|
44
|
-
throw new Error(`Unable to verify session via ${config.verifyUrl} ${outcomeLabel}. Authentication was not saved. Ensure login completed successfully and retry.`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
else if (selectors.length > 0 && loginOutcome !== "selector") {
|
|
48
|
-
// Without a verification URL, we only persist when a login selector confirms success.
|
|
49
|
-
throw new Error(`Login was not confirmed ${outcomeLabel}. Authentication was not saved.`);
|
|
50
|
-
}
|
|
51
|
-
// Capture user agent for future headless contexts
|
|
52
|
-
const userAgent = await page.evaluate(() => navigator.userAgent);
|
|
53
|
-
const state = await context.storageState();
|
|
54
|
-
await writeAtomicJson(storagePath, state, 0o600);
|
|
55
|
-
return userAgent;
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
await page.close();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function waitForLoginForService(page, selectors) {
|
|
62
|
-
if (selectors.length > 0) {
|
|
63
|
-
return waitForLogin(page, selectors);
|
|
64
|
-
}
|
|
65
|
-
// When no selectors are configured, skip waiting and rely on verification if available.
|
|
66
|
-
return "skipped";
|
|
67
|
-
}
|