@youdie006/prodex 0.39.2 → 0.39.3
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 +1 -6
- package/dist/chatgpt-browser.js +61 -5
- package/dist/cli-pro.js +11 -0
- package/dist/picker-interaction.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<
|
|
4
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/youdie006/prodex/main/assets/logo-wordmark-dark.png">
|
|
5
|
-
<img src="https://raw.githubusercontent.com/youdie006/prodex/main/assets/logo-wordmark.png" width="300" alt="PROdex">
|
|
6
|
-
</picture>
|
|
7
|
-
|
|
8
|
-
**Ask ChatGPT Pro from your terminal, or let Codex, Claude and other coding agents ask it for you, through the logged-in browser you already have, with a receipt for every answer.**
|
|
3
|
+
<img src="https://raw.githubusercontent.com/youdie006/prodex/main/assets/cli-banner.png" alt="prodex - ChatGPT Pro for your terminal and your coding agents, local, with receipts" width="760" />
|
|
9
4
|
|
|
10
5
|
[](https://github.com/youdie006/prodex/actions/workflows/ci.yml)
|
|
11
6
|
[](https://www.npmjs.com/package/@youdie006/prodex)
|
package/dist/chatgpt-browser.js
CHANGED
|
@@ -6,7 +6,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { captureBrowserDiagnostics, diagnosticsEnabled, diagnosticsNote } from "./browser-diagnostics.js";
|
|
8
8
|
import os from "node:os";
|
|
9
|
-
import { answeredDialogWarning, chatSurfaceState, effortNeedsWorkSurface, javascriptDialogResponse, menuKeyboardStep, readPowerSliderSelection, sliderRestoreStep, surfaceFromProbe } from "./picker-interaction.js";
|
|
9
|
+
import { answeredDialogWarning, chatSurfaceState, effortNeedsWorkSurface, javascriptDialogResponse, menuKeyboardStep, readPowerSliderSelection, sliderRestoreStep, surfaceFromProbe, sliderPressOutcome, sliderDidNotRespond } from "./picker-interaction.js";
|
|
10
10
|
export class ChatGptBrowserBlockerError extends Error {
|
|
11
11
|
blocker;
|
|
12
12
|
constructor(blocker) {
|
|
@@ -1769,6 +1769,10 @@ async function assertSelectionCommitted(cdp, label) {
|
|
|
1769
1769
|
}
|
|
1770
1770
|
// Roughly three seconds of grace for a menu that is still painting.
|
|
1771
1771
|
const POWER_SLIDER_APPEAR_ATTEMPTS = 5;
|
|
1772
|
+
// Presses the walk will wait out while the focused slider ignores them: on a
|
|
1773
|
+
// page built moments ago the key handler attaches after focus is possible.
|
|
1774
|
+
// About six seconds all told, which is more than the measured gap.
|
|
1775
|
+
const POWER_SLIDER_SWALLOWED_PRESS_RETRIES = 6;
|
|
1772
1776
|
/**
|
|
1773
1777
|
* Move the power slider until its Effort readout is the requested step. The
|
|
1774
1778
|
* menu must already be open. Returns the quota line so the caller can warn
|
|
@@ -2050,22 +2054,72 @@ async function selectPowerStep(cdp, requested) {
|
|
|
2050
2054
|
if (!state?.ok)
|
|
2051
2055
|
throw new Error(state?.reason ?? "Could not read ChatGPT's power slider");
|
|
2052
2056
|
const steps = (state.max ?? 4) - (state.min ?? 0) + 1;
|
|
2053
|
-
|
|
2057
|
+
let presses = 0;
|
|
2058
|
+
let swallowed = 0;
|
|
2059
|
+
let everMoved = false;
|
|
2060
|
+
while (presses <= steps * 2) {
|
|
2054
2061
|
if (state.effort && powerLabelMatches(requested, state.effort))
|
|
2055
2062
|
return { effort: state.effort };
|
|
2056
2063
|
// Walk upward first, then back down: the labels are ordered, but their
|
|
2057
2064
|
// exact set can change, so this never assumes a fixed index for a name.
|
|
2058
2065
|
const atTop = (state.position ?? 0) >= (state.max ?? 4);
|
|
2059
|
-
const key =
|
|
2066
|
+
const key = presses < steps && !atTop ? "ArrowRight" : "ArrowLeft";
|
|
2067
|
+
const before = state.position ?? 0;
|
|
2060
2068
|
await dispatchArrowKey(cdp, key);
|
|
2061
2069
|
await sleep(400);
|
|
2062
2070
|
state = await cdp.evaluate(powerSliderStateExpression());
|
|
2063
2071
|
if (!state?.ok)
|
|
2064
2072
|
throw new Error(state?.reason ?? "Could not read ChatGPT's power slider");
|
|
2073
|
+
const outcome = sliderPressOutcome({
|
|
2074
|
+
before,
|
|
2075
|
+
after: state.position ?? before,
|
|
2076
|
+
key,
|
|
2077
|
+
...(state.min !== undefined ? { min: state.min } : {}),
|
|
2078
|
+
...(state.max !== undefined ? { max: state.max } : {})
|
|
2079
|
+
});
|
|
2080
|
+
if (outcome === "swallowed") {
|
|
2081
|
+
// Focus succeeded but the press did nothing with room to move: the
|
|
2082
|
+
// handler is not attached yet. Two consults were blocked with "has no
|
|
2083
|
+
// Pro step. It showed: Instant, 1 of 5" after ten such presses, and a
|
|
2084
|
+
// build that treats that as the picker declining would send at Instant
|
|
2085
|
+
// instead. Wait for the handler rather than counting the press.
|
|
2086
|
+
swallowed += 1;
|
|
2087
|
+
if (swallowed <= POWER_SLIDER_SWALLOWED_PRESS_RETRIES) {
|
|
2088
|
+
await sleep(600);
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
break;
|
|
2092
|
+
}
|
|
2093
|
+
if (outcome === "moved")
|
|
2094
|
+
everMoved = true;
|
|
2095
|
+
presses += 1;
|
|
2096
|
+
}
|
|
2097
|
+
if (!everMoved) {
|
|
2098
|
+
throw new Error(`ChatGPT's power slider did not respond to arrow keys: it stayed at "${state.effort ?? "?"}" ` +
|
|
2099
|
+
`(${(state.position ?? 0) + 1} of ${(state.max ?? 4) + 1}) while the picker was still hydrating, ` +
|
|
2100
|
+
`so the "${requested}" step could not be reached.`);
|
|
2065
2101
|
}
|
|
2066
2102
|
const available = (state.lines ?? []).join(" / ");
|
|
2067
2103
|
throw new Error(`ChatGPT's model picker has no "${requested}" step. It showed: ${available}`);
|
|
2068
2104
|
}
|
|
2105
|
+
/**
|
|
2106
|
+
* The slider walk, with one more try when the first found a slider that
|
|
2107
|
+
* ignored every press: by then the handler has had seconds to attach.
|
|
2108
|
+
*/
|
|
2109
|
+
async function selectPowerStepWithGrace(cdp, requested) {
|
|
2110
|
+
try {
|
|
2111
|
+
return await selectPowerStep(cdp, requested);
|
|
2112
|
+
}
|
|
2113
|
+
catch (error) {
|
|
2114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2115
|
+
if (!sliderDidNotRespond(message))
|
|
2116
|
+
throw error;
|
|
2117
|
+
await sleep(1_500);
|
|
2118
|
+
if (!(await ensurePickerOpen(cdp)))
|
|
2119
|
+
throw error;
|
|
2120
|
+
return await selectPowerStep(cdp, requested);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2069
2123
|
async function dispatchArrowKey(cdp, key) {
|
|
2070
2124
|
const code = key;
|
|
2071
2125
|
const virtualKey = key === "ArrowLeft" ? 37 : 39;
|
|
@@ -2183,11 +2237,13 @@ async function selectModelReasoning(cdp, options, selectionWarnings = []) {
|
|
|
2183
2237
|
throw new Error("ChatGPT's model picker would not reopen after the model was chosen.");
|
|
2184
2238
|
}
|
|
2185
2239
|
try {
|
|
2186
|
-
await
|
|
2240
|
+
await selectPowerStepWithGrace(cdp, plan.sliderLabel);
|
|
2187
2241
|
}
|
|
2188
2242
|
catch (error) {
|
|
2189
2243
|
// Only "this slider has no such step" is the picker declining. A
|
|
2190
|
-
// slider that will not open, or will not move
|
|
2244
|
+
// slider that will not open, or will not move - the hydrating case,
|
|
2245
|
+
// which reads as "did not respond to arrow keys" - is a real
|
|
2246
|
+
// failure, and must not turn into a send at whatever step it was on.
|
|
2191
2247
|
const message = error instanceof Error ? error.message : String(error);
|
|
2192
2248
|
const noSuchStep = /has no "[^"]*" step/.test(message);
|
|
2193
2249
|
if (!noSuchStep)
|
package/dist/cli-pro.js
CHANGED
|
@@ -1770,6 +1770,17 @@ export function browserSendBlockerFromError(error) {
|
|
|
1770
1770
|
next_step: "Another prodex send holds the browser. Wait for it to finish and retry, or pass a longer --timeout-ms, which is also the queue budget."
|
|
1771
1771
|
};
|
|
1772
1772
|
}
|
|
1773
|
+
// The picker's slider took focus before its key handler was attached and
|
|
1774
|
+
// ignored every press. Retrying is the cure; nothing in the browser is
|
|
1775
|
+
// broken, so the generic "resolve it manually" pointed at nothing.
|
|
1776
|
+
if (/power slider did not respond to arrow keys/.test(message)) {
|
|
1777
|
+
return {
|
|
1778
|
+
code: "picker_not_responding",
|
|
1779
|
+
message,
|
|
1780
|
+
retryable: true,
|
|
1781
|
+
next_step: "ChatGPT's picker had not finished loading when prodex tried to move its slider, so the requested step was never reached and nothing was sent. Retry the send. If it repeats on a page that has been open for a while, open the picker once in the visible browser and move the slider by hand, then retry."
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1773
1784
|
// The tab's DevTools connection went away mid-send with no timeout behind
|
|
1774
1785
|
// it: the tab was closed, navigated from outside, or the browser exited.
|
|
1775
1786
|
if (/Chrome DevTools websocket (closed|is not open)/.test(message)) {
|
|
@@ -182,3 +182,25 @@ export function surfaceFromProbe(probe) {
|
|
|
182
182
|
return "Work";
|
|
183
183
|
return undefined;
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* What one arrow press on the power slider did. "swallowed" is the case the
|
|
187
|
+
* walk has to wait out: on a page built moments ago the slider takes focus
|
|
188
|
+
* before its key handler is attached, so a press with room to move does
|
|
189
|
+
* nothing - and a walk that counted those presses as steps reported a step
|
|
190
|
+
* that exists as missing ("has no Pro step. It showed: Instant, 1 of 5").
|
|
191
|
+
*/
|
|
192
|
+
export function sliderPressOutcome(input) {
|
|
193
|
+
if (input.after !== input.before)
|
|
194
|
+
return "moved";
|
|
195
|
+
const min = input.min ?? 0;
|
|
196
|
+
const max = input.max ?? 4;
|
|
197
|
+
if (input.key === "ArrowRight" && input.before >= max)
|
|
198
|
+
return "at-edge";
|
|
199
|
+
if (input.key === "ArrowLeft" && input.before <= min)
|
|
200
|
+
return "at-edge";
|
|
201
|
+
return "swallowed";
|
|
202
|
+
}
|
|
203
|
+
/** The slider-walk failure that means "hydrating", as opposed to "no such step". */
|
|
204
|
+
export function sliderDidNotRespond(message) {
|
|
205
|
+
return /power slider did not respond to arrow keys/.test(message);
|
|
206
|
+
}
|