@youdie006/prodex 0.40.5 → 0.40.8
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 +25 -10
- package/dist/browser-send-lock.js +10 -131
- package/dist/chatgpt-browser.js +311 -686
- package/dist/cli-help.js +20 -17
- package/dist/cli-ledger.js +2 -0
- package/dist/cli-pro.js +236 -100
- package/dist/cli-server.js +2 -2
- package/dist/cli.js +1 -4
- package/dist/config.js +3 -3
- package/dist/http-mcp.js +1 -1
- package/dist/issue-report.js +9 -4
- package/dist/mcp-tools.js +42 -10
- package/dist/mcp.js +2 -2
- package/dist/registry.js +54 -6
- package/dist/repo-write.js +22 -2
- package/dist/safe-file.js +249 -1
- package/dist/store.js +7 -1
- package/dist/tui-flow.js +0 -1
- package/dist/tui-run.js +62 -25
- package/dist/tui.js +68 -54
- package/docs/claude.md +3 -1
- package/docs/cli-reference.md +17 -6
- package/docs/clients.md +4 -6
- package/docs/http-mcp.md +5 -1
- package/docs/releasing.md +3 -1
- package/package.json +1 -1
package/dist/cli-pro.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { buildDryRunBundle } from "./bundle.js";
|
|
5
|
-
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, formatModelMenuOption, listChatGptModelOptions, deleteChatGptConversation, endWedgedBrowser, browserRecoveryPlan, findWedgedBrowser, wedgedBrowserBlocker, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch,
|
|
5
|
+
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, formatModelMenuOption, listChatGptModelOptions, deleteChatGptConversation, endWedgedBrowser, browserRecoveryPlan, findWedgedBrowser, wedgedBrowserBlocker, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch, resolveBrowserWindowMode, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt, statusMeansBrowserDead, namesPro, sendWarningsFromError, destinationVerification, chatGptProjectIdFromUrl } from "./chatgpt-browser.js";
|
|
6
6
|
import { ASK_PRO_BOOLEAN_FLAGS, ASK_PRO_PREVIEW_VALUE_FLAGS, ASK_PRO_VALUE_FLAGS, assertHelpRequestArgs, assertNoExtraArgs, assertOnlyOptions, findHelpFlagIndexBeforePromptDelimiter, formatCliCommand, hasAskProDryRunMode, hasAskProMode, hasAskProSendMode, isHelpSubcommand, parseAskProArgs, printHelpIfRequested, readFlag, readPortFlag, readPositionalsWithOptions, readNonNegativeIntegerFlag, readPositiveIntegerFlag, readRepeatedFlag, resolveCwdFlag, resolveOptionalFileFlag, unknownSubcommandError } from "./cli-args.js";
|
|
7
7
|
import { printProBrowserHelp, printProHelp } from "./cli-help.js";
|
|
8
8
|
import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledger.js";
|
|
@@ -16,6 +16,15 @@ import { readBridgeRoots } from "./registry.js";
|
|
|
16
16
|
import { BridgeStore, MAX_FETCHABLE_RESULT_ARTIFACT_BYTES } from "./store.js";
|
|
17
17
|
import { CLI_VERSION } from "./cli-help.js";
|
|
18
18
|
import { PRODEX_ISSUE_REPO, buildIssueReport, fileGitHubIssue } from "./issue-report.js";
|
|
19
|
+
function resolveBrowserProfileDirForLaunch(profileDir, launchCwd = process.cwd()) {
|
|
20
|
+
const resolved = path.resolve(launchCwd, profileDir);
|
|
21
|
+
try {
|
|
22
|
+
return statSync(resolved).isDirectory() ? realpathSync(resolved) : resolved;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
export async function runChatgptCommand(rest, io) {
|
|
20
29
|
const [subcommand, ...chatgptArgs] = rest;
|
|
21
30
|
if (!subcommand || isHelpSubcommand(subcommand)) {
|
|
@@ -195,23 +204,45 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
195
204
|
if (browserSubcommand === "login") {
|
|
196
205
|
if (printProBrowserHelpIfRequested(browserArgs, "pro browser login", io, {
|
|
197
206
|
valueFlags: ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"],
|
|
198
|
-
booleanFlags: ["--dry-run", "--wait", "--no-wait", "--headless", "--minimized", "--virtual-display"]
|
|
207
|
+
booleanFlags: ["--dry-run", "--wait", "--no-wait", "--headed", "--headless", "--minimized", "--virtual-display"]
|
|
199
208
|
})) {
|
|
200
209
|
return 0;
|
|
201
210
|
}
|
|
202
|
-
assertOnlyOptions(browserArgs, "pro browser login", ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"], ["--dry-run", "--wait", "--no-wait", "--headless", "--minimized", "--virtual-display"]);
|
|
211
|
+
assertOnlyOptions(browserArgs, "pro browser login", ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"], ["--dry-run", "--wait", "--no-wait", "--headed", "--headless", "--minimized", "--virtual-display"]);
|
|
203
212
|
if (browserArgs.includes("--wait") && browserArgs.includes("--no-wait")) {
|
|
204
213
|
throw new Error("pro browser login cannot combine --wait and --no-wait");
|
|
205
214
|
}
|
|
206
215
|
const loginUrl = readChatGptBrowserUrlFlag(browserArgs);
|
|
207
216
|
const sourceCli = resolveOptionalFileFlag(io.cwd, browserArgs, "--source-cli");
|
|
208
217
|
const targetCwd = readFlag(browserArgs, "--cwd") ? resolveCwdFlag(io.cwd, browserArgs) : undefined;
|
|
209
|
-
const
|
|
218
|
+
const requestedProfileFlag = readFlag(browserArgs, "--profile-dir");
|
|
219
|
+
const requestedProfileDir = requestedProfileFlag
|
|
220
|
+
? resolveBrowserProfileDirForLaunch(requestedProfileFlag)
|
|
221
|
+
: undefined;
|
|
210
222
|
const port = resolveCdpPort(readPortFlag(browserArgs, "--port"));
|
|
211
223
|
const launchTimeoutMs = readPositiveIntegerFlag(browserArgs, "--launch-timeout-ms");
|
|
224
|
+
const savedLaunch = await readLastBrowserLoginLaunch();
|
|
225
|
+
// A saved launch belongs to a control port. Reusing its profile for a
|
|
226
|
+
// different port would silently widen the existing custom-port rules.
|
|
227
|
+
const savedLaunchForPort = savedLaunch?.port === port ? savedLaunch : undefined;
|
|
228
|
+
const savedProfileDir = savedLaunchForPort?.profile_dir
|
|
229
|
+
? resolveBrowserProfileDirForLaunch(savedLaunchForPort.profile_dir)
|
|
230
|
+
: undefined;
|
|
231
|
+
const profileDir = requestedProfileDir ?? savedProfileDir;
|
|
232
|
+
const windowMode = resolveBrowserWindowMode({
|
|
233
|
+
flags: {
|
|
234
|
+
...(browserArgs.includes("--headed") ? { headed: true } : {}),
|
|
235
|
+
...(browserArgs.includes("--headless") ? { headless: true } : {}),
|
|
236
|
+
...(browserArgs.includes("--virtual-display") ? { virtualDisplay: true } : {}),
|
|
237
|
+
...(browserArgs.includes("--minimized") ? { minimized: true } : {})
|
|
238
|
+
},
|
|
239
|
+
// Window mode is a user preference across launches; only the saved
|
|
240
|
+
// profile and display identity are scoped to this resolved port.
|
|
241
|
+
...(savedLaunch ? { lastLogin: savedLaunch } : {})
|
|
242
|
+
});
|
|
212
243
|
const commandOptions = {
|
|
213
244
|
...(targetCwd ? { cwd: targetCwd } : {}),
|
|
214
|
-
...(
|
|
245
|
+
...(requestedProfileDir ? { profileDir: requestedProfileDir } : {}),
|
|
215
246
|
...(port !== DEFAULT_CDP_PORT ? { port } : {}),
|
|
216
247
|
...(readFlag(browserArgs, "--url") ? { url: loginUrl } : {}),
|
|
217
248
|
...(launchTimeoutMs !== undefined ? { launchTimeoutMs } : {})
|
|
@@ -219,6 +250,8 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
219
250
|
if (browserArgs.includes("--dry-run")) {
|
|
220
251
|
printBrowserLoginGuide(io.stdout, {
|
|
221
252
|
opened: false,
|
|
253
|
+
headless: windowMode.headless,
|
|
254
|
+
virtualDisplay: windowMode.virtualDisplay,
|
|
222
255
|
loginUrl,
|
|
223
256
|
profileDir: profileDir ?? defaultChatGptProfileDir(),
|
|
224
257
|
port,
|
|
@@ -231,48 +264,41 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
231
264
|
// again: Chrome's singleton would just open ANOTHER window (the recurring
|
|
232
265
|
// "extra windows" problem, which then blocks sends as
|
|
233
266
|
// ambiguous_chatgpt_tabs). Reuse the running instance instead.
|
|
234
|
-
if (browserArgs.includes("--virtual-display") && browserArgs.includes("--headless")) {
|
|
235
|
-
throw new Error("pro browser login cannot combine --headless and --virtual-display (a virtual display already hides the window).");
|
|
236
|
-
}
|
|
237
|
-
// Reopen the browser the way it was last opened unless a flag or the
|
|
238
|
-
// environment says otherwise. A virtual-display user who follows the
|
|
239
|
-
// `browser_unreachable` advice used to get a visible window back.
|
|
240
|
-
const savedLaunch = await readLastBrowserLoginLaunch();
|
|
241
|
-
const windowMode = resolveBrowserWindowMode({
|
|
242
|
-
flags: {
|
|
243
|
-
...(browserArgs.includes("--headless") ? { headless: true } : {}),
|
|
244
|
-
...(browserArgs.includes("--virtual-display") ? { virtualDisplay: true } : {}),
|
|
245
|
-
...(browserArgs.includes("--minimized") ? { minimized: true } : {})
|
|
246
|
-
},
|
|
247
|
-
...(savedLaunch ? { lastLogin: savedLaunch } : {})
|
|
248
|
-
});
|
|
249
267
|
const headless = windowMode.headless;
|
|
250
|
-
// A real browser on a virtual X display: no window anywhere, and
|
|
251
|
-
// Cloudflare sees an ordinary headed Chrome (headless it rejects).
|
|
252
268
|
const wantsVirtualDisplay = windowMode.virtualDisplay;
|
|
253
|
-
const virtualDisplay = wantsVirtualDisplay
|
|
254
|
-
? await ensureVirtualDisplay(savedLaunch?.virtual_display !== undefined && !browserArgs.includes("--virtual-display")
|
|
255
|
-
? { displayNumber: savedLaunch.virtual_display }
|
|
256
|
-
: {})
|
|
257
|
-
: undefined;
|
|
258
269
|
const alreadyRunning = (await getChatGptBrowserStatus({ port })).reachable;
|
|
259
270
|
if (alreadyRunning) {
|
|
271
|
+
if (requestedProfileDir &&
|
|
272
|
+
savedProfileDir &&
|
|
273
|
+
requestedProfileDir !== savedProfileDir) {
|
|
274
|
+
throw new Error(`A ChatGPT browser is already running on port ${port} with profile ${savedProfileDir}; a different profile was requested. Close the existing browser yourself, then rerun with the intended profile.`);
|
|
275
|
+
}
|
|
260
276
|
// One Chrome profile cannot serve a headed and a headless instance at
|
|
261
277
|
// once, and reusing the running one would silently ignore the
|
|
262
278
|
// requested mode. Say so instead of pretending the switch took.
|
|
263
|
-
const
|
|
264
|
-
const runningHeadless = previous?.port === port ? previous.headless === true : undefined;
|
|
279
|
+
const runningHeadless = savedLaunchForPort ? savedLaunchForPort.headless === true : undefined;
|
|
265
280
|
if (runningHeadless !== undefined && runningHeadless !== headless) {
|
|
266
|
-
throw new Error(`A ${runningHeadless ? "headless" : "headed"} ChatGPT browser is already running on port ${port}, but ${headless ? "headless" : "headed"} was requested. Close
|
|
281
|
+
throw new Error(`A ${runningHeadless ? "headless" : "headed"} ChatGPT browser is already running on port ${port}, but ${headless ? "headless" : "headed"} was requested. Close the existing browser yourself, then rerun; prodex will not end it.`);
|
|
267
282
|
}
|
|
268
283
|
// Same for the display: a browser already on your desktop cannot be
|
|
269
284
|
// moved onto a virtual display by reusing it, and silently reusing
|
|
270
285
|
// it would leave the window exactly where the user asked it not to be.
|
|
271
|
-
const runningVirtual =
|
|
286
|
+
const runningVirtual = savedLaunchForPort ? savedLaunchForPort.virtual_display !== undefined : undefined;
|
|
272
287
|
if (runningVirtual !== undefined && runningVirtual !== wantsVirtualDisplay) {
|
|
273
|
-
throw new Error(`A ChatGPT browser is already running on port ${port} on ${runningVirtual ? "a virtual display" : "your desktop"}, but ${wantsVirtualDisplay ? "a virtual display" : "your desktop"} was requested. Close
|
|
288
|
+
throw new Error(`A ChatGPT browser is already running on port ${port} on ${runningVirtual ? "a virtual display" : "your desktop"}, but ${wantsVirtualDisplay ? "a virtual display" : "your desktop"} was requested. Close the existing browser yourself, then rerun; prodex will not end it.`);
|
|
289
|
+
}
|
|
290
|
+
if (savedLaunchForPort?.minimized === true && !windowMode.minimized) {
|
|
291
|
+
throw new Error(`A minimized ChatGPT browser is already running on port ${port}, but a visible headed browser was requested. Close the existing browser yourself, then rerun with --headed; prodex will not end it or pretend the minimized window was restored.`);
|
|
274
292
|
}
|
|
275
293
|
}
|
|
294
|
+
// Allocate/rejoin a display only for a new browser. An already-running
|
|
295
|
+
// virtual browser owns its saved display identity and needs no new X
|
|
296
|
+
// server just because login was invoked again.
|
|
297
|
+
const virtualDisplay = wantsVirtualDisplay && !alreadyRunning
|
|
298
|
+
? await ensureVirtualDisplay(savedLaunchForPort?.virtual_display !== undefined
|
|
299
|
+
? { displayNumber: savedLaunchForPort.virtual_display }
|
|
300
|
+
: {})
|
|
301
|
+
: undefined;
|
|
276
302
|
// A wedged browser is unreachable, so without this login would launch a
|
|
277
303
|
// second Chrome onto the same profile and leave the first one burning
|
|
278
304
|
// CPU - the same mistake the unattended recovery path made. Checked
|
|
@@ -291,7 +317,9 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
291
317
|
port,
|
|
292
318
|
profileDir,
|
|
293
319
|
url: loginUrl,
|
|
294
|
-
|
|
320
|
+
// Pass false too: openChatGptBrowser otherwise consults the env
|
|
321
|
+
// again and can resurrect a mode the shared resolver disabled.
|
|
322
|
+
headless,
|
|
295
323
|
...(virtualDisplay ? { virtualDisplay: { displayNumber: virtualDisplay.displayNumber, xauthority: virtualDisplay.xauthority } } : {})
|
|
296
324
|
});
|
|
297
325
|
if (!alreadyRunning) {
|
|
@@ -302,10 +330,9 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
302
330
|
// Minimize BEFORE recording, so the record reflects what actually
|
|
303
331
|
// happened (a desktop that hides minimized windows gets restored and
|
|
304
332
|
// recorded as a normal window).
|
|
305
|
-
const wantsMinimized = browserArgs.includes("--minimized") || resolveMinimizeWindowPreference();
|
|
306
333
|
let minimized = false;
|
|
307
334
|
let minimizeNote;
|
|
308
|
-
if (
|
|
335
|
+
if (windowMode.minimized && !headless) {
|
|
309
336
|
try {
|
|
310
337
|
const outcome = await minimizeChatGptWindow({ port: opened.port });
|
|
311
338
|
minimized = outcome.minimized;
|
|
@@ -317,20 +344,31 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
317
344
|
minimizeNote = `window: could not minimize (${errorMessage(error)}); leaving it as it is.`;
|
|
318
345
|
}
|
|
319
346
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
// A reused browser with neither an explicit nor saved profile is
|
|
348
|
+
// reachable, but its actual profile cannot be inferred from the port.
|
|
349
|
+
// Do not overwrite a useful launch record with an invented default.
|
|
350
|
+
if (!alreadyRunning || profileDir) {
|
|
351
|
+
await recordBrowserLoginLaunch({
|
|
352
|
+
profile_dir: opened.profileDir,
|
|
353
|
+
port: opened.port,
|
|
354
|
+
headless,
|
|
355
|
+
minimized,
|
|
356
|
+
...(virtualDisplay
|
|
357
|
+
? { virtual_display: virtualDisplay.displayNumber }
|
|
358
|
+
: wantsVirtualDisplay && savedLaunchForPort?.virtual_display !== undefined
|
|
359
|
+
? { virtual_display: savedLaunchForPort.virtual_display }
|
|
360
|
+
: {})
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const virtualDisplayNumber = virtualDisplay?.displayNumber ?? savedLaunchForPort?.virtual_display;
|
|
364
|
+
if (wantsVirtualDisplay && virtualDisplayNumber !== undefined) {
|
|
365
|
+
io.stdout(`window: none - running on virtual display :${virtualDisplayNumber}${virtualDisplay?.startedNow ? " (started now)" : " (reused)"}.`);
|
|
329
366
|
}
|
|
330
367
|
printBrowserLoginGuide(io.stdout, {
|
|
331
368
|
opened: !alreadyRunning,
|
|
332
369
|
reused: alreadyRunning,
|
|
333
370
|
headless,
|
|
371
|
+
virtualDisplay: wantsVirtualDisplay,
|
|
334
372
|
loginUrl,
|
|
335
373
|
profileDir: opened.profileDir,
|
|
336
374
|
port: opened.port,
|
|
@@ -345,7 +383,12 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
345
383
|
// Verify it here (bounded, no human to wait for) instead of letting
|
|
346
384
|
// the first consult fail with a confusing not-logged-in blocker.
|
|
347
385
|
const headlessWaitMs = readPositiveIntegerFlag(browserArgs, "--wait-timeout-ms") ?? 30_000;
|
|
348
|
-
const headlessReady = await waitForChatGptLoginReady(io.stderr, {
|
|
386
|
+
const headlessReady = await waitForChatGptLoginReady(io.stderr, {
|
|
387
|
+
port: opened.port,
|
|
388
|
+
timeoutMs: headlessWaitMs,
|
|
389
|
+
windowMode,
|
|
390
|
+
headedLoginCommand: formatHeadedBrowserLoginCommand(sourceCli, commandOptions)
|
|
391
|
+
});
|
|
349
392
|
if (!headlessReady) {
|
|
350
393
|
// Name the real cause. Cloudflare rejects headless Chrome by
|
|
351
394
|
// design (its docs list headless browsers as unsupported), and
|
|
@@ -355,8 +398,8 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
355
398
|
const challenged = finalStatus?.blocker?.code === "cloudflare_check" || /just a moment/i.test(finalStatus?.title ?? "");
|
|
356
399
|
io.stdout("");
|
|
357
400
|
io.stdout(challenged
|
|
358
|
-
?
|
|
359
|
-
: `headless: the profile is not signed in. Run \`${
|
|
401
|
+
? `headless: Cloudflare challenged the headless browser and never let ChatGPT load. Run \`${formatHeadedBrowserLoginCommand(sourceCli, commandOptions)}\` for a visible interactive check; headless browsers are not supported by Cloudflare.`
|
|
402
|
+
: `headless: the profile is not signed in. Run \`${formatHeadedBrowserLoginCommand(sourceCli, commandOptions)}\` for a visible interactive login, sign in, close that browser yourself, then rerun with --headless.`);
|
|
360
403
|
return 1;
|
|
361
404
|
}
|
|
362
405
|
io.stdout("headless: signed-in session confirmed - consults will run with no visible window.");
|
|
@@ -370,7 +413,12 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
370
413
|
if (!shouldWaitForReady)
|
|
371
414
|
return 0;
|
|
372
415
|
const waitTimeoutMs = readPositiveIntegerFlag(browserArgs, "--wait-timeout-ms") ?? 300_000;
|
|
373
|
-
const ready = await waitForChatGptLoginReady(io.stderr, {
|
|
416
|
+
const ready = await waitForChatGptLoginReady(io.stderr, {
|
|
417
|
+
port: opened.port,
|
|
418
|
+
timeoutMs: waitTimeoutMs,
|
|
419
|
+
windowMode,
|
|
420
|
+
headedLoginCommand: formatHeadedBrowserLoginCommand(sourceCli, commandOptions)
|
|
421
|
+
});
|
|
374
422
|
return ready ? 0 : 1;
|
|
375
423
|
}
|
|
376
424
|
if (browserSubcommand === "ask") {
|
|
@@ -933,12 +981,6 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
933
981
|
export async function runConsultsCommand(rest, io) {
|
|
934
982
|
throw new Error("The legacy `consults` alias is retired. Use `prodex pro list`, `prodex pro latest`, or `prodex pro show <task-id|latest>`.");
|
|
935
983
|
}
|
|
936
|
-
// PRODEX_MINIMIZE_WINDOW=1 gives the no-window setup to every entry point,
|
|
937
|
-
// including the MCP server's auto-recovery, without a CLI flag.
|
|
938
|
-
export function resolveMinimizeWindowPreference(env = process.env) {
|
|
939
|
-
const raw = (env.PRODEX_MINIMIZE_WINDOW ?? "").trim().toLowerCase();
|
|
940
|
-
return raw === "1" || raw === "true" || raw === "yes";
|
|
941
|
-
}
|
|
942
984
|
function autoLoginDisabledByEnv(env = process.env) {
|
|
943
985
|
const raw = (env.PRODEX_NO_AUTO_LOGIN ?? "").trim().toLowerCase();
|
|
944
986
|
return raw === "1" || raw === "true" || raw === "yes";
|
|
@@ -1328,9 +1370,9 @@ export async function runAskProCommand(rest, io) {
|
|
|
1328
1370
|
port: browserPort,
|
|
1329
1371
|
prompt: bundle.sendText,
|
|
1330
1372
|
targetUrl: normalizedTargetUrl,
|
|
1331
|
-
//
|
|
1332
|
-
//
|
|
1333
|
-
...(continuedFromTaskId ? { navigateToTargetUrl: true } : {}),
|
|
1373
|
+
// Continuations and explicit TUI picks navigate while this lock is
|
|
1374
|
+
// held. Direct CLI targets keep their existing verification-only semantics.
|
|
1375
|
+
...(continuedFromTaskId || (io.navigateInteractiveTarget === true && normalizedTargetUrl) ? { navigateToTargetUrl: true } : {}),
|
|
1334
1376
|
timeoutMs: browserTimeoutMs,
|
|
1335
1377
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
1336
1378
|
...(tools.length > 0 ? { tools } : {}),
|
|
@@ -1352,15 +1394,21 @@ export async function runAskProCommand(rest, io) {
|
|
|
1352
1394
|
const autoLoginAllowed = !parsedAskPro.optionArgs.includes("--no-auto-login") &&
|
|
1353
1395
|
(parsedAskPro.optionArgs.includes("--auto-login") || io.isInteractive === true);
|
|
1354
1396
|
let consult;
|
|
1397
|
+
// Declared out here so a retry that fails still reports what recovery
|
|
1398
|
+
// did: the notes used to live inside the inner catch and were attached
|
|
1399
|
+
// to the ANSWER, so a recovered browser whose retry then died recorded
|
|
1400
|
+
// nothing about the recovery at all.
|
|
1401
|
+
const recoveryNotes = [];
|
|
1355
1402
|
try {
|
|
1356
1403
|
try {
|
|
1357
1404
|
consult = await sendOnce();
|
|
1358
1405
|
}
|
|
1359
1406
|
catch (error) {
|
|
1360
1407
|
const firstBlocker = browserSendBlockerFromError(error);
|
|
1361
|
-
|
|
1408
|
+
// Loss while reading an already-submitted prompt must not send it again.
|
|
1409
|
+
if (firstBlocker.code !== "browser_unreachable" ||
|
|
1410
|
+
!firstBlocker.retryable || firstBlocker.thread || !autoLoginAllowed)
|
|
1362
1411
|
throw error;
|
|
1363
|
-
const recoveryNotes = [];
|
|
1364
1412
|
const recovered = await attemptBrowserAutoRecovery(io.stderr, {
|
|
1365
1413
|
...(browserPort !== undefined ? { port: browserPort } : {}),
|
|
1366
1414
|
notes: recoveryNotes
|
|
@@ -1387,7 +1435,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
1387
1435
|
// What the send had already noticed before it died. These used to go
|
|
1388
1436
|
// out with the result, so a failure dropped them - including the note
|
|
1389
1437
|
// that would explain it, like having just moved off the Work surface.
|
|
1390
|
-
const blockedWarnings = sendWarningsFromError(error).map(redactProject);
|
|
1438
|
+
const blockedWarnings = [...recoveryNotes, ...sendWarningsFromError(error)].map(redactProject);
|
|
1391
1439
|
for (const warning of blockedWarnings)
|
|
1392
1440
|
io.stderr(warning);
|
|
1393
1441
|
const persistedBlocker = {
|
|
@@ -1797,6 +1845,26 @@ function autoClearDisabledByEnv(env = process.env) {
|
|
|
1797
1845
|
const raw = (env.PRODEX_NO_AUTO_CLEAR ?? "").trim().toLowerCase();
|
|
1798
1846
|
return raw === "1" || raw === "true" || raw === "yes";
|
|
1799
1847
|
}
|
|
1848
|
+
function reportResidualBrowserRecoveryBlocker(stderr, pids, port) {
|
|
1849
|
+
const blocker = wedgedBrowserBlocker([...new Set(pids)].sort((left, right) => left - right), port);
|
|
1850
|
+
stderr(`recover: failed - ${blocker.message}`);
|
|
1851
|
+
stderr(`recover: ${blocker.next_step}`);
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* What recovery did to the browser, in the words the receipt keeps.
|
|
1856
|
+
*
|
|
1857
|
+
* Both halves are recorded because both change what the answer came from. The
|
|
1858
|
+
* ending case takes someone's running browser with it; the launch case is the
|
|
1859
|
+
* quieter one and used to record nothing at all - measured, killing the
|
|
1860
|
+
* dedicated browser and sending with --auto-login recovered in three seconds
|
|
1861
|
+
* and left warnings: [] on the receipt, the result and the task.
|
|
1862
|
+
*/
|
|
1863
|
+
export function browserRecoveredNote(ended) {
|
|
1864
|
+
return ended.length > 0
|
|
1865
|
+
? `browser_recovered: the dedicated browser stopped answering its control port and prodex ended it (pid ${ended.join(", ")}) and started a fresh one before sending. Anything it was doing at the time is gone; the profile and login were kept.`
|
|
1866
|
+
: "browser_recovered: the dedicated browser was not running, so prodex started it with the saved profile before sending. The login was kept; anything the old browser had open is gone.";
|
|
1867
|
+
}
|
|
1800
1868
|
export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
1801
1869
|
// Launching is right when the browser is gone and wrong when it is only deaf:
|
|
1802
1870
|
// a second Chrome on the same profile joins the wedged one rather than
|
|
@@ -1807,9 +1875,26 @@ export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
|
1807
1875
|
// scanning the DEFAULT profile for a custom-profile user put a second,
|
|
1808
1876
|
// healthy browser's renderers on the list this function kills.
|
|
1809
1877
|
const lastLogin = await readLastBrowserLoginLaunch().catch(() => undefined);
|
|
1878
|
+
const recoveryPort = resolveCdpPort(options.port);
|
|
1879
|
+
const savedIdentityBelongsToAnotherPort = lastLogin?.port !== undefined &&
|
|
1880
|
+
lastLogin.port !== recoveryPort &&
|
|
1881
|
+
(lastLogin.profile_dir !== undefined || lastLogin.virtual_display !== undefined);
|
|
1882
|
+
if (savedIdentityBelongsToAnotherPort) {
|
|
1883
|
+
stderr(`recover: failed - saved browser identity belongs to port ${lastLogin.port}, not requested port ${recoveryPort}. Run \`prodex pro browser login --port ${recoveryPort} --headed\` with the intended profile before retrying; prodex will not launch an unknown account.`);
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
const lastLoginForPort = lastLogin?.port === undefined || lastLogin.port === recoveryPort ? lastLogin : undefined;
|
|
1887
|
+
let windowMode;
|
|
1888
|
+
try {
|
|
1889
|
+
windowMode = resolveBrowserWindowMode({ ...(lastLogin ? { lastLogin } : {}) });
|
|
1890
|
+
}
|
|
1891
|
+
catch (error) {
|
|
1892
|
+
stderr(`recover: failed - ${errorMessage(error)}`);
|
|
1893
|
+
return false;
|
|
1894
|
+
}
|
|
1810
1895
|
const scanFor = {
|
|
1811
1896
|
...(options.port !== undefined ? { port: options.port } : {}),
|
|
1812
|
-
...(
|
|
1897
|
+
...(lastLoginForPort?.profile_dir ? { profileDir: lastLoginForPort.profile_dir } : {})
|
|
1813
1898
|
};
|
|
1814
1899
|
const wedged = findWedgedBrowser(scanFor);
|
|
1815
1900
|
if (wedged.length > 0) {
|
|
@@ -1829,17 +1914,25 @@ export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
|
1829
1914
|
return false;
|
|
1830
1915
|
}
|
|
1831
1916
|
stderr(`recover: the browser stopped answering; ending it (pid ${wedged.join(", ")}) and starting a fresh one...`);
|
|
1832
|
-
//
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1917
|
+
// Keep the attempt even if launch fails; completion is recorded after READY.
|
|
1918
|
+
options.notes?.push(`browser_recovery_started: prodex began restarting the unresponsive dedicated browser (pid ${wedged.join(", ")}).`);
|
|
1919
|
+
const termination = await endWedgedBrowser(wedged);
|
|
1920
|
+
if (termination.failed.length > 0) {
|
|
1921
|
+
return reportResidualBrowserRecoveryBlocker(stderr, termination.failed, recoveryPort);
|
|
1922
|
+
}
|
|
1836
1923
|
// Wait for the profile lock to actually clear rather than guessing at a
|
|
1837
1924
|
// delay: the replacement launch fails outright if the old process still
|
|
1838
1925
|
// holds it, which is how the first self-heal attempt ended.
|
|
1926
|
+
let residualPids = [];
|
|
1839
1927
|
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
1840
|
-
|
|
1928
|
+
residualPids = findWedgedBrowser(scanFor);
|
|
1929
|
+
if (residualPids.length === 0)
|
|
1841
1930
|
break;
|
|
1842
|
-
|
|
1931
|
+
if (attempt < 9)
|
|
1932
|
+
await sleep(1_000);
|
|
1933
|
+
}
|
|
1934
|
+
if (residualPids.length > 0) {
|
|
1935
|
+
return reportResidualBrowserRecoveryBlocker(stderr, residualPids, recoveryPort);
|
|
1843
1936
|
}
|
|
1844
1937
|
}
|
|
1845
1938
|
stderr("recover: browser is not running - launching the dedicated ChatGPT browser (Ctrl+C aborts)...");
|
|
@@ -1847,33 +1940,50 @@ export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
|
1847
1940
|
// Reuse the profile the user last logged in with; launching the default
|
|
1848
1941
|
// profile for a custom-profile user would wait on the wrong (logged-out)
|
|
1849
1942
|
// profile or, worse, silently send to a different account.
|
|
1850
|
-
//
|
|
1851
|
-
//
|
|
1852
|
-
//
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
// put a window back on a desktop they deliberately keep empty.
|
|
1856
|
-
const virtualDisplay = lastLogin?.virtual_display !== undefined || resolveVirtualDisplayPreference()
|
|
1857
|
-
? await ensureVirtualDisplay(lastLogin?.virtual_display !== undefined ? { displayNumber: lastLogin.virtual_display } : {}).catch(() => undefined)
|
|
1943
|
+
// Recovery follows the same precedence as login. In particular, one
|
|
1944
|
+
// supplied false env setting selects the env group and disables saved
|
|
1945
|
+
// modes instead of being mistaken for "not configured".
|
|
1946
|
+
const virtualDisplay = windowMode.virtualDisplay
|
|
1947
|
+
? await ensureVirtualDisplay(lastLoginForPort?.virtual_display !== undefined ? { displayNumber: lastLoginForPort.virtual_display } : {})
|
|
1858
1948
|
: undefined;
|
|
1859
1949
|
const opened = openChatGptBrowser({
|
|
1860
1950
|
...(options.port !== undefined ? { port: options.port } : {}),
|
|
1861
|
-
...(
|
|
1862
|
-
|
|
1951
|
+
...(lastLoginForPort?.profile_dir ? { profileDir: lastLoginForPort.profile_dir } : {}),
|
|
1952
|
+
// Always pass the resolved boolean. Omitting false lets the browser
|
|
1953
|
+
// boundary re-read PRODEX_HEADLESS and revive stale environment state.
|
|
1954
|
+
headless: windowMode.headless,
|
|
1863
1955
|
...(virtualDisplay ? { virtualDisplay: { displayNumber: virtualDisplay.displayNumber, xauthority: virtualDisplay.xauthority } } : {})
|
|
1864
1956
|
});
|
|
1865
1957
|
await assertBrowserLaunchStayedAlive(opened);
|
|
1866
|
-
|
|
1867
|
-
if (
|
|
1868
|
-
return false;
|
|
1869
|
-
// Restore the no-window setup too: someone who runs minimized does not
|
|
1870
|
-
// want recovery to leave a window sitting on their desktop.
|
|
1871
|
-
if (!headless && (lastLogin?.minimized === true || resolveMinimizeWindowPreference())) {
|
|
1958
|
+
let minimized = false;
|
|
1959
|
+
if (windowMode.minimized) {
|
|
1872
1960
|
const outcome = await minimizeChatGptWindow({ port: opened.port }).catch(() => undefined);
|
|
1873
|
-
|
|
1961
|
+
minimized = outcome?.minimized === true;
|
|
1962
|
+
if (minimized)
|
|
1874
1963
|
stderr("recover: window minimized again");
|
|
1875
1964
|
}
|
|
1965
|
+
// Record the actual launch before waiting for authentication. A manual
|
|
1966
|
+
// login must target this profile and display even if readiness is blocked.
|
|
1967
|
+
await recordBrowserLoginLaunch({
|
|
1968
|
+
profile_dir: opened.profileDir,
|
|
1969
|
+
port: opened.port,
|
|
1970
|
+
headless: windowMode.headless,
|
|
1971
|
+
minimized,
|
|
1972
|
+
...(virtualDisplay ? { virtual_display: virtualDisplay.displayNumber } : {})
|
|
1973
|
+
});
|
|
1974
|
+
const ready = await waitForChatGptLoginReady(stderr, {
|
|
1975
|
+
port: opened.port,
|
|
1976
|
+
timeoutMs: 120_000,
|
|
1977
|
+
windowMode,
|
|
1978
|
+
headedLoginCommand: formatHeadedBrowserLoginCommand(undefined, {
|
|
1979
|
+
profileDir: opened.profileDir,
|
|
1980
|
+
...(opened.port !== DEFAULT_CDP_PORT ? { port: opened.port } : {})
|
|
1981
|
+
})
|
|
1982
|
+
});
|
|
1983
|
+
if (!ready)
|
|
1984
|
+
return false;
|
|
1876
1985
|
stderr("recover: browser READY - retrying the send...");
|
|
1986
|
+
options.notes?.push(browserRecoveredNote(wedged));
|
|
1877
1987
|
return true;
|
|
1878
1988
|
}
|
|
1879
1989
|
catch (error) {
|
|
@@ -1893,8 +2003,13 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
|
|
|
1893
2003
|
const openTabFn = deps.openTabFn ?? openChatGptTab;
|
|
1894
2004
|
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
1895
2005
|
const pollMs = options.pollMs ?? 2_000;
|
|
2006
|
+
const hasInteractiveWindow = options.windowMode?.headless !== true && options.windowMode?.virtualDisplay !== true;
|
|
2007
|
+
const headedLoginCommand = options.headedLoginCommand ?? "prodex pro browser login --headed";
|
|
2008
|
+
const headedLoginHint = ` No interactive window is available; run \`${headedLoginCommand}\` to complete login, captcha, or human verification visibly.`;
|
|
1896
2009
|
const startedAt = now();
|
|
1897
|
-
stderr(
|
|
2010
|
+
stderr(hasInteractiveWindow
|
|
2011
|
+
? "login: waiting for a logged-in ChatGPT tab (finish login in the dedicated Chrome browser; Ctrl+C stops waiting)..."
|
|
2012
|
+
: `login: waiting for a logged-in ChatGPT tab (no interactive window; use \`${headedLoginCommand}\` if login or verification is required; Ctrl+C stops waiting)...`);
|
|
1898
2013
|
let lastState = "";
|
|
1899
2014
|
let openMissingTabAttempts = 0;
|
|
1900
2015
|
while (now() - startedAt < timeoutMs) {
|
|
@@ -1911,19 +2026,26 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
|
|
|
1911
2026
|
? "login: the running Chrome had no ChatGPT tab - opening a ChatGPT tab in it..."
|
|
1912
2027
|
: `login: still no ChatGPT tab - opening one again (attempt ${attempt})...`);
|
|
1913
2028
|
const opened = await openTabFn(options.port);
|
|
1914
|
-
if (opened === false)
|
|
1915
|
-
stderr(
|
|
2029
|
+
if (opened === false) {
|
|
2030
|
+
stderr(hasInteractiveWindow
|
|
2031
|
+
? "login: could not open a ChatGPT tab through the debug port; open https://chatgpt.com/ in that browser."
|
|
2032
|
+
: `login: could not open a ChatGPT tab through the debug port; run \`${headedLoginCommand}\` to open it visibly.`);
|
|
2033
|
+
}
|
|
1916
2034
|
await sleepFn(pollMs);
|
|
1917
2035
|
continue;
|
|
1918
2036
|
}
|
|
1919
2037
|
const state = !status.reachable
|
|
1920
2038
|
? "login: browser starting..."
|
|
1921
2039
|
: status.blocker
|
|
1922
|
-
? `login: blocked - ${status.blocker.message}`
|
|
2040
|
+
? `login: blocked - ${status.blocker.message}${hasInteractiveWindow ? "" : headedLoginHint}`
|
|
1923
2041
|
: !status.loggedInLikely
|
|
1924
|
-
?
|
|
2042
|
+
? hasInteractiveWindow
|
|
2043
|
+
? "login: waiting for ChatGPT login in the dedicated Chrome browser..."
|
|
2044
|
+
: `login: waiting for a saved ChatGPT login.${headedLoginHint}`
|
|
1925
2045
|
: !status.hasComposer
|
|
1926
|
-
?
|
|
2046
|
+
? hasInteractiveWindow
|
|
2047
|
+
? "login: logged in; open a chat so the prompt composer is visible..."
|
|
2048
|
+
: `login: logged in, but no prompt composer is ready.${headedLoginHint}`
|
|
1927
2049
|
: "";
|
|
1928
2050
|
if (state === "") {
|
|
1929
2051
|
stderr(`login: READY - logged-in ChatGPT tab with composer detected (${Math.round((now() - startedAt) / 1000)}s).`);
|
|
@@ -1938,7 +2060,9 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
|
|
|
1938
2060
|
break;
|
|
1939
2061
|
await sleepFn(Math.min(pollMs, Math.max(1, remainingMs)));
|
|
1940
2062
|
}
|
|
1941
|
-
stderr(
|
|
2063
|
+
stderr(hasInteractiveWindow
|
|
2064
|
+
? `login: not ready after ${Math.round(timeoutMs / 1000)}s. Finish login in the browser, then verify with \`prodex pro browser check\`.`
|
|
2065
|
+
: `login: not ready after ${Math.round(timeoutMs / 1000)}s. Run \`${headedLoginCommand}\` to complete login, captcha, or human verification visibly, then retry.`);
|
|
1942
2066
|
return false;
|
|
1943
2067
|
}
|
|
1944
2068
|
export async function assertBrowserLaunchStayedAlive(opened, timeoutMs) {
|
|
@@ -2529,9 +2653,14 @@ export async function listConsultListEntries(store, options = { readOnly: true }
|
|
|
2529
2653
|
}
|
|
2530
2654
|
return entries;
|
|
2531
2655
|
}
|
|
2656
|
+
function formatHeadedBrowserLoginCommand(sourceCli, options = {}) {
|
|
2657
|
+
return `${formatBrowserLoginCommand(sourceCli, options)} --headed`;
|
|
2658
|
+
}
|
|
2532
2659
|
export function printBrowserLoginGuide(stdout, input) {
|
|
2533
|
-
const
|
|
2660
|
+
const noInteractiveWindow = input.headless === true || input.virtualDisplay === true;
|
|
2661
|
+
const windowAvailable = (input.opened || input.reused === true) && !noInteractiveWindow;
|
|
2534
2662
|
const loginCommand = formatBrowserLoginCommand(input.sourceCli, input.commandOptions);
|
|
2663
|
+
const headedLoginCommand = formatHeadedBrowserLoginCommand(input.sourceCli, input.commandOptions);
|
|
2535
2664
|
const runtimeCommandOptions = {
|
|
2536
2665
|
...(input.commandOptions?.cwd ? { cwd: input.commandOptions.cwd } : {}),
|
|
2537
2666
|
...(input.commandOptions?.port ? { port: input.commandOptions.port } : {})
|
|
@@ -2543,13 +2672,18 @@ export function printBrowserLoginGuide(stdout, input) {
|
|
|
2543
2672
|
? input.reused
|
|
2544
2673
|
? "Headless ChatGPT browser is already running - reusing it (no window)."
|
|
2545
2674
|
: "Started the dedicated ChatGPT browser headless (no window). It reuses the profile you signed in with."
|
|
2546
|
-
: input.reused
|
|
2547
|
-
?
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2675
|
+
: input.virtualDisplay && (input.opened || input.reused)
|
|
2676
|
+
? input.reused
|
|
2677
|
+
? "ChatGPT browser is already running on its saved virtual display - reusing it (no visible window)."
|
|
2678
|
+
: "Started the dedicated ChatGPT browser on a virtual display (no visible window)."
|
|
2679
|
+
: input.reused
|
|
2680
|
+
? "Chrome is already running for ChatGPT - reusing it (no new window opened)."
|
|
2681
|
+
: input.opened
|
|
2682
|
+
? "Opened the dedicated Chrome window for ChatGPT."
|
|
2683
|
+
: "Dry run: no browser was opened.");
|
|
2684
|
+
if (noInteractiveWindow && (input.opened || input.reused)) {
|
|
2552
2685
|
stdout("");
|
|
2686
|
+
stdout(`If ChatGPT needs login, captcha, or human verification, run \`${headedLoginCommand}\` to handle it in a visible window.`);
|
|
2553
2687
|
stdout(`Next: run \`${checkCommand}\` to confirm the session, then consult as usual.`);
|
|
2554
2688
|
return;
|
|
2555
2689
|
}
|
|
@@ -2565,7 +2699,9 @@ export function printBrowserLoginGuide(stdout, input) {
|
|
|
2565
2699
|
stdout(`7. Run \`${smokeCommand}\` to verify a real Pro response path.`);
|
|
2566
2700
|
}
|
|
2567
2701
|
else {
|
|
2568
|
-
stdout(
|
|
2702
|
+
stdout(noInteractiveWindow
|
|
2703
|
+
? `1. Run \`${headedLoginCommand}\` to open a visible dedicated Chrome window for login or verification.`
|
|
2704
|
+
: `1. Run \`${loginCommand}\` without \`--dry-run\` to open the dedicated Chrome window.`);
|
|
2569
2705
|
stdout(`2. Log in manually at ${input.loginUrl} in that Chrome window.`);
|
|
2570
2706
|
stdout("3. If ChatGPT asks for captcha, Cloudflare/human verification, permission, or account verification, complete it in the browser.");
|
|
2571
2707
|
stdout("4. For usage limit, message limit, model limit, or rate limit, wait for the reset or choose an available model in the browser.");
|
package/dist/cli-server.js
CHANGED
|
@@ -40,8 +40,8 @@ export async function runSetupCommand(rest, io) {
|
|
|
40
40
|
? await runBrowserDefaultsWizard(resolvePromptUser(io), io.stdout)
|
|
41
41
|
: parseBrowserDefaultFlags(rest);
|
|
42
42
|
const config = await writeLocalConfig(targetCwd, {
|
|
43
|
-
host: readFlag(rest, "--host")
|
|
44
|
-
port: readPortFlag(rest, "--port")
|
|
43
|
+
host: readFlag(rest, "--host"),
|
|
44
|
+
port: readPortFlag(rest, "--port"),
|
|
45
45
|
token: readFlag(rest, "--token"),
|
|
46
46
|
tokenTtlHours: readPositiveNumberFlag(rest, "--token-ttl-hours"),
|
|
47
47
|
browserDefaults
|
package/dist/cli.js
CHANGED
|
@@ -82,10 +82,6 @@ async function runInteractiveUi(io) {
|
|
|
82
82
|
const { listRecentChatGptConversations } = await import("./chatgpt-browser.js");
|
|
83
83
|
return listRecentChatGptConversations({});
|
|
84
84
|
},
|
|
85
|
-
openThread: async (url) => {
|
|
86
|
-
const { navigateChatGptTabTo } = await import("./chatgpt-browser.js");
|
|
87
|
-
return navigateChatGptTabTo(url, {});
|
|
88
|
-
},
|
|
89
85
|
listProjectsWithIds: async () => {
|
|
90
86
|
const { listChatGptProjectsWithIds } = await import("./chatgpt-browser.js");
|
|
91
87
|
return listChatGptProjectsWithIds({});
|
|
@@ -97,6 +93,7 @@ async function runInteractiveUi(io) {
|
|
|
97
93
|
},
|
|
98
94
|
runConsult: (args, onProgress) => runCli(args, {
|
|
99
95
|
...io,
|
|
96
|
+
navigateInteractiveTarget: true,
|
|
100
97
|
stderr: (line) => {
|
|
101
98
|
if (line.startsWith("progress:"))
|
|
102
99
|
onProgress(line);
|