@love-moon/chat-web 0.3.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/CHANGELOG.md +8 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +142 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +27 -0
- package/dist/commands/doctor.js +116 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/info.d.ts +32 -0
- package/dist/commands/info.js +81 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +61 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/core/browser.d.ts +70 -0
- package/dist/core/browser.js +96 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/errors.d.ts +60 -0
- package/dist/core/errors.js +153 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/install-chromium.d.ts +55 -0
- package/dist/core/install-chromium.js +156 -0
- package/dist/core/install-chromium.js.map +1 -0
- package/dist/core/keyboard.d.ts +39 -0
- package/dist/core/keyboard.js +54 -0
- package/dist/core/keyboard.js.map +1 -0
- package/dist/core/locator-score.d.ts +41 -0
- package/dist/core/locator-score.js +101 -0
- package/dist/core/locator-score.js.map +1 -0
- package/dist/core/logger.d.ts +10 -0
- package/dist/core/logger.js +38 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/navigate.d.ts +52 -0
- package/dist/core/navigate.js +102 -0
- package/dist/core/navigate.js.map +1 -0
- package/dist/core/paths.d.ts +12 -0
- package/dist/core/paths.js +30 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/profile-manager.d.ts +13 -0
- package/dist/core/profile-manager.js +44 -0
- package/dist/core/profile-manager.js.map +1 -0
- package/dist/core/provider.d.ts +64 -0
- package/dist/core/provider.js +31 -0
- package/dist/core/provider.js.map +1 -0
- package/dist/core/response-watcher.d.ts +35 -0
- package/dist/core/response-watcher.js +70 -0
- package/dist/core/response-watcher.js.map +1 -0
- package/dist/core/snapshot.d.ts +38 -0
- package/dist/core/snapshot.js +137 -0
- package/dist/core/snapshot.js.map +1 -0
- package/dist/core/sse-parser.d.ts +20 -0
- package/dist/core/sse-parser.js +49 -0
- package/dist/core/sse-parser.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/chatgpt-sse-collector.d.ts +76 -0
- package/dist/providers/chatgpt-sse-collector.js +298 -0
- package/dist/providers/chatgpt-sse-collector.js.map +1 -0
- package/dist/providers/chatgpt.d.ts +56 -0
- package/dist/providers/chatgpt.js +357 -0
- package/dist/providers/chatgpt.js.map +1 -0
- package/dist/providers/deepseek.d.ts +22 -0
- package/dist/providers/deepseek.js +153 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/gemini.d.ts +102 -0
- package/dist/providers/gemini.js +480 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/session.d.ts +121 -0
- package/dist/session.js +242 -0
- package/dist/session.js.map +1 -0
- package/package.json +47 -0
package/CHANGELOG.md
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
import { doctor, formatDoctorReport } from "./commands/doctor.js";
|
|
4
|
+
import { info, formatInfo } from "./commands/info.js";
|
|
5
|
+
import { login } from "./commands/login.js";
|
|
6
|
+
import { ChatWebError } from "./core/errors.js";
|
|
7
|
+
import { createLogger } from "./core/logger.js";
|
|
8
|
+
import { registerBuiltinProviders } from "./providers/index.js";
|
|
9
|
+
const USAGE = `chat-web — chat web automation runtime
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
chat-web login <provider> Open a browser to sign in; profile is persisted.
|
|
13
|
+
chat-web doctor <provider> [--snapshot] Inspect provider page state.
|
|
14
|
+
[--screenshot]
|
|
15
|
+
[--html]
|
|
16
|
+
[--json]
|
|
17
|
+
chat-web info [<provider>] [--live] [--json] Show login state for one or all providers.
|
|
18
|
+
|
|
19
|
+
For programmatic multi-turn chat use the SDK:
|
|
20
|
+
|
|
21
|
+
import { ChatSession } from "@love-moon/chat-web";
|
|
22
|
+
|
|
23
|
+
const session = await ChatSession.open("chatgpt");
|
|
24
|
+
try {
|
|
25
|
+
const r1 = await session.send("hello");
|
|
26
|
+
const r2 = await session.send("tell me more");
|
|
27
|
+
console.log(r1.response, r2.response);
|
|
28
|
+
} finally {
|
|
29
|
+
await session.close();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Environment:
|
|
33
|
+
CHAT_WEB_HOME Override the ~/.chat-web/ root directory.
|
|
34
|
+
CHAT_WEB_HEADLESS "1" to launch Chromium headless by default.
|
|
35
|
+
CHAT_WEB_LOG silent | error | warn | info | debug
|
|
36
|
+
`;
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const positional = [];
|
|
39
|
+
const flags = new Map();
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const arg = argv[i];
|
|
42
|
+
if (arg.startsWith("--")) {
|
|
43
|
+
const key = arg.slice(2);
|
|
44
|
+
const next = argv[i + 1];
|
|
45
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
46
|
+
flags.set(key, next);
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
flags.set(key, true);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
positional.push(arg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { positional, flags };
|
|
58
|
+
}
|
|
59
|
+
function flagBool(flags, key) {
|
|
60
|
+
const v = flags.get(key);
|
|
61
|
+
return v === true || v === "true";
|
|
62
|
+
}
|
|
63
|
+
function flagString(flags, key) {
|
|
64
|
+
const v = flags.get(key);
|
|
65
|
+
return typeof v === "string" ? v : undefined;
|
|
66
|
+
}
|
|
67
|
+
async function main() {
|
|
68
|
+
registerBuiltinProviders();
|
|
69
|
+
const argv = process.argv.slice(2);
|
|
70
|
+
if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
|
|
71
|
+
process.stdout.write(USAGE);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
const [command, ...rest] = argv;
|
|
75
|
+
const { positional, flags } = parseArgs(rest);
|
|
76
|
+
const logLevel = flagString(flags, "log") ?? undefined;
|
|
77
|
+
const logger = createLogger(logLevel ?? process.env.CHAT_WEB_LOG ?? "info");
|
|
78
|
+
switch (command) {
|
|
79
|
+
case "login": {
|
|
80
|
+
const provider = positional[0];
|
|
81
|
+
if (!provider)
|
|
82
|
+
throw new Error("Usage: chat-web login <provider>");
|
|
83
|
+
await login(provider, {
|
|
84
|
+
logger,
|
|
85
|
+
autoExit: flagBool(flags, "auto-exit"),
|
|
86
|
+
});
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
case "doctor": {
|
|
90
|
+
const provider = positional[0];
|
|
91
|
+
if (!provider)
|
|
92
|
+
throw new Error("Usage: chat-web doctor <provider>");
|
|
93
|
+
const report = await doctor(provider, {
|
|
94
|
+
snapshot: flagBool(flags, "snapshot"),
|
|
95
|
+
screenshot: flagBool(flags, "screenshot"),
|
|
96
|
+
html: flagBool(flags, "html"),
|
|
97
|
+
headless: flagBool(flags, "headless") || undefined,
|
|
98
|
+
logger,
|
|
99
|
+
});
|
|
100
|
+
if (flagBool(flags, "json")) {
|
|
101
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
process.stdout.write(`${formatDoctorReport(report)}\n`);
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
case "info": {
|
|
109
|
+
const provider = positional[0]; // optional
|
|
110
|
+
const rows = await info({
|
|
111
|
+
provider,
|
|
112
|
+
live: flagBool(flags, "live"),
|
|
113
|
+
headless: flagBool(flags, "headless") || undefined,
|
|
114
|
+
logger,
|
|
115
|
+
});
|
|
116
|
+
if (flagBool(flags, "json")) {
|
|
117
|
+
process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
process.stdout.write(`${formatInfo(rows)}\n`);
|
|
121
|
+
}
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
process.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
main().then((code) => {
|
|
130
|
+
if (typeof code === "number")
|
|
131
|
+
process.exit(code);
|
|
132
|
+
}, (err) => {
|
|
133
|
+
if (err instanceof ChatWebError) {
|
|
134
|
+
process.stderr.write(`Error [${err.code}]: ${err.message}\n`);
|
|
135
|
+
if (err.hint)
|
|
136
|
+
process.stderr.write(`Hint: ${err.hint}\n`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
process.stderr.write(`${err.stack ?? String(err)}\n`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Logger } from "../core/logger.js";
|
|
2
|
+
import { type ProviderDiagnostics } from "../core/provider.js";
|
|
3
|
+
export interface DoctorOptions {
|
|
4
|
+
/** Dump the lightweight snapshot to ~/.chat-web/logs/. */
|
|
5
|
+
snapshot?: boolean;
|
|
6
|
+
/** Dump a PNG screenshot to ~/.chat-web/logs/. */
|
|
7
|
+
screenshot?: boolean;
|
|
8
|
+
/** Dump the raw HTML to ~/.chat-web/logs/. */
|
|
9
|
+
html?: boolean;
|
|
10
|
+
logger?: Logger;
|
|
11
|
+
headless?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DoctorReport extends ProviderDiagnostics {
|
|
14
|
+
provider: string;
|
|
15
|
+
profileDir: string;
|
|
16
|
+
snapshotFile?: string;
|
|
17
|
+
screenshotFile?: string;
|
|
18
|
+
htmlFile?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run a non-destructive health check for a provider profile.
|
|
22
|
+
*
|
|
23
|
+
* Doctor never sends a real chat message — it only opens the provider,
|
|
24
|
+
* inspects the DOM, and optionally dumps debugging artifacts.
|
|
25
|
+
*/
|
|
26
|
+
export declare function doctor(providerName: string, options?: DoctorOptions): Promise<DoctorReport>;
|
|
27
|
+
export declare function formatDoctorReport(r: DoctorReport): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { launchProviderBrowser } from "../core/browser.js";
|
|
4
|
+
import { defaultLogger } from "../core/logger.js";
|
|
5
|
+
import { logsDir } from "../core/paths.js";
|
|
6
|
+
import { createProfileManager } from "../core/profile-manager.js";
|
|
7
|
+
import { getProvider } from "../core/provider.js";
|
|
8
|
+
import { formatSnapshot, takeSnapshot } from "../core/snapshot.js";
|
|
9
|
+
/**
|
|
10
|
+
* Run a non-destructive health check for a provider profile.
|
|
11
|
+
*
|
|
12
|
+
* Doctor never sends a real chat message — it only opens the provider,
|
|
13
|
+
* inspects the DOM, and optionally dumps debugging artifacts.
|
|
14
|
+
*/
|
|
15
|
+
export async function doctor(providerName, options = {}) {
|
|
16
|
+
const logger = options.logger ?? defaultLogger;
|
|
17
|
+
const provider = getProvider(providerName);
|
|
18
|
+
const profileManager = createProfileManager();
|
|
19
|
+
const profileDir = profileManager.getProfileDir(providerName);
|
|
20
|
+
const { context, page } = await launchProviderBrowser(providerName, {
|
|
21
|
+
headless: options.headless ?? false,
|
|
22
|
+
profileManager,
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
await provider.open(page);
|
|
26
|
+
// Best-effort settle: most chat sites finish JS init shortly after
|
|
27
|
+
// domcontentloaded. We don't fail on timeout — we just want to give
|
|
28
|
+
// ProseMirror / app shell a chance to render before snapshotting, so
|
|
29
|
+
// doctor sees the same DOM the runtime sees.
|
|
30
|
+
await page.waitForLoadState("networkidle", { timeout: 8_000 }).catch(() => undefined);
|
|
31
|
+
const diagnostics = provider.diagnose
|
|
32
|
+
? await provider.diagnose(page)
|
|
33
|
+
: await fallbackDiagnose(provider, page);
|
|
34
|
+
let snapshotFile;
|
|
35
|
+
let screenshotFile;
|
|
36
|
+
let htmlFile;
|
|
37
|
+
if (options.snapshot || options.screenshot || options.html) {
|
|
38
|
+
await fs.mkdir(logsDir(), { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
41
|
+
if (options.snapshot) {
|
|
42
|
+
const snap = await takeSnapshot(page);
|
|
43
|
+
snapshotFile = path.join(logsDir(), `${ts}-${providerName}-snapshot.json`);
|
|
44
|
+
await fs.writeFile(snapshotFile, JSON.stringify(snap, null, 2), "utf8");
|
|
45
|
+
logger.info(`Wrote snapshot to ${snapshotFile}`);
|
|
46
|
+
// Also print a human-readable form to stderr for quick inspection.
|
|
47
|
+
logger.debug(formatSnapshot(snap));
|
|
48
|
+
}
|
|
49
|
+
if (options.screenshot) {
|
|
50
|
+
screenshotFile = path.join(logsDir(), `${ts}-${providerName}-screenshot.png`);
|
|
51
|
+
await page.screenshot({ path: screenshotFile, fullPage: true });
|
|
52
|
+
logger.info(`Wrote screenshot to ${screenshotFile}`);
|
|
53
|
+
}
|
|
54
|
+
if (options.html) {
|
|
55
|
+
htmlFile = path.join(logsDir(), `${ts}-${providerName}-page.html`);
|
|
56
|
+
const html = await page.content();
|
|
57
|
+
await fs.writeFile(htmlFile, html, "utf8");
|
|
58
|
+
logger.info(`Wrote HTML to ${htmlFile}`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...diagnostics,
|
|
62
|
+
provider: providerName,
|
|
63
|
+
profileDir,
|
|
64
|
+
snapshotFile,
|
|
65
|
+
screenshotFile,
|
|
66
|
+
htmlFile,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await context.close().catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function fallbackDiagnose(provider, page) {
|
|
74
|
+
const [loggedIn, sendBtn] = await Promise.all([
|
|
75
|
+
provider.isLoggedIn(page),
|
|
76
|
+
provider.findSendButton(page).then((l) => l !== null).catch(() => false),
|
|
77
|
+
]);
|
|
78
|
+
let inputFound = false;
|
|
79
|
+
try {
|
|
80
|
+
await provider.findInput(page);
|
|
81
|
+
inputFound = true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
inputFound = false;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
loggedIn,
|
|
88
|
+
inputFound,
|
|
89
|
+
sendButtonFound: sendBtn,
|
|
90
|
+
assistantMessageCount: 0,
|
|
91
|
+
stopButtonFound: false,
|
|
92
|
+
lastAssistantLength: 0,
|
|
93
|
+
pageUrl: page.url(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function formatDoctorReport(r) {
|
|
97
|
+
const lines = [
|
|
98
|
+
`Provider: ${r.provider}`,
|
|
99
|
+
`Profile: ${r.profileDir}`,
|
|
100
|
+
`Page URL: ${r.pageUrl}`,
|
|
101
|
+
`Login: ${r.loggedIn}`,
|
|
102
|
+
`Input found: ${r.inputFound}`,
|
|
103
|
+
`Send button found: ${r.sendButtonFound}`,
|
|
104
|
+
`Assistant messages: ${r.assistantMessageCount}`,
|
|
105
|
+
`Stop button found: ${r.stopButtonFound}`,
|
|
106
|
+
`Last assistant length: ${r.lastAssistantLength}`,
|
|
107
|
+
];
|
|
108
|
+
if (r.snapshotFile)
|
|
109
|
+
lines.push(`Snapshot: ${r.snapshotFile}`);
|
|
110
|
+
if (r.screenshotFile)
|
|
111
|
+
lines.push(`Screenshot: ${r.screenshotFile}`);
|
|
112
|
+
if (r.htmlFile)
|
|
113
|
+
lines.push(`HTML: ${r.htmlFile}`);
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAyC,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,IAAI,EAAE,UAAU,EAAuC,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Logger } from "../core/logger.js";
|
|
2
|
+
export interface InfoOptions {
|
|
3
|
+
/** Restrict the report to a single provider. */
|
|
4
|
+
provider?: string;
|
|
5
|
+
/** Actually launch Chromium to verify isLoggedIn live. Default false (fast filesystem-only mode). */
|
|
6
|
+
live?: boolean;
|
|
7
|
+
/** When live, launch headless. Default true (info is usually run from scripts). */
|
|
8
|
+
headless?: boolean;
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
}
|
|
11
|
+
export interface ProviderInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
homeUrl: string;
|
|
14
|
+
profileDir: string;
|
|
15
|
+
profileExists: boolean;
|
|
16
|
+
/** Last-modified time of the profile dir (ISO-8601), if it exists. */
|
|
17
|
+
profileLastUsed?: string;
|
|
18
|
+
/** Only populated when `live: true`. */
|
|
19
|
+
loggedIn?: boolean;
|
|
20
|
+
/** Filled if a live check failed. */
|
|
21
|
+
liveError?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Inspect provider login state.
|
|
25
|
+
*
|
|
26
|
+
* Default mode is filesystem-only: cheap, doesn't launch a browser, just
|
|
27
|
+
* tells you which providers have a persisted profile and when it was last
|
|
28
|
+
* touched. Pass `live: true` to actually open Chromium and call
|
|
29
|
+
* `isLoggedIn` on each provider — that's accurate but slow.
|
|
30
|
+
*/
|
|
31
|
+
export declare function info(options?: InfoOptions): Promise<ProviderInfo[]>;
|
|
32
|
+
export declare function formatInfo(rows: ProviderInfo[]): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { defaultLogger } from "../core/logger.js";
|
|
3
|
+
import { createProfileManager } from "../core/profile-manager.js";
|
|
4
|
+
import { listProviders, getProvider } from "../core/provider.js";
|
|
5
|
+
import { ChatSession } from "../session.js";
|
|
6
|
+
/**
|
|
7
|
+
* Inspect provider login state.
|
|
8
|
+
*
|
|
9
|
+
* Default mode is filesystem-only: cheap, doesn't launch a browser, just
|
|
10
|
+
* tells you which providers have a persisted profile and when it was last
|
|
11
|
+
* touched. Pass `live: true` to actually open Chromium and call
|
|
12
|
+
* `isLoggedIn` on each provider — that's accurate but slow.
|
|
13
|
+
*/
|
|
14
|
+
export async function info(options = {}) {
|
|
15
|
+
const logger = options.logger ?? defaultLogger;
|
|
16
|
+
const profileManager = createProfileManager();
|
|
17
|
+
const names = options.provider ? [options.provider] : listProviders();
|
|
18
|
+
const results = [];
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
const provider = getProvider(name);
|
|
21
|
+
const dir = profileManager.getProfileDir(name);
|
|
22
|
+
const stat = await fs.stat(dir).catch(() => null);
|
|
23
|
+
const profileExists = stat !== null && stat.isDirectory();
|
|
24
|
+
const profileLastUsed = stat ? stat.mtime.toISOString() : undefined;
|
|
25
|
+
const entry = {
|
|
26
|
+
name,
|
|
27
|
+
homeUrl: provider.homeUrl,
|
|
28
|
+
profileDir: dir,
|
|
29
|
+
profileExists,
|
|
30
|
+
profileLastUsed,
|
|
31
|
+
};
|
|
32
|
+
if (options.live) {
|
|
33
|
+
try {
|
|
34
|
+
const session = await ChatSession.open(name, {
|
|
35
|
+
headless: options.headless ?? true,
|
|
36
|
+
logger,
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
entry.loggedIn = await session.isLoggedIn();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await session.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
entry.liveError = err.message;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
results.push(entry);
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
export function formatInfo(rows) {
|
|
54
|
+
if (rows.length === 0)
|
|
55
|
+
return "No providers registered.";
|
|
56
|
+
const lines = [];
|
|
57
|
+
for (const r of rows) {
|
|
58
|
+
lines.push(`Provider: ${r.name}`);
|
|
59
|
+
lines.push(` Home: ${r.homeUrl}`);
|
|
60
|
+
lines.push(` Profile dir: ${r.profileDir}`);
|
|
61
|
+
lines.push(` Profile saved: ${r.profileExists ? "yes" : "no"}`);
|
|
62
|
+
if (r.profileLastUsed) {
|
|
63
|
+
lines.push(` Last used: ${r.profileLastUsed}`);
|
|
64
|
+
}
|
|
65
|
+
if (r.loggedIn !== undefined) {
|
|
66
|
+
lines.push(` Logged in: ${r.loggedIn ? "yes" : "no"}`);
|
|
67
|
+
}
|
|
68
|
+
else if (r.liveError) {
|
|
69
|
+
lines.push(` Logged in: unknown (${r.liveError})`);
|
|
70
|
+
}
|
|
71
|
+
else if (!r.profileExists) {
|
|
72
|
+
lines.push(` Logged in: no (run: chat-web login ${r.name})`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
lines.push(` Logged in: unknown (pass --live to check)`);
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
return lines.join("\n").trimEnd();
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.js","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Logger } from "../core/logger.js";
|
|
2
|
+
export interface LoginOptions {
|
|
3
|
+
logger?: Logger;
|
|
4
|
+
/** When true, exit as soon as the provider reports a logged-in state instead of waiting for the user to press Enter. */
|
|
5
|
+
autoExit?: boolean;
|
|
6
|
+
/** Poll interval for autoExit. */
|
|
7
|
+
pollIntervalMs?: number;
|
|
8
|
+
/** Hard upper bound for the login flow. */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Open a headed browser so the user can sign in manually.
|
|
13
|
+
*
|
|
14
|
+
* We never automate the actual sign-in (no email/password handling, no
|
|
15
|
+
* captcha bypass). The whole point of `chat-web login` is to give the
|
|
16
|
+
* user a one-time interactive window whose state is then persisted to
|
|
17
|
+
* the provider profile.
|
|
18
|
+
*/
|
|
19
|
+
export declare function login(providerName: string, options?: LoginOptions): Promise<void>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import { defaultLogger } from "../core/logger.js";
|
|
3
|
+
import { ChatSession } from "../session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Open a headed browser so the user can sign in manually.
|
|
6
|
+
*
|
|
7
|
+
* We never automate the actual sign-in (no email/password handling, no
|
|
8
|
+
* captcha bypass). The whole point of `chat-web login` is to give the
|
|
9
|
+
* user a one-time interactive window whose state is then persisted to
|
|
10
|
+
* the provider profile.
|
|
11
|
+
*/
|
|
12
|
+
export async function login(providerName, options = {}) {
|
|
13
|
+
const logger = options.logger ?? defaultLogger;
|
|
14
|
+
const session = await ChatSession.open(providerName, {
|
|
15
|
+
headless: false,
|
|
16
|
+
logger,
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
logger.info(`Opened ${session.provider} in a Chromium window backed by ${session.userDataDir}.`);
|
|
20
|
+
logger.info("Complete the sign-in flow in the browser window.");
|
|
21
|
+
if (options.autoExit) {
|
|
22
|
+
await waitForLoggedIn(session, options, logger);
|
|
23
|
+
logger.info(`Detected logged-in state for ${providerName}. Closing browser.`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
logger.info("When done, press Enter in this terminal to close the browser.");
|
|
27
|
+
await waitForEnter();
|
|
28
|
+
}
|
|
29
|
+
if (await session.isLoggedIn()) {
|
|
30
|
+
logger.info(`Saved persistent profile for ${providerName} at ${session.userDataDir}.`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
logger.warn(`Could not verify login for ${providerName}. The browser was closed without a confirmed signed-in state.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await session.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function waitForEnter() {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
rl.question("", () => {
|
|
44
|
+
rl.close();
|
|
45
|
+
resolve();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function waitForLoggedIn(session, options, logger) {
|
|
50
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
|
|
51
|
+
const timeoutMs = options.timeoutMs ?? 10 * 60 * 1000;
|
|
52
|
+
const deadline = Date.now() + timeoutMs;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
const ok = await session.isLoggedIn().catch(() => false);
|
|
55
|
+
if (ok)
|
|
56
|
+
return;
|
|
57
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
58
|
+
}
|
|
59
|
+
logger.warn("Timed out waiting for login.");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC,OAAO,EAAE,aAAa,EAAe,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAY5C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,YAAoB,EAAE,UAAwB,EAAE;IAC1E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;IAE/C,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE;QACnD,QAAQ,EAAE,KAAK;QACf,MAAM;KACP,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CACT,UAAU,OAAO,CAAC,QAAQ,mCAAmC,OAAO,CAAC,WAAW,GAAG,CACpF,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QAEhE,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,gCAAgC,YAAY,oBAAoB,CAAC,CAAC;QAChF,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;YAC7E,MAAM,YAAY,EAAE,CAAC;QACvB,CAAC;QAED,IAAI,MAAM,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,gCAAgC,YAAY,OAAO,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;QACzF,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,8BAA8B,YAAY,+DAA+D,CAC1G,CAAC;QACJ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE;YACnB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,OAAoB,EACpB,OAAqB,EACrB,MAAc;IAEd,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QACzD,IAAI,EAAE;YAAE,OAAO;QACf,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type BrowserContext, type Page } from "playwright";
|
|
2
|
+
import { type Logger } from "./logger.js";
|
|
3
|
+
import { type BrowserProfileManager } from "./profile-manager.js";
|
|
4
|
+
export interface LaunchOptions {
|
|
5
|
+
/** Override headless. Defaults to headed (RFC §19.3) but `false` is overridden by env vars. */
|
|
6
|
+
headless?: boolean;
|
|
7
|
+
/** Width × height of the viewport. */
|
|
8
|
+
viewport?: {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
/** Extra Chromium args. Merged after our defaults. */
|
|
13
|
+
args?: string[];
|
|
14
|
+
/** Inject a custom profile manager (mostly for testing). */
|
|
15
|
+
profileManager?: BrowserProfileManager;
|
|
16
|
+
/** Logger used for the (rare) auto-install path. */
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
/**
|
|
19
|
+
* Disable the runtime auto-install of Chromium. Defaults to honouring
|
|
20
|
+
* the `CHAT_WEB_NO_AUTO_INSTALL` / `CHAT_WEB_SKIP_BROWSER_INSTALL`
|
|
21
|
+
* env vars; pass `true` to force-disable regardless.
|
|
22
|
+
*/
|
|
23
|
+
noAutoInstall?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Use a specific Chromium-family browser channel instead of the
|
|
26
|
+
* Playwright-bundled `chrome-headless-shell`. Accepts the standard
|
|
27
|
+
* Playwright channel ids: `"chrome"` / `"chrome-beta"` / `"msedge"` /
|
|
28
|
+
* `"chromium"`. Honours the `CHAT_WEB_BROWSER_CHANNEL` env var.
|
|
29
|
+
*
|
|
30
|
+
* When set, Playwright uses the system-installed binary (so e.g.
|
|
31
|
+
* `channel: "chrome"` means "the same Google Chrome you double-click
|
|
32
|
+
* on the dock"). Mutually exclusive with `executablePath` — if both
|
|
33
|
+
* are provided, `executablePath` wins.
|
|
34
|
+
*
|
|
35
|
+
* Note: this does NOT bypass Google's WAA anti-abuse on AI Studio
|
|
36
|
+
* (verified: system Chrome + headed + the same userDataDir still
|
|
37
|
+
* gets blocked because WAA detects the CDP connection itself, not
|
|
38
|
+
* the binary identity). It's still useful for the other providers
|
|
39
|
+
* (ChatGPT, DeepSeek) when the user's network treats Playwright's
|
|
40
|
+
* `chrome-headless-shell` differently from real Chrome.
|
|
41
|
+
*/
|
|
42
|
+
channel?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Explicit path to a Chrome / Chromium binary. Overrides `channel`.
|
|
45
|
+
* Honours the `CHAT_WEB_BROWSER_EXECUTABLE` env var.
|
|
46
|
+
*
|
|
47
|
+
* Example (macOS):
|
|
48
|
+
* `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
|
|
49
|
+
*/
|
|
50
|
+
executablePath?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface LaunchedBrowser {
|
|
53
|
+
context: BrowserContext;
|
|
54
|
+
page: Page;
|
|
55
|
+
/** The userDataDir that was used. Useful for doctor / debug output. */
|
|
56
|
+
userDataDir: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Launch (or reattach to) a persistent Chromium context for a provider.
|
|
60
|
+
*
|
|
61
|
+
* Important: we use `launchPersistentContext`, NOT `browser.newContext`,
|
|
62
|
+
* because ChatGPT / DeepSeek depend on more than just cookies (see RFC §19.1).
|
|
63
|
+
*
|
|
64
|
+
* First-run UX: if Playwright reports that the Chromium binary is not
|
|
65
|
+
* installed (a very common state under pnpm 10+, which silently blocks
|
|
66
|
+
* the `playwright` postinstall script), we run
|
|
67
|
+
* `npx playwright install chromium` once and retry the launch. Disable
|
|
68
|
+
* via `CHAT_WEB_NO_AUTO_INSTALL=1` (or `noAutoInstall: true` on the call).
|
|
69
|
+
*/
|
|
70
|
+
export declare function launchProviderBrowser(provider: string, options?: LaunchOptions): Promise<LaunchedBrowser>;
|