@youdie006/prodex 0.40.18 → 0.40.19
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 +3 -1
- package/dist/cli-pro.js +74 -4
- package/dist/cli-shared.js +15 -2
- package/docs/releasing.md +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,9 @@ Finish any native browser or OS confirmation before requesting the handoff: Chro
|
|
|
77
77
|
|
|
78
78
|
A Chrome account-connection chooser is separate from ChatGPT sign-in. If prodex confirms that chooser is displayed, choose for yourself whether to connect Chrome to the account; "Use Chrome without an account" declines that separate connection. A hidden or unreadable internal target still blocks the handoff but is not proof that a prompt is visible or that ChatGPT is logged out.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
The login guide explains this separate Chrome choice before the authentication steps. Keep the ChatGPT tab open after making it. If a verified signed-in headed session reaches another authentication or protection blocker immediately after the headless handoff, prodex stops without prescribing another login cycle. Repeating login is not a demonstrated fix for that headless failure; `login: READY` before the handoff is not `background: READY` after it.
|
|
81
|
+
|
|
82
|
+
"Login readiness is not yet confirmed" does not mean the saved login was lost. Inspect the dedicated browser before signing in again. An explicit `login_required`, `cloudflare_check`, `captcha_required`, or `permission_required` during a headless/virtual-display wait stops promptly. Outside the just-verified handoff case above, `prodex pro browser login --headed --recover-visible` is available for explicit visible inspection of a running headless browser's same profile and page. This recovery refuses other tabs, active work, filled inputs, and uncertain browser identity; it never clicks a security check or sends a prompt. A usage limit or missing composer has its own next step instead of a blanket login instruction.
|
|
81
83
|
|
|
82
84
|
While Pro thinks, progress goes to stderr: connecting, prompt sent, elapsed time while generating. A Pro selection raises the send budget to twenty minutes on its own; `--timeout-ms` overrides it. Answers are read from the rendered page, so formatting can differ from the original message. If the dedicated browser is not running, an interactive `ask` starts it, waits for your saved session, and retries once (`--no-auto-login` turns that off; scripts opt in with `--auto-login`).
|
|
83
85
|
|
package/dist/cli-pro.js
CHANGED
|
@@ -392,9 +392,10 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
392
392
|
port: opened.port,
|
|
393
393
|
headless,
|
|
394
394
|
minimized,
|
|
395
|
+
// A blocked background handoff must not make a temporary auth window permanent.
|
|
395
396
|
...(alreadyRunning && savedLaunchForPort?.resume_headless === true &&
|
|
396
|
-
!Object.values(windowModeOptions.flags).some((value) => typeof value === "boolean") &&
|
|
397
|
-
|
|
397
|
+
(background || (!Object.values(windowModeOptions.flags).some((value) => typeof value === "boolean") &&
|
|
398
|
+
!["PRODEX_HEADLESS", "PRODEX_VIRTUAL_DISPLAY", "PRODEX_MINIMIZE_WINDOW"].some((key) => (process.env[key] ?? "").trim() !== "")))
|
|
398
399
|
? { resume_headless: true } : {}),
|
|
399
400
|
...(virtualDisplay
|
|
400
401
|
? { virtual_display: virtualDisplay.displayNumber }
|
|
@@ -1544,7 +1545,7 @@ export async function runAskProCommand(rest, io, beforeSend) {
|
|
|
1544
1545
|
}
|
|
1545
1546
|
}
|
|
1546
1547
|
catch (error) {
|
|
1547
|
-
const blocker = sourceAwareBrowserBlocker(browserSendBlockerFromError(error), sourceCli, browserCommandOptions);
|
|
1548
|
+
const blocker = await withManualStepForWindowMode(sourceAwareBrowserBlocker(browserSendBlockerFromError(error), sourceCli, browserCommandOptions), resolveCdpPort(browserPort), sourceCli, browserCommandOptions);
|
|
1548
1549
|
const message = blocker.next_step ? `${blocker.message} Next: ${blocker.next_step}` : errorMessage(error);
|
|
1549
1550
|
// The blocker text can quote the requested/default project name (e.g. a
|
|
1550
1551
|
// "project not found" error). Local stdout/stderr keep it (useful to the
|
|
@@ -2319,6 +2320,7 @@ async function completeBackgroundBrowserLogin(io, options) {
|
|
|
2319
2320
|
port: opened.port,
|
|
2320
2321
|
timeoutMs: options.timeoutMs,
|
|
2321
2322
|
windowMode: { headless: true, virtualDisplay: false, minimized: false },
|
|
2323
|
+
verifiedHeadedHandoff: true,
|
|
2322
2324
|
headedLoginCommand: options.headedLoginCommand,
|
|
2323
2325
|
headlessRecoveryCommand: options.headlessRecoveryCommand
|
|
2324
2326
|
});
|
|
@@ -2387,6 +2389,12 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
|
|
|
2387
2389
|
const blocker = status.blocker;
|
|
2388
2390
|
const blockerNextStep = blocker?.next_step ? ` Next: ${blocker.next_step}` : "";
|
|
2389
2391
|
if (!hasInteractiveWindow && blocker && needsVisibleAuthRecovery(blocker.code)) {
|
|
2392
|
+
if (options.verifiedHeadedHandoff) {
|
|
2393
|
+
stderr(`login: blocked - ${blocker.message}`);
|
|
2394
|
+
stderr(`login: NOT READY - ${blocker.code}. Sign-in was verified before the headless handoff, but the same profile is not ready headlessly.`);
|
|
2395
|
+
stderr("login: Another login is not a demonstrated fix. Stop automatic recovery and inspect this headless failure before another login or mode change. No further window will be opened automatically.");
|
|
2396
|
+
return false;
|
|
2397
|
+
}
|
|
2390
2398
|
const visibleStep = options.windowMode?.headless === true && options.headlessRecoveryCommand
|
|
2391
2399
|
? `run \`${options.headlessRecoveryCommand}\` to handle it visibly.`
|
|
2392
2400
|
: `${manualInspection} Handle the reported step visibly.`;
|
|
@@ -3040,6 +3048,60 @@ export async function listConsultListEntries(store, options = { readOnly: true }
|
|
|
3040
3048
|
}
|
|
3041
3049
|
return entries;
|
|
3042
3050
|
}
|
|
3051
|
+
/** Blockers whose only remedy is a person acting inside the browser window. */
|
|
3052
|
+
const MANUAL_BROWSER_STEP_CODES = new Set(["login_required", "captcha_required", "cloudflare_check"]);
|
|
3053
|
+
/**
|
|
3054
|
+
* Rewrite a "do it in the visible browser" next step for a browser that has no
|
|
3055
|
+
* window to do it in.
|
|
3056
|
+
*
|
|
3057
|
+
* Measured with a signed-out profile on a virtual display: `pro browser check`
|
|
3058
|
+
* and every send reported `login_required` with "Log in manually in the
|
|
3059
|
+
* visible browser." - on a browser prodex had itself started with no visible
|
|
3060
|
+
* window. The login command already knew the way out (close that browser, log
|
|
3061
|
+
* in through a headed one), but the blocker a send or a check prints did not.
|
|
3062
|
+
* A session that expires under a no-window setup is exactly this case, and the
|
|
3063
|
+
* advice it got could not be followed.
|
|
3064
|
+
*
|
|
3065
|
+
* Only the manual-step codes are rewritten, and only when the saved launch
|
|
3066
|
+
* belongs to the port in use, so a stale record for another browser cannot
|
|
3067
|
+
* change the advice for this one.
|
|
3068
|
+
*/
|
|
3069
|
+
export function manualBrowserStepForWindowMode(blocker, launch, port, commands) {
|
|
3070
|
+
if (!MANUAL_BROWSER_STEP_CODES.has(blocker.code))
|
|
3071
|
+
return blocker;
|
|
3072
|
+
if (!launch || launch.port !== port)
|
|
3073
|
+
return blocker;
|
|
3074
|
+
if (launch.headless === true) {
|
|
3075
|
+
return {
|
|
3076
|
+
...blocker,
|
|
3077
|
+
next_step: `The dedicated browser runs headless, with no window to do this in. Run \`${commands.visibleRecovery}\` ` +
|
|
3078
|
+
"to switch it to a visible window through the guarded path, complete the step there, then retry."
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
if (launch.virtual_display !== undefined) {
|
|
3082
|
+
return {
|
|
3083
|
+
...blocker,
|
|
3084
|
+
next_step: "The dedicated browser runs on a virtual display, with no window to do this in. Close it yourself when no " +
|
|
3085
|
+
`consult is running, then run \`${commands.headedLogin}\` to complete the step in a visible window, and retry.`
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
return blocker;
|
|
3089
|
+
}
|
|
3090
|
+
/** The same rewrite, with the saved launch record and the commands resolved for this call. */
|
|
3091
|
+
async function withManualStepForWindowMode(blocker, port, sourceCli, commandOptions) {
|
|
3092
|
+
if (!MANUAL_BROWSER_STEP_CODES.has(blocker.code))
|
|
3093
|
+
return blocker;
|
|
3094
|
+
const launch = await readLastBrowserLoginLaunch().catch(() => undefined);
|
|
3095
|
+
const options = {
|
|
3096
|
+
...commandOptions,
|
|
3097
|
+
port,
|
|
3098
|
+
...(launch?.profile_dir ? { profileDir: launch.profile_dir } : {})
|
|
3099
|
+
};
|
|
3100
|
+
return manualBrowserStepForWindowMode(blocker, launch, port, {
|
|
3101
|
+
headedLogin: formatHeadedBrowserLoginCommand(sourceCli, options),
|
|
3102
|
+
visibleRecovery: formatVisibleAuthRecoveryCommand(sourceCli, options)
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3043
3105
|
function formatHeadedBrowserLoginCommand(sourceCli, options = {}) {
|
|
3044
3106
|
return `${formatBrowserLoginCommand(sourceCli, options)} --headed`;
|
|
3045
3107
|
}
|
|
@@ -3100,6 +3162,9 @@ export function printBrowserLoginGuide(stdout, input) {
|
|
|
3100
3162
|
return;
|
|
3101
3163
|
}
|
|
3102
3164
|
stdout("");
|
|
3165
|
+
stdout("Chrome account connection is separate from ChatGPT sign-in. If Chrome asks, choose whether to connect the browser account yourself.");
|
|
3166
|
+
stdout("'Use Chrome without an account' declines that browser connection, not your ChatGPT login. Keep the ChatGPT tab open after making that choice.");
|
|
3167
|
+
stdout("");
|
|
3103
3168
|
stdout("Steps:");
|
|
3104
3169
|
if (windowAvailable) {
|
|
3105
3170
|
stdout(input.reusedProfile || input.reused === true
|
|
@@ -3261,7 +3326,12 @@ export async function printProductCheck(store, io, args, configCwd = io.cwd) {
|
|
|
3261
3326
|
else if (browserStatus.blocker) {
|
|
3262
3327
|
const visibilityText = browserStatus.blocker.code === "tab_not_visible" ? ` visibility=${browserStatus.visibilityState ?? "unknown"}` : "";
|
|
3263
3328
|
io.stdout(`chatgpt: blocked ${browserStatus.blocker.code}${visibilityText} - ${browserStatus.blocker.message}`);
|
|
3264
|
-
const
|
|
3329
|
+
const modeAware = await withManualStepForWindowMode(browserStatus.blocker, checkPort, sourceCli, browserCommandOptions);
|
|
3330
|
+
// A rewritten step already carries fully formed commands; running it back
|
|
3331
|
+
// through the product-check rewrite would only re-derive them.
|
|
3332
|
+
const nextStep = modeAware === browserStatus.blocker
|
|
3333
|
+
? productCheckBrowserNextStep(browserStatus.blocker.next_step, sourceCli, browserCommandOptions)
|
|
3334
|
+
: modeAware.next_step;
|
|
3265
3335
|
if (nextStep)
|
|
3266
3336
|
io.stdout(`next: ${nextStep}`);
|
|
3267
3337
|
}
|
package/dist/cli-shared.js
CHANGED
|
@@ -88,10 +88,10 @@ export function sourceAwareBrowserNextStep(nextStep, sourceCli, options = {}) {
|
|
|
88
88
|
return `\`cd ${cwdPrefix} && ${formatBrowserAskCommandBody(sourceCli)}${storedArgs ?? ""}\``;
|
|
89
89
|
})
|
|
90
90
|
.replace(/`prodex pro browser login([^`]*)?`/g, (_match, storedArgs) => {
|
|
91
|
-
return `\`${formatBrowserLoginCommand(sourceCli, browserOptionsWithStoredPort(options, storedArgs))}\``;
|
|
91
|
+
return `\`${formatBrowserLoginCommand(sourceCli, browserOptionsWithStoredPort(options, storedArgs))}${flagsBesidesPort(storedArgs)}\``;
|
|
92
92
|
})
|
|
93
93
|
.replace(/`prodex pro browser smoke([^`]*)?`/g, (_match, storedArgs) => {
|
|
94
|
-
return `\`${formatBrowserSmokeCommand(sourceCli, browserOptionsWithStoredPort(options, storedArgs))}\``;
|
|
94
|
+
return `\`${formatBrowserSmokeCommand(sourceCli, browserOptionsWithStoredPort(options, storedArgs))}${flagsBesidesPort(storedArgs)}\``;
|
|
95
95
|
})
|
|
96
96
|
.replace(/`prodex pro browser ask([^`]*)?`/g, (_match, storedArgs) => {
|
|
97
97
|
return `\`${formatBrowserAskCommandBody(sourceCli)}${storedArgs ?? ""}\``;
|
|
@@ -131,6 +131,19 @@ export function isUntrustedResultError(error) {
|
|
|
131
131
|
export function formatCommandInCwd(command, cwd) {
|
|
132
132
|
return cwd ? `cd ${shellQuote(cwd)} && ${command}` : command;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The flags of a quoted command other than --port, which the rewrite rebuilds
|
|
136
|
+
* itself. They used to be dropped: a next step saying "run `prodex pro browser
|
|
137
|
+
* login --background`" came out as a plain `login`, and "--headed" vanished from
|
|
138
|
+
* the step that exists to get a window - so following the advice relaunched the
|
|
139
|
+
* browser in the very mode it was telling the caller to leave.
|
|
140
|
+
*/
|
|
141
|
+
export function flagsBesidesPort(storedArgs) {
|
|
142
|
+
if (!storedArgs)
|
|
143
|
+
return "";
|
|
144
|
+
const rest = storedArgs.replace(/(?:^|\s)--port\s+\d{1,5}(?=\s|$)/, " ").replace(/\s+/g, " ").trim();
|
|
145
|
+
return rest ? ` ${rest}` : "";
|
|
146
|
+
}
|
|
134
147
|
export function browserOptionsWithStoredPort(options, storedArgs) {
|
|
135
148
|
if (options.port || !storedArgs)
|
|
136
149
|
return options;
|
package/docs/releasing.md
CHANGED
|
@@ -16,6 +16,28 @@ Release target: `v0.40.13` on `main`. The GitHub Release records the resolved co
|
|
|
16
16
|
|
|
17
17
|
The release adds a guarded, opt-in transition and more accurate blocker reporting. It does not claim that headless Pro consultation is verified on either deployment target or that authentication can be retained indefinitely.
|
|
18
18
|
|
|
19
|
+
## 0.40.2 - 0.40.7 Release Record
|
|
20
|
+
|
|
21
|
+
Recorded 2026-09-29, after the fact. These versions went around the tag-and-CI path, so none of them has a git tag, a GitHub Release or npm provenance; this section is their durable record. The commit for each published version was matched to the npm publish time and cross-checked by downloading the published package and finding that version's fixes in its `dist`.
|
|
22
|
+
|
|
23
|
+
**Channel.** Built with `prodex release pack` (normalized tarball) and published by hand with `npm publish <tarball>` from WSL, because a source-tree publish is refused on the WSL mount's file modes. That route bypasses `publish.yml`, so there is no provenance attestation (0.40.1 and 0.40.8 have one; 0.40.2 - 0.40.6 do not), no tag and no Release. Tags were not backfilled: pushing a `v*` tag runs `publish.yml`, which would re-verify old code and then fail at `npm publish` because the version already exists.
|
|
24
|
+
|
|
25
|
+
| Version | Commit | npm publish (UTC) | Verification before publish | Live check |
|
|
26
|
+
|---|---|---|---|---|
|
|
27
|
+
| 0.40.2 | `003d4d7` | 2026-09-09 17:39 | vitest 1126 pass; `release:verify` ok | Defective: installed from the registry on 2026-09-10 and sent into a real project - refused `project_not_bound`. Every project send fails in this version (see CHANGELOG). |
|
|
28
|
+
| 0.40.3 | `6f36b47` | 2026-09-10 07:27 | vitest 1184 pass; `release:verify` ok | Registry package sent into an existing project, across projects, and with `--project-new`; `destination.verified: true` on the receipt. |
|
|
29
|
+
| 0.40.4 | `2a84bc5` | 2026-09-11 00:21 | vitest 1198 pass; `release:verify` ok | `--continue` with the tab deliberately moved elsewhere, over CLI and over MCP `pro_consult` (`continue_thread`). |
|
|
30
|
+
| 0.40.5 | `a9c629d` | 2026-09-14 00:43 | vitest 1248 pass; `release:verify` ok; tarball `publish --dry-run` ok | create-image and web-search sends; back-to-back sends queued on the lock 3/3. |
|
|
31
|
+
| 0.40.6 | `4550cad` | 2026-09-14 01:52 | vitest 1259 pass; `release:verify` ok; tarball `publish --dry-run` ok | Dedicated browser killed twice; `--auto-login` relaunched it in about 3 s and the receipt carried `browser_recovered`. |
|
|
32
|
+
| 0.40.7 | `678a02e` (tag only) | not published | `publish.yml` run 34816390439 failed at Release verification; logs no longer retrievable | Superseded by 0.40.8. |
|
|
33
|
+
|
|
34
|
+
**Installation.** Global npm installs, verified with `prodex --version` on each host after install:
|
|
35
|
+
|
|
36
|
+
- 0.40.3, 0.40.4, 0.40.5, 0.40.6: WSL, M3 and the Mac mini, each upgraded on its publish day (the Mac mini came from 0.37.0 at 0.40.3). M4 was unreachable over SSH every time and was never updated.
|
|
37
|
+
- Running services are separate from the install. After 0.40.3 and 0.40.4 the long-lived stdio MCP servers (13 on WSL, 8 on M3) kept the old code. A freshly spawned server reported `serverInfo.version` 0.40.4 and completed a `continue_thread` consult. On 2026-09-14, before 0.40.5, every prodex MCP server was stopped with SIGTERM (20 on WSL, 7 on M3) with no consult in flight; the servers the clients respawned came up on 0.40.4, the install at that moment, and needed another reconnect after the 0.40.5 and 0.40.6 installs.
|
|
38
|
+
- State on 2026-09-29: WSL 0.40.18, M3 0.40.18, Mac mini still 0.40.6 (not upgraded since), M4 unreachable (its hostname no longer resolves).
|
|
39
|
+
- 2026-09-29: Mac mini upgraded 0.40.6 -> 0.40.18 with `npm i -g @youdie006/prodex@0.40.18`; `prodex --version` reports 0.40.18. No prodex MCP server was running there, so nothing needed a restart; the next one a client starts runs 0.40.18.
|
|
40
|
+
|
|
19
41
|
## Publishing
|
|
20
42
|
|
|
21
43
|
Publishing to npm runs entirely in CI with **no long-lived token** — auth is npm [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC), so nothing needs to store or paste an `NPM_TOKEN`, and every release carries a verifiable `--provenance` attestation.
|