@youdie006/prodex 0.16.11 → 0.16.13
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/dist/chatgpt-browser.js +79 -3
- package/dist/cli-args.js +1 -0
- package/dist/cli-help.js +4 -3
- package/dist/cli-pro.js +41 -2
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -933,7 +933,26 @@ async function selectModelReasoning(cdp, options) {
|
|
|
933
933
|
throw new Error(button.reason ?? "Could not open the ChatGPT model selector");
|
|
934
934
|
}
|
|
935
935
|
try {
|
|
936
|
-
|
|
936
|
+
// The hover-verified click can be transiently refused right after a page
|
|
937
|
+
// transition (measured live: the just-closed create-project modal's
|
|
938
|
+
// overlay still covered the selector for a beat, and one refusal aborted
|
|
939
|
+
// the whole send). Retry briefly with FRESH coordinates; a persistent
|
|
940
|
+
// cover still fails with the refusal message.
|
|
941
|
+
const clickDeadline = Date.now() + 5_000;
|
|
942
|
+
for (;;) {
|
|
943
|
+
try {
|
|
944
|
+
await verifiedClickAt(cdp, button.x, button.y, "model selector");
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
if (Date.now() >= clickDeadline || !/Refusing to click/.test(error instanceof Error ? error.message : String(error)))
|
|
949
|
+
throw error;
|
|
950
|
+
await sleep(700);
|
|
951
|
+
const fresh = await cdp.evaluate(modelButtonRectExpression());
|
|
952
|
+
if (fresh.ok && fresh.x !== undefined && fresh.y !== undefined)
|
|
953
|
+
button = fresh;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
937
956
|
const opened = await waitForExpressionTrue(cdp, menuOpenExpression(), MENU_OPEN_TIMEOUT_MS);
|
|
938
957
|
if (!opened)
|
|
939
958
|
throw new Error("ChatGPT model menu did not open after clicking the selector");
|
|
@@ -1120,7 +1139,7 @@ async function selectProject(cdp, options) {
|
|
|
1120
1139
|
}
|
|
1121
1140
|
if (!hit.ok || hit.x === undefined || hit.y === undefined) {
|
|
1122
1141
|
const detail = hit.reason && hit.reason !== "project not found in sidebar" ? ` (${hit.reason})` : "";
|
|
1123
|
-
throw new Error(`ChatGPT project not found in sidebar: ${options.project}${detail}
|
|
1142
|
+
throw new Error(`ChatGPT project not found in sidebar: ${options.project}${detail} List the visible names with \`prodex pro browser projects\`.`);
|
|
1124
1143
|
}
|
|
1125
1144
|
await verifiedClickAt(cdp, hit.x, hit.y, `project ${options.project}`);
|
|
1126
1145
|
const navigated = await waitForExpressionTrue(cdp, `location.href !== ${JSON.stringify(hrefBefore)}`, PROJECT_NAVIGATION_TIMEOUT_MS);
|
|
@@ -1205,7 +1224,24 @@ export async function sendChatGptPrompt(options) {
|
|
|
1205
1224
|
if (normalizedTargetUrl)
|
|
1206
1225
|
assertChatGptTargetUrlMatches(status.url, normalizedTargetUrl);
|
|
1207
1226
|
assertVisibleChatGptTab(status.visibilityState, status.url, normalizedTargetUrl);
|
|
1208
|
-
|
|
1227
|
+
let busyBlocker = chatGptBusyBlocker(status.generating);
|
|
1228
|
+
if (busyBlocker && (options.busyWaitMs ?? 0) > 0) {
|
|
1229
|
+
// Queue behind the in-flight response instead of failing: shared-tab
|
|
1230
|
+
// contention (another agent or the user mid-generation) is a when, not an
|
|
1231
|
+
// if. Bounded, and a mid-wait page blocker (usage limit etc.) still throws.
|
|
1232
|
+
const busyDeadline = Date.now() + (options.busyWaitMs ?? 0);
|
|
1233
|
+
emitProgress("waiting", "tab busy with another response; waiting");
|
|
1234
|
+
while (busyBlocker && Date.now() < busyDeadline) {
|
|
1235
|
+
await sleep(3_000);
|
|
1236
|
+
status = await evaluateOnPage(page, statusExpression());
|
|
1237
|
+
const midBlocker = detectChatGptPageBlocker(status);
|
|
1238
|
+
if (midBlocker)
|
|
1239
|
+
throw new ChatGptBrowserBlockerError(midBlocker);
|
|
1240
|
+
busyBlocker = chatGptBusyBlocker(status.generating);
|
|
1241
|
+
if (busyBlocker)
|
|
1242
|
+
emitProgress("waiting", "tab busy with another response; waiting");
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1209
1245
|
if (busyBlocker) {
|
|
1210
1246
|
throw new ChatGptBrowserBlockerError(busyBlocker);
|
|
1211
1247
|
}
|
|
@@ -1378,6 +1414,46 @@ export function modelMenuOptionsExpression() {
|
|
|
1378
1414
|
// Read-only discovery: open the composer model menu, read the option labels,
|
|
1379
1415
|
// and press Escape. Nothing is clicked inside the menu, so the user's model
|
|
1380
1416
|
// selection is never changed.
|
|
1417
|
+
// Sidebar project names, extracted from the per-row options-button aria-labels
|
|
1418
|
+
// (English "Open project options for <name>", Korean "<name> 프로젝트 옵션 열기").
|
|
1419
|
+
export function sidebarProjectNamesExpression() {
|
|
1420
|
+
return `(() => {
|
|
1421
|
+
const names = [...document.querySelectorAll('[aria-label*="프로젝트 옵션"],[aria-label*="project options" i]')]
|
|
1422
|
+
.map((b) => (b.getAttribute("aria-label") || "").replace(/^open project options for /i, "").replace(/\\s*프로젝트 옵션 열기$/, "").trim())
|
|
1423
|
+
.filter(Boolean);
|
|
1424
|
+
return [...new Set(names)];
|
|
1425
|
+
})()`;
|
|
1426
|
+
}
|
|
1427
|
+
// Read-only discovery for --project/setup --project: list the sidebar project
|
|
1428
|
+
// names exactly as ChatGPT renders them, so nobody has to guess spelling or
|
|
1429
|
+
// case. Polls briefly because the Projects section hydrates after navigation.
|
|
1430
|
+
export async function listChatGptSidebarProjects(input = {}) {
|
|
1431
|
+
const port = resolveCdpPort(input.port);
|
|
1432
|
+
const timeoutMs = input.timeoutMs ?? 15_000;
|
|
1433
|
+
const pageResult = await findChatGptPage(port, computePageDiscoveryTimeout(timeoutMs), undefined);
|
|
1434
|
+
if (!pageResult.ok) {
|
|
1435
|
+
throwBlockerOrError(pageResult.blocker, "ChatGPT browser page is not available");
|
|
1436
|
+
}
|
|
1437
|
+
if (!pageResult.page) {
|
|
1438
|
+
if (pageResult.blocker)
|
|
1439
|
+
throw new ChatGptBrowserBlockerError(pageResult.blocker);
|
|
1440
|
+
assertChatGptPageAvailable();
|
|
1441
|
+
}
|
|
1442
|
+
const page = pageResult.page;
|
|
1443
|
+
const status = await readSettledChatGptPageStatus(page);
|
|
1444
|
+
const blocker = detectChatGptPageBlocker(status);
|
|
1445
|
+
if (blocker)
|
|
1446
|
+
throw new ChatGptBrowserBlockerError(blocker);
|
|
1447
|
+
let projects = [];
|
|
1448
|
+
const deadline = Date.now() + 6_000;
|
|
1449
|
+
for (;;) {
|
|
1450
|
+
projects = await evaluateOnPage(page, sidebarProjectNamesExpression());
|
|
1451
|
+
if (projects.length > 0 || Date.now() >= deadline)
|
|
1452
|
+
break;
|
|
1453
|
+
await sleep(300);
|
|
1454
|
+
}
|
|
1455
|
+
return { url: status.url, projects };
|
|
1456
|
+
}
|
|
1381
1457
|
export async function listChatGptModelOptions(input = {}) {
|
|
1382
1458
|
const port = resolveCdpPort(input.port);
|
|
1383
1459
|
const timeoutMs = input.timeoutMs ?? 15_000;
|
package/dist/cli-args.js
CHANGED
package/dist/cli-help.js
CHANGED
|
@@ -24,7 +24,8 @@ Ask / consult commands:
|
|
|
24
24
|
prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 1500]
|
|
25
25
|
prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]
|
|
26
26
|
prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of model menu options
|
|
27
|
-
prodex pro browser
|
|
27
|
+
prodex pro browser projects [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of sidebar project names (for --project)
|
|
28
|
+
prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"] [--project "name" | --project-new "name"] "prompt" # explicit visible-browser send
|
|
28
29
|
prodex pro latest [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
29
30
|
prodex pro list [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
30
31
|
prodex pro show <task-id|latest> [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
@@ -250,8 +251,8 @@ export function printProBrowserHelp(stdout, sourceCli) {
|
|
|
250
251
|
: "prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]";
|
|
251
252
|
const selectionUsage = '[--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"] [--project "name" | --project-new "name"]';
|
|
252
253
|
const askUsage = sourceCli
|
|
253
|
-
? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000] [--target-url url --confirm-target] [--new-chat] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`
|
|
254
|
-
: `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000] [--target-url url --confirm-target] [--new-chat] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`;
|
|
254
|
+
? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`
|
|
255
|
+
: `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`;
|
|
255
256
|
const modelsUsage = sourceCli
|
|
256
257
|
? `${cli} pro browser models${sourceCliOption} [--port 9333] [--timeout-ms 15000]`
|
|
257
258
|
: "prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000]";
|
package/dist/cli-pro.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { buildDryRunBundle } from "./bundle.js";
|
|
4
|
-
import { DEFAULT_CDP_PORT, resolveCdpPort, chatGptVisibilityBlocker, defaultChatGptProfileDir, getChatGptBrowserStatus, listChatGptModelOptions, normalizeChatGptTargetUrl, openChatGptBrowser, parseProMode, parseReasoningEffort, readLastBrowserLoginLaunch, recordBrowserLoginLaunch, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
4
|
+
import { DEFAULT_CDP_PORT, resolveCdpPort, chatGptVisibilityBlocker, defaultChatGptProfileDir, getChatGptBrowserStatus, listChatGptModelOptions, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, parseProMode, parseReasoningEffort, readLastBrowserLoginLaunch, recordBrowserLoginLaunch, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
5
5
|
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, readPositiveIntegerFlag, readRepeatedFlag, resolveCwdFlag, resolveOptionalFileFlag, unknownSubcommandError } from "./cli-args.js";
|
|
6
6
|
import { printProBrowserHelp, printProHelp } from "./cli-help.js";
|
|
7
7
|
import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledger.js";
|
|
@@ -145,6 +145,7 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
145
145
|
const straySendFlag = [
|
|
146
146
|
"--port",
|
|
147
147
|
"--timeout-ms",
|
|
148
|
+
"--busy-wait-ms",
|
|
148
149
|
"--target-url",
|
|
149
150
|
"--confirm-target",
|
|
150
151
|
"--project",
|
|
@@ -330,7 +331,36 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
330
331
|
io.stdout("Use radio entries with `pro browser ask --model/--effort` (e.g. --model Pro).");
|
|
331
332
|
return 0;
|
|
332
333
|
}
|
|
333
|
-
|
|
334
|
+
if (browserSubcommand === "projects") {
|
|
335
|
+
if (printProBrowserHelpIfRequested(browserArgs, "pro browser projects", io, { valueFlags: ["--port", "--timeout-ms", "--source-cli"] }))
|
|
336
|
+
return 0;
|
|
337
|
+
assertOnlyOptions(browserArgs, "pro browser projects", ["--port", "--timeout-ms", "--source-cli"]);
|
|
338
|
+
const projectsSourceCli = resolveOptionalFileFlag(io.cwd, browserArgs, "--source-cli");
|
|
339
|
+
const projectsPort = readPortFlag(browserArgs, "--port");
|
|
340
|
+
const projectsTimeoutMs = readPositiveIntegerFlag(browserArgs, "--timeout-ms");
|
|
341
|
+
const projectsResolvedPort = resolveCdpPort(projectsPort);
|
|
342
|
+
let listed;
|
|
343
|
+
try {
|
|
344
|
+
listed = await listChatGptSidebarProjects({ port: projectsPort, timeoutMs: projectsTimeoutMs });
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
const blocker = sourceAwareBrowserBlocker(browserSendBlockerFromError(error), projectsSourceCli, {
|
|
348
|
+
...(projectsResolvedPort !== DEFAULT_CDP_PORT ? { port: projectsResolvedPort } : {})
|
|
349
|
+
});
|
|
350
|
+
throw new Error(blocker.next_step ? `${blocker.message} Next: ${blocker.next_step}` : errorMessage(error));
|
|
351
|
+
}
|
|
352
|
+
if (listed.projects.length === 0) {
|
|
353
|
+
io.stdout("No projects visible in the ChatGPT sidebar (read-only check).");
|
|
354
|
+
io.stdout("If you expect projects, open the sidebar's Projects section once in the visible browser, then retry.");
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
io.stdout("ChatGPT sidebar projects (read-only; exact names as rendered):");
|
|
358
|
+
for (const name of listed.projects)
|
|
359
|
+
io.stdout(` ${name}`);
|
|
360
|
+
io.stdout("Use with `pro browser ask --project \"<name>\"` or pin one with `prodex setup --project \"<name>\"`.");
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
throw unknownSubcommandError("pro browser", browserSubcommand, ["login", "ask", "smoke", "check", "models", "projects"]);
|
|
334
364
|
}
|
|
335
365
|
if (subcommand === "open" || subcommand === "status" || subcommand === "smoke" || subcommand === "check" || subcommand === "doctor") {
|
|
336
366
|
throw new Error(`Use \`prodex pro browser ${subcommand === "doctor" ? "check" : subcommand}\` for explicit browser automation.`);
|
|
@@ -565,6 +595,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
565
595
|
...(selectionEffort ? { effort: selectionEffort } : {})
|
|
566
596
|
};
|
|
567
597
|
const browserPort = hasSendMode ? resolveCdpPort(readPortFlag(parsedAskPro.optionArgs, "--port")) : undefined;
|
|
598
|
+
const busyWaitMs = readPositiveIntegerFlag(parsedAskPro.optionArgs, "--busy-wait-ms");
|
|
568
599
|
// Pro extended can legitimately think for minutes, so its default timeout is
|
|
569
600
|
// higher; an explicit --timeout-ms always wins.
|
|
570
601
|
const defaultBrowserTimeoutMs = selectionProMode === "확장" ? 300_000 : 90_000;
|
|
@@ -630,6 +661,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
630
661
|
targetUrl: normalizedTargetUrl,
|
|
631
662
|
timeoutMs: browserTimeoutMs,
|
|
632
663
|
...(newChat ? { newChat: true } : {}),
|
|
664
|
+
...(busyWaitMs !== undefined ? { busyWaitMs } : {}),
|
|
633
665
|
project: selectionProject,
|
|
634
666
|
projectNew: selectionProjectNew,
|
|
635
667
|
model: selectionModel,
|
|
@@ -699,6 +731,13 @@ export async function runAskProCommand(rest, io) {
|
|
|
699
731
|
if (!selectionModel && !selectionProMode && !selectionEffort) {
|
|
700
732
|
persistenceWarnings.push("model_selection_warning: no model/effort was selected for this send (no per-ask flag, no saved default), so it used whatever the ChatGPT UI last had selected. Pin one with `prodex setup --model Pro` or pass --model/--effort.");
|
|
701
733
|
}
|
|
734
|
+
// In-project threads carry the project slug in their URL
|
|
735
|
+
// (/g/g-p-<project>/c/<id>); a bare /c/<id> after requesting a project
|
|
736
|
+
// means the thread landed at root - say so instead of leaving it to a
|
|
737
|
+
// sidebar audit (field-verified failure mode).
|
|
738
|
+
if ((selectionMetadata.project || selectionMetadata.project_new) && !/\/g\/g-p-/.test(consult.url ?? "")) {
|
|
739
|
+
persistenceWarnings.push("project_landing_warning: a project was requested but the answered thread URL is a root /c/ thread, so it likely landed OUTSIDE the project. Move it via the thread menu (Move to project) or re-run; list projects with `prodex pro browser projects`.");
|
|
740
|
+
}
|
|
702
741
|
// Truncation and other send warnings must be visible at runtime, not
|
|
703
742
|
// only inside the persisted receipt: a caller who never opens .bridge
|
|
704
743
|
// would otherwise treat a cut-off answer as complete.
|