@youdie006/prodex 0.16.10 → 0.16.12

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.
@@ -1120,7 +1120,7 @@ async function selectProject(cdp, options) {
1120
1120
  }
1121
1121
  if (!hit.ok || hit.x === undefined || hit.y === undefined) {
1122
1122
  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}`);
1123
+ throw new Error(`ChatGPT project not found in sidebar: ${options.project}${detail} List the visible names with \`prodex pro browser projects\`.`);
1124
1124
  }
1125
1125
  await verifiedClickAt(cdp, hit.x, hit.y, `project ${options.project}`);
1126
1126
  const navigated = await waitForExpressionTrue(cdp, `location.href !== ${JSON.stringify(hrefBefore)}`, PROJECT_NAVIGATION_TIMEOUT_MS);
@@ -1178,12 +1178,20 @@ export async function sendChatGptPrompt(options) {
1178
1178
  assertChatGptPageAvailable();
1179
1179
  }
1180
1180
  const page = pageResult.page;
1181
- if (options.newChat) {
1181
+ if (options.newChat && !options.project && !options.projectNew) {
1182
1182
  // Long accumulated threads eventually break acceptance detection, so
1183
1183
  // start from a clean chat. Wait for the tab to actually reach the fresh
1184
1184
  // empty chat (root URL, zero messages) rather than a fixed sleep: a slow
1185
1185
  // SPA navigation could otherwise leave the old thread rendered, poisoning
1186
1186
  // the answer-count baseline captured below and causing a false timeout.
1187
+ //
1188
+ // Skipped when a project is requested: the project home the selection step
1189
+ // navigates to IS the fresh composer for "a new chat in this project".
1190
+ // Navigating to the root new chat first leaves the SPA composer bound to
1191
+ // the ROOT conversation target even after entering the project, so the
1192
+ // thread silently lands outside the project (measured live: --new-chat
1193
+ // --project threads appeared in the root chat list, --project-only
1194
+ // threads appeared inside the project).
1187
1195
  await evaluateOnPage(page, `location.assign("https://chatgpt.com/")`);
1188
1196
  await waitForFreshChatGptPage(page, 8_000);
1189
1197
  }
@@ -1370,6 +1378,46 @@ export function modelMenuOptionsExpression() {
1370
1378
  // Read-only discovery: open the composer model menu, read the option labels,
1371
1379
  // and press Escape. Nothing is clicked inside the menu, so the user's model
1372
1380
  // selection is never changed.
1381
+ // Sidebar project names, extracted from the per-row options-button aria-labels
1382
+ // (English "Open project options for <name>", Korean "<name> 프로젝트 옵션 열기").
1383
+ export function sidebarProjectNamesExpression() {
1384
+ return `(() => {
1385
+ const names = [...document.querySelectorAll('[aria-label*="프로젝트 옵션"],[aria-label*="project options" i]')]
1386
+ .map((b) => (b.getAttribute("aria-label") || "").replace(/^open project options for /i, "").replace(/\\s*프로젝트 옵션 열기$/, "").trim())
1387
+ .filter(Boolean);
1388
+ return [...new Set(names)];
1389
+ })()`;
1390
+ }
1391
+ // Read-only discovery for --project/setup --project: list the sidebar project
1392
+ // names exactly as ChatGPT renders them, so nobody has to guess spelling or
1393
+ // case. Polls briefly because the Projects section hydrates after navigation.
1394
+ export async function listChatGptSidebarProjects(input = {}) {
1395
+ const port = resolveCdpPort(input.port);
1396
+ const timeoutMs = input.timeoutMs ?? 15_000;
1397
+ const pageResult = await findChatGptPage(port, computePageDiscoveryTimeout(timeoutMs), undefined);
1398
+ if (!pageResult.ok) {
1399
+ throwBlockerOrError(pageResult.blocker, "ChatGPT browser page is not available");
1400
+ }
1401
+ if (!pageResult.page) {
1402
+ if (pageResult.blocker)
1403
+ throw new ChatGptBrowserBlockerError(pageResult.blocker);
1404
+ assertChatGptPageAvailable();
1405
+ }
1406
+ const page = pageResult.page;
1407
+ const status = await readSettledChatGptPageStatus(page);
1408
+ const blocker = detectChatGptPageBlocker(status);
1409
+ if (blocker)
1410
+ throw new ChatGptBrowserBlockerError(blocker);
1411
+ let projects = [];
1412
+ const deadline = Date.now() + 6_000;
1413
+ for (;;) {
1414
+ projects = await evaluateOnPage(page, sidebarProjectNamesExpression());
1415
+ if (projects.length > 0 || Date.now() >= deadline)
1416
+ break;
1417
+ await sleep(300);
1418
+ }
1419
+ return { url: status.url, projects };
1420
+ }
1373
1421
  export async function listChatGptModelOptions(input = {}) {
1374
1422
  const port = resolveCdpPort(input.port);
1375
1423
  const timeoutMs = input.timeoutMs ?? 15_000;
package/dist/cli-help.js CHANGED
@@ -24,6 +24,7 @@ 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 projects [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of sidebar project names (for --project)
27
28
  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] [--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]
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";
@@ -330,7 +330,36 @@ export async function runProCommand(rest, io, runCliFn) {
330
330
  io.stdout("Use radio entries with `pro browser ask --model/--effort` (e.g. --model Pro).");
331
331
  return 0;
332
332
  }
333
- throw unknownSubcommandError("pro browser", browserSubcommand, ["login", "ask", "smoke", "check", "models"]);
333
+ if (browserSubcommand === "projects") {
334
+ if (printProBrowserHelpIfRequested(browserArgs, "pro browser projects", io, { valueFlags: ["--port", "--timeout-ms", "--source-cli"] }))
335
+ return 0;
336
+ assertOnlyOptions(browserArgs, "pro browser projects", ["--port", "--timeout-ms", "--source-cli"]);
337
+ const projectsSourceCli = resolveOptionalFileFlag(io.cwd, browserArgs, "--source-cli");
338
+ const projectsPort = readPortFlag(browserArgs, "--port");
339
+ const projectsTimeoutMs = readPositiveIntegerFlag(browserArgs, "--timeout-ms");
340
+ const projectsResolvedPort = resolveCdpPort(projectsPort);
341
+ let listed;
342
+ try {
343
+ listed = await listChatGptSidebarProjects({ port: projectsPort, timeoutMs: projectsTimeoutMs });
344
+ }
345
+ catch (error) {
346
+ const blocker = sourceAwareBrowserBlocker(browserSendBlockerFromError(error), projectsSourceCli, {
347
+ ...(projectsResolvedPort !== DEFAULT_CDP_PORT ? { port: projectsResolvedPort } : {})
348
+ });
349
+ throw new Error(blocker.next_step ? `${blocker.message} Next: ${blocker.next_step}` : errorMessage(error));
350
+ }
351
+ if (listed.projects.length === 0) {
352
+ io.stdout("No projects visible in the ChatGPT sidebar (read-only check).");
353
+ io.stdout("If you expect projects, open the sidebar's Projects section once in the visible browser, then retry.");
354
+ return 0;
355
+ }
356
+ io.stdout("ChatGPT sidebar projects (read-only; exact names as rendered):");
357
+ for (const name of listed.projects)
358
+ io.stdout(` ${name}`);
359
+ io.stdout("Use with `pro browser ask --project \"<name>\"` or pin one with `prodex setup --project \"<name>\"`.");
360
+ return 0;
361
+ }
362
+ throw unknownSubcommandError("pro browser", browserSubcommand, ["login", "ask", "smoke", "check", "models", "projects"]);
334
363
  }
335
364
  if (subcommand === "open" || subcommand === "status" || subcommand === "smoke" || subcommand === "check" || subcommand === "doctor") {
336
365
  throw new Error(`Use \`prodex pro browser ${subcommand === "doctor" ? "check" : subcommand}\` for explicit browser automation.`);
@@ -699,6 +728,13 @@ export async function runAskProCommand(rest, io) {
699
728
  if (!selectionModel && !selectionProMode && !selectionEffort) {
700
729
  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
730
  }
731
+ // In-project threads carry the project slug in their URL
732
+ // (/g/g-p-<project>/c/<id>); a bare /c/<id> after requesting a project
733
+ // means the thread landed at root - say so instead of leaving it to a
734
+ // sidebar audit (field-verified failure mode).
735
+ if ((selectionMetadata.project || selectionMetadata.project_new) && !/\/g\/g-p-/.test(consult.url ?? "")) {
736
+ 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`.");
737
+ }
702
738
  // Truncation and other send warnings must be visible at runtime, not
703
739
  // only inside the persisted receipt: a caller who never opens .bridge
704
740
  // would otherwise treat a cut-off answer as complete.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.16.10",
3
+ "version": "0.16.12",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",