@youdie006/prodex 0.16.4 → 0.16.6

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.
@@ -857,6 +857,13 @@ export function projectItemRectExpression(name) {
857
857
  }
858
858
  }
859
859
  if (!target) return { ok: false, reason: "project not found in sidebar" };
860
+ // 2026-07 ChatGPT update: the project row (li) is no longer a link - the
861
+ // navigation affordance is a dedicated "Open project home" button inside
862
+ // the row (verified live: clicking the row does nothing, clicking the home
863
+ // button navigates to /g/g-p-...). Prefer it; fall back to the row click
864
+ // for the old UI.
865
+ const home = target.querySelector('[aria-label*="project home" i],[aria-label*="프로젝트 홈"]');
866
+ if (home) return clickPoint(home);
860
867
  return clickPoint(target, 18);
861
868
  })()`;
862
869
  }
@@ -903,7 +910,11 @@ async function selectModelReasoning(cdp, options) {
903
910
  // Open the Pro sub-mode submenu via the chevron, then pick 기본/확장.
904
911
  const expander = await cdp.evaluate(proSubmenuExpanderRectExpression());
905
912
  if (!expander.ok || expander.x === undefined || expander.y === undefined) {
906
- throw new Error(expander.reason ?? "Could not open the ChatGPT Pro sub-mode submenu");
913
+ // The 2026-07 ChatGPT update removed the Pro sub-mode chevron from the
914
+ // model picker (verified live, including on hover) - Pro is a plain
915
+ // radio now. Name the change and the working alternative instead of a
916
+ // bare "expander not found".
917
+ throw new Error("The ChatGPT model menu no longer offers Pro sub-modes (the 2026-07 update removed the Pro submenu; Pro is a single mode now). Use --model Pro instead of --pro-mode. If ChatGPT restores sub-modes, update prodex (npm i -g @youdie006/prodex@latest).");
907
918
  }
908
919
  await verifiedClickAt(cdp, expander.x, expander.y, "Pro sub-mode expander");
909
920
  const subLabels = PRO_MODE_SUBMENU_LABELS[options.proMode];
@@ -1063,7 +1074,14 @@ async function selectProject(cdp, options) {
1063
1074
  await verifiedClickAt(cdp, hit.x, hit.y, `project ${options.project}`);
1064
1075
  const navigated = await waitForExpressionTrue(cdp, `location.href !== ${JSON.stringify(hrefBefore)}`, PROJECT_NAVIGATION_TIMEOUT_MS);
1065
1076
  if (!navigated) {
1066
- throw new Error(`Clicking project "${options.project}" did not navigate the visible tab. If the tab is already inside this project, omit --project and retry.`);
1077
+ // The href staying put is fine when the tab was ALREADY on this project's
1078
+ // home: the click landed on the requested row's own navigation control
1079
+ // (hover-verified above), so an unchanged project URL means "already
1080
+ // there", not a failed click.
1081
+ const alreadyInProject = await cdp.evaluate(`/^https:\\/\\/chatgpt\\.com\\/g\\/g-p-/.test(location.href)`);
1082
+ if (!alreadyInProject) {
1083
+ throw new Error(`Clicking project "${options.project}" did not navigate the visible tab. If the tab is already inside this project, omit --project and retry.`);
1084
+ }
1067
1085
  }
1068
1086
  const composerReady = await waitForExpressionTrue(cdp, `Boolean(document.querySelector('#prompt-textarea,[contenteditable="true"],textarea'))`, PROJECT_NAVIGATION_TIMEOUT_MS);
1069
1087
  if (!composerReady) {
@@ -1724,7 +1742,7 @@ function composerExpressionHelpers() {
1724
1742
  };
1725
1743
  `;
1726
1744
  }
1727
- function answerExpression() {
1745
+ export function answerExpression() {
1728
1746
  const excludedTextSelector = JSON.stringify(CHATGPT_RUNTIME_BLOCKER_TEXT_EXCLUDED_ANCESTORS);
1729
1747
  const blockerScanExcludedSelector = JSON.stringify(CHATGPT_BLOCKER_SCAN_EXCLUDED_ANCESTORS);
1730
1748
  const streamingSelector = JSON.stringify(CHATGPT_STREAMING_SELECTOR);
package/dist/cli-pro.js CHANGED
@@ -297,17 +297,31 @@ export async function runProCommand(rest, io, runCliFn) {
297
297
  if (printProBrowserHelpIfRequested(browserArgs, "pro browser models", io, { valueFlags: ["--port", "--timeout-ms", "--source-cli"] }))
298
298
  return 0;
299
299
  assertOnlyOptions(browserArgs, "pro browser models", ["--port", "--timeout-ms", "--source-cli"]);
300
- const listed = await listChatGptModelOptions({
301
- port: readPortFlag(browserArgs, "--port"),
302
- timeoutMs: readPositiveIntegerFlag(browserArgs, "--timeout-ms")
303
- });
300
+ const modelsSourceCli = resolveOptionalFileFlag(io.cwd, browserArgs, "--source-cli");
301
+ const modelsPort = readPortFlag(browserArgs, "--port");
302
+ let listed;
303
+ try {
304
+ listed = await listChatGptModelOptions({
305
+ port: modelsPort,
306
+ timeoutMs: readPositiveIntegerFlag(browserArgs, "--timeout-ms")
307
+ });
308
+ }
309
+ catch (error) {
310
+ // Keep the next step actionable for a custom --port: the raw blocker
311
+ // suggests the default-port login command, which would not fix a
312
+ // 9444-style setup.
313
+ const blocker = sourceAwareBrowserBlocker(browserSendBlockerFromError(error), modelsSourceCli, {
314
+ ...(modelsPort !== undefined && modelsPort !== DEFAULT_CDP_PORT ? { port: modelsPort } : {})
315
+ });
316
+ throw new Error(blocker.next_step ? `${blocker.message} Next: ${blocker.next_step}` : errorMessage(error));
317
+ }
304
318
  io.stdout("Model menu options in the visible ChatGPT tab (read-only; nothing was selected):");
305
319
  for (const option of listed.options) {
306
320
  const marker = option.checked ? "*" : " ";
307
321
  const suffix = option.kind === "submenu" ? " (has sub-variants; not selectable via --model yet)" : "";
308
322
  io.stdout(`${marker} ${option.label}${suffix}`);
309
323
  }
310
- io.stdout("Use radio entries with `pro browser ask --model/--effort`; Pro sub-modes via --pro-mode 기본|확장.");
324
+ io.stdout("Use radio entries with `pro browser ask --model/--effort` (e.g. --model Pro).");
311
325
  return 0;
312
326
  }
313
327
  throw unknownSubcommandError("pro browser", browserSubcommand, ["login", "ask", "smoke", "check", "models"]);
@@ -1197,10 +1211,21 @@ export async function printProductCheck(store, io, args, configCwd = io.cwd) {
1197
1211
  catch {
1198
1212
  // config unreadable: already reported by the config line above
1199
1213
  }
1200
- const browserStatus = await getChatGptBrowserStatus({
1201
- port: resolveCdpPort(readPortFlag(args, "--port")),
1202
- timeoutMs: readPositiveIntegerFlag(args, "--timeout-ms") ?? 1500
1203
- });
1214
+ let browserStatus;
1215
+ try {
1216
+ browserStatus = await getChatGptBrowserStatus({
1217
+ port: resolveCdpPort(readPortFlag(args, "--port")),
1218
+ timeoutMs: readPositiveIntegerFlag(args, "--timeout-ms") ?? 1500
1219
+ });
1220
+ }
1221
+ catch (error) {
1222
+ // A reachable-but-broken page (e.g. a failing in-page evaluate) must show
1223
+ // as a check failure like doctor does, not crash the whole check with an
1224
+ // internal "Runtime.evaluate failed".
1225
+ io.stdout(`chatgpt: check failed - ${errorMessage(error)}`);
1226
+ io.stdout("next: Reload the ChatGPT tab in the dedicated browser (or rerun `prodex pro browser login`), then retry.");
1227
+ return false;
1228
+ }
1204
1229
  const browserCommandOptions = {
1205
1230
  cwd: setupHintCwd,
1206
1231
  port: readPortFlag(args, "--port") ?? undefined
@@ -77,6 +77,12 @@ export async function applyRepoWriteDryRun(root, store, input) {
77
77
  const current = await readWritableExistingFile(root, metadata.path);
78
78
  const currentPreimage = sha256(current.content);
79
79
  if (currentPreimage !== input.preimage_sha256) {
80
+ // A retry after a successful apply sees the file already holding the new
81
+ // content - name that instead of the generic conflict message so the
82
+ // caller can tell "already done" from "someone else changed the file".
83
+ if (currentPreimage === metadata.new_sha256) {
84
+ throw new Error(`Write for ${metadata.path} was already applied (the file already matches the reviewed content). Re-run repo_write_file_dry_run if you need a new change.`);
85
+ }
80
86
  throw new Error(`File preimage changed for ${metadata.path}`);
81
87
  }
82
88
  const newContent = await readDryRunReplacementContent(store, metadata);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",