codex-multi-auth 2.6.0 → 2.7.0

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.
@@ -17,6 +17,10 @@ export declare function isBrowserLaunchSuppressed(): boolean;
17
17
  * OAuth codes) from `url` before calling. Invocations are not atomic—concurrent calls may race but are
18
18
  * safe to perform.
19
19
  *
20
+ * Under WSL the browser lives on the Windows host, so `xdg-open` is the wrong tool and is usually not
21
+ * even installed. Prefer `wslview` (wslu), then Windows PowerShell via interop, before falling back to
22
+ * the Linux opener.
23
+ *
20
24
  * @param url - The URL to open; redact sensitive tokens (e.g., OAuth codes) before passing.
21
25
  * @returns `true` if a browser launch was attempted, `false` if no suitable opener was available or an exception occurred.
22
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../lib/auth/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAQnD;AA6DD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAwCnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmDzD"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../lib/auth/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAQnD;AAqGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAyCnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA8DzD"}
@@ -6,6 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import { PLATFORM_OPENERS } from "../constants.js";
9
+ import { isWsl } from "../wsl.js";
9
10
  const BROWSER_DISABLED_VALUES = new Set(["0", "false", "no", "off", "none"]);
10
11
  const NO_BROWSER_TRUTHY_VALUES = new Set(["1", "true", "yes", "on"]);
11
12
  /**
@@ -89,6 +90,40 @@ function commandExists(command) {
89
90
  }
90
91
  return false;
91
92
  }
93
+ /**
94
+ * Escape a value for safe interpolation into a double-quoted PowerShell string.
95
+ *
96
+ * Neutralizes backticks, `$` sub-expression injection, and embedded double quotes.
97
+ */
98
+ function escapeForPowerShell(value) {
99
+ return value.replace(/`/g, "``").replace(/\$/g, "`$").replace(/"/g, '""');
100
+ }
101
+ /**
102
+ * Run a PowerShell command, fire-and-forget.
103
+ *
104
+ * Reachable from Windows and from WSL, where `powershell.exe` resolves through
105
+ * Windows/Linux PATH interop.
106
+ *
107
+ * @param command - A PowerShell command string; interpolated values must already be escaped.
108
+ * @param stdinText - Optional text piped to the command's stdin.
109
+ * @returns `true` if the spawn was attempted, `false` if PowerShell is unavailable.
110
+ */
111
+ function spawnPowerShell(command, stdinText) {
112
+ if (!commandExists("powershell.exe")) {
113
+ return false;
114
+ }
115
+ const child = spawn("powershell.exe", ["-NoLogo", "-NoProfile", "-Command", command], {
116
+ stdio: stdinText === undefined ? "ignore" : ["pipe", "ignore", "ignore"],
117
+ });
118
+ child.on("error", () => { });
119
+ if (stdinText !== undefined) {
120
+ // A child that dies before draining stdin emits EPIPE on the stream, not
121
+ // on the child. Unhandled, that is an async throw no try/catch can catch.
122
+ child.stdin?.on("error", () => { });
123
+ child.stdin?.end(stdinText);
124
+ }
125
+ return true;
126
+ }
92
127
  /**
93
128
  * Launches the user's default browser to open the provided URL using a platform-appropriate command.
94
129
  *
@@ -98,6 +133,10 @@ function commandExists(command) {
98
133
  * OAuth codes) from `url` before calling. Invocations are not atomic—concurrent calls may race but are
99
134
  * safe to perform.
100
135
  *
136
+ * Under WSL the browser lives on the Windows host, so `xdg-open` is the wrong tool and is usually not
137
+ * even installed. Prefer `wslview` (wslu), then Windows PowerShell via interop, before falling back to
138
+ * the Linux opener.
139
+ *
101
140
  * @param url - The URL to open; redact sensitive tokens (e.g., OAuth codes) before passing.
102
141
  * @returns `true` if a browser launch was attempted, `false` if no suitable opener was available or an exception occurred.
103
142
  */
@@ -108,17 +147,22 @@ export function openBrowserUrl(url) {
108
147
  }
109
148
  // Windows: use PowerShell Start-Process to avoid cmd/start quirks with URLs containing '&' or ':'
110
149
  if (process.platform === "win32") {
111
- if (!commandExists("powershell.exe")) {
112
- return false;
150
+ return spawnPowerShell(`Start-Process "${escapeForPowerShell(url)}"`);
151
+ }
152
+ if (isWsl()) {
153
+ // wslu's browser shim: hands the URL to the Windows default browser.
154
+ if (commandExists("wslview")) {
155
+ const child = spawn("wslview", [url], {
156
+ stdio: "ignore",
157
+ shell: false,
158
+ });
159
+ child.on("error", () => { });
160
+ return true;
113
161
  }
114
- // Escape PowerShell special characters: backticks, double quotes, and $ (sub-expression injection)
115
- const psUrl = url
116
- .replace(/`/g, "``")
117
- .replace(/\$/g, "`$")
118
- .replace(/"/g, '""');
119
- const child = spawn("powershell.exe", ["-NoLogo", "-NoProfile", "-Command", `Start-Process "${psUrl}"`], { stdio: "ignore" });
120
- child.on("error", () => { });
121
- return true;
162
+ if (spawnPowerShell(`Start-Process "${escapeForPowerShell(url)}"`)) {
163
+ return true;
164
+ }
165
+ // Fall through: an X/Wayland browser inside the distro is still possible.
122
166
  }
123
167
  const opener = getBrowserOpener();
124
168
  if (!commandExists(opener)) {
@@ -151,16 +195,24 @@ export function copyTextToClipboard(text) {
151
195
  if (!text)
152
196
  return false;
153
197
  if (process.platform === "win32") {
154
- if (!commandExists("powershell.exe")) {
155
- return false;
198
+ return spawnPowerShell(`Set-Clipboard -Value "${escapeForPowerShell(text)}"`);
199
+ }
200
+ if (isWsl()) {
201
+ // The clipboard the user pastes from is the Windows one, not the distro's.
202
+ if (commandExists("clip.exe")) {
203
+ const child = spawn("clip.exe", [], {
204
+ stdio: ["pipe", "ignore", "ignore"],
205
+ shell: false,
206
+ });
207
+ child.on("error", () => { });
208
+ child.stdin?.on("error", () => { });
209
+ child.stdin?.end(text);
210
+ return true;
156
211
  }
157
- const psText = text
158
- .replace(/`/g, "``")
159
- .replace(/\$/g, "`$")
160
- .replace(/"/g, '""');
161
- const child = spawn("powershell.exe", ["-NoLogo", "-NoProfile", "-Command", `Set-Clipboard -Value "${psText}"`], { stdio: "ignore" });
162
- child.on("error", () => { });
163
- return true;
212
+ if (spawnPowerShell(`Set-Clipboard -Value "${escapeForPowerShell(text)}"`)) {
213
+ return true;
214
+ }
215
+ // Fall through to the Linux clipboard tools.
164
216
  }
165
217
  if (process.platform === "darwin") {
166
218
  if (!commandExists("pbcopy"))
@@ -170,6 +222,7 @@ export function copyTextToClipboard(text) {
170
222
  shell: false,
171
223
  });
172
224
  child.on("error", () => { });
225
+ child.stdin?.on("error", () => { });
173
226
  child.stdin?.end(text);
174
227
  return true;
175
228
  }
@@ -186,6 +239,7 @@ export function copyTextToClipboard(text) {
186
239
  shell: false,
187
240
  });
188
241
  child.on("error", () => { });
242
+ child.stdin?.on("error", () => { });
189
243
  child.stdin?.end(text);
190
244
  return true;
191
245
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Operator-facing guidance for OAuth callback failures on the fixed callback port.
3
+ *
4
+ * The redirect URI is registered with the provider, so the port cannot be
5
+ * negotiated away when it is contended. The best the CLI can do is name the
6
+ * likely conflict and point at a flow that does not need the port at all.
7
+ *
8
+ * Windows and WSL contend for that port as far as a browser running on the
9
+ * Windows host is concerned, so a listener on either side can swallow a callback
10
+ * intended for the other. From inside the distro this is invisible: the WSL
11
+ * listener binds cleanly and simply never receives the redirect.
12
+ */
13
+ /**
14
+ * Why the browser callback did not produce an authorization code.
15
+ *
16
+ * - `bind-failed`: the local listener could not take the callback port at all.
17
+ * - `callback-timeout`: the listener bound, but no redirect ever arrived.
18
+ */
19
+ export type CallbackFailureReason = "bind-failed" | "callback-timeout";
20
+ /** @internal */
21
+ export type CallbackFailureContext = {
22
+ /** `errno` code from a failed listen, when one is known. */
23
+ bindErrorCode?: string;
24
+ };
25
+ /**
26
+ * Build the lines shown to the user when a browser OAuth callback fails.
27
+ *
28
+ * Contention is only *asserted* when it was actually observed — that is, the
29
+ * listen failed with `EADDRINUSE`. A callback that never arrives is far more
30
+ * often a cancelled or abandoned sign-in than a stolen redirect, so that case
31
+ * is phrased as a conditional ("if you completed sign-in and still landed
32
+ * here") rather than a diagnosis.
33
+ *
34
+ * @param reason - Which half of the callback flow broke.
35
+ * @param context - Extra detail from the failed bind, when available.
36
+ * @returns Guidance lines. Empty strings are intentional blank separators.
37
+ */
38
+ export declare function describeCallbackFailure(reason: CallbackFailureReason, context?: CallbackFailureContext): string[];
39
+ //# sourceMappingURL=callback-guidance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-guidance.d.ts","sourceRoot":"","sources":["../../../lib/auth/callback-guidance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAEvE,gBAAgB;AAChB,MAAM,MAAM,sBAAsB,GAAG;IACpC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA4CF;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,sBAA2B,GAClC,MAAM,EAAE,CA0CV"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Operator-facing guidance for OAuth callback failures on the fixed callback port.
3
+ *
4
+ * The redirect URI is registered with the provider, so the port cannot be
5
+ * negotiated away when it is contended. The best the CLI can do is name the
6
+ * likely conflict and point at a flow that does not need the port at all.
7
+ *
8
+ * Windows and WSL contend for that port as far as a browser running on the
9
+ * Windows host is concerned, so a listener on either side can swallow a callback
10
+ * intended for the other. From inside the distro this is invisible: the WSL
11
+ * listener binds cleanly and simply never receives the redirect.
12
+ */
13
+ import { AUTH_REDIRECT } from "./auth.js";
14
+ import { getWslDistroName, isWsl } from "../wsl.js";
15
+ const INSPECT_WINDOWS = ` Windows (PowerShell): Get-NetTCPConnection -LocalPort ${AUTH_REDIRECT.port}`;
16
+ const INSPECT_LINUX = ` Linux / WSL: ss -lptn 'sport = :${AUTH_REDIRECT.port}'`;
17
+ const INSPECT_DARWIN = ` macOS: lsof -nP -iTCP:${AUTH_REDIRECT.port} -sTCP:LISTEN`;
18
+ const USE_DEVICE_AUTH = " Or sign in without the callback port: codex-multi-auth login --device-auth";
19
+ /**
20
+ * The commands that would reveal a listener on the callback port here.
21
+ *
22
+ * Inside WSL both sides of the boundary are worth checking, because the
23
+ * offending listener is usually the one on the Windows host.
24
+ */
25
+ function inspectCommands() {
26
+ if (isWsl())
27
+ return [INSPECT_WINDOWS, INSPECT_LINUX];
28
+ if (process.platform === "win32")
29
+ return [INSPECT_WINDOWS];
30
+ if (process.platform === "darwin")
31
+ return [INSPECT_DARWIN];
32
+ return [INSPECT_LINUX];
33
+ }
34
+ /**
35
+ * Why a listener on the other side of the Windows/WSL boundary may be at fault.
36
+ *
37
+ * @returns Explanatory lines, or an empty array when no WSL boundary is in play.
38
+ */
39
+ function crossBoundaryNote() {
40
+ if (isWsl()) {
41
+ const distro = getWslDistroName();
42
+ const where = distro ? `WSL (${distro})` : "WSL";
43
+ return [
44
+ `You are running inside ${where}, but the browser opens on the Windows host.`,
45
+ `Windows and ${where} contend for localhost:${AUTH_REDIRECT.port}, so a codex-multi-auth`,
46
+ "or Codex login or proxy on the Windows side can take the callback meant for this one.",
47
+ ];
48
+ }
49
+ if (process.platform === "win32") {
50
+ return [
51
+ `A codex-multi-auth login or proxy running inside WSL can also hold port ${AUTH_REDIRECT.port}.`,
52
+ ];
53
+ }
54
+ return [];
55
+ }
56
+ /**
57
+ * Build the lines shown to the user when a browser OAuth callback fails.
58
+ *
59
+ * Contention is only *asserted* when it was actually observed — that is, the
60
+ * listen failed with `EADDRINUSE`. A callback that never arrives is far more
61
+ * often a cancelled or abandoned sign-in than a stolen redirect, so that case
62
+ * is phrased as a conditional ("if you completed sign-in and still landed
63
+ * here") rather than a diagnosis.
64
+ *
65
+ * @param reason - Which half of the callback flow broke.
66
+ * @param context - Extra detail from the failed bind, when available.
67
+ * @returns Guidance lines. Empty strings are intentional blank separators.
68
+ */
69
+ export function describeCallbackFailure(reason, context = {}) {
70
+ const port = AUTH_REDIRECT.port;
71
+ if (reason === "bind-failed") {
72
+ // A failed listen is hard evidence: something is on the port right now.
73
+ if (context.bindErrorCode === "EADDRINUSE") {
74
+ return [
75
+ `Could not listen on port ${port} for the OAuth callback — another process already holds it.`,
76
+ ...crossBoundaryNote(),
77
+ "",
78
+ `Find the listener:`,
79
+ ...inspectCommands(),
80
+ "",
81
+ "Close it, then retry.",
82
+ USE_DEVICE_AUTH,
83
+ ];
84
+ }
85
+ return [
86
+ `Could not listen on port ${port} for the OAuth callback${context.bindErrorCode ? ` (${context.bindErrorCode})` : ""}.`,
87
+ "",
88
+ "The callback port is fixed by the provider and cannot be changed.",
89
+ USE_DEVICE_AUTH,
90
+ ];
91
+ }
92
+ // The listener bound cleanly and nothing arrived. Usually the sign-in was
93
+ // simply cancelled or abandoned — say so first, and do not misdiagnose it.
94
+ return [
95
+ `No OAuth callback arrived on port ${port} before the sign-in window closed.`,
96
+ "",
97
+ "If you closed or cancelled the browser sign-in, just run login again.",
98
+ "",
99
+ "If you completed sign-in in the browser and still landed here, something else",
100
+ `may have taken the callback on port ${port}:`,
101
+ ...crossBoundaryNote(),
102
+ ...inspectCommands(),
103
+ "",
104
+ USE_DEVICE_AUTH,
105
+ ];
106
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAWnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,EACrC,KAAK,GACL,EAAE;IACF,KAAK,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,eAAe,CAAC,CAgH3B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAWnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,EACrC,KAAK,GACL,EAAE;IACF,KAAK,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,eAAe,CAAC,CAiH3B"}
@@ -112,6 +112,7 @@ export function startLocalOAuthServer({ state, }) {
112
112
  resolve({
113
113
  port: AUTH_REDIRECT.port,
114
114
  ready: false,
115
+ bindErrorCode: err?.code,
115
116
  close: () => {
116
117
  pollAborted = true;
117
118
  try {
@@ -2,8 +2,25 @@ import type { DashboardDisplaySettings } from "../../dashboard-settings.js";
2
2
  import type { QuotaCacheEntry } from "../../quota-cache.js";
3
3
  import { type CodexQuotaSnapshot, fetchCodexQuotaSnapshot } from "../../quota-probe.js";
4
4
  export declare function styleQuotaSummary(summary: string): string;
5
- export declare function formatQuotaSnapshotForDashboard(snapshot: Awaited<ReturnType<typeof fetchCodexQuotaSnapshot>>, settings: DashboardDisplaySettings): string;
5
+ /**
6
+ * Render the per-account health line printed by `codex-multi-auth check`.
7
+ *
8
+ * `check` is the only caller, and unlike the dashboard rows and account menu it
9
+ * prints one account per line with no width pressure, so it opts into the
10
+ * absolute reset clock for each quota window.
11
+ */
12
+ export declare function formatQuotaSnapshotForDashboard(snapshot: Awaited<ReturnType<typeof fetchCodexQuotaSnapshot>>, settings: DashboardDisplaySettings, now?: number): string;
6
13
  export declare function quotaCacheEntryToSnapshot(entry: QuotaCacheEntry): CodexQuotaSnapshot;
7
- export declare function formatCompactQuotaSnapshot(snapshot: CodexQuotaSnapshot, now?: number): string;
8
- export declare function formatAccountQuotaSummary(entry: QuotaCacheEntry, now?: number): string;
14
+ /**
15
+ * Options shared by the compact quota formatters.
16
+ *
17
+ * `showReset` is opt-in so that space-constrained surfaces (dashboard rows, the
18
+ * account menu, forecast lines) keep their existing single-line width, while
19
+ * `codex-multi-auth check` can append the absolute reset time.
20
+ */
21
+ export interface CompactQuotaFormatOptions {
22
+ showReset?: boolean;
23
+ }
24
+ export declare function formatCompactQuotaSnapshot(snapshot: CodexQuotaSnapshot, now?: number, options?: CompactQuotaFormatOptions): string;
25
+ export declare function formatAccountQuotaSummary(entry: QuotaCacheEntry, now?: number, options?: CompactQuotaFormatOptions): string;
9
26
  //# sourceMappingURL=quota-formatters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"quota-formatters.d.ts","sourceRoot":"","sources":["../../../../lib/codex-manager/formatters/quota-formatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACN,KAAK,kBAAkB,EACvB,uBAAuB,EAEvB,MAAM,sBAAsB,CAAC;AAY9B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA8BzD;AAED,wBAAgB,+BAA+B,CAC9C,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,EAC7D,QAAQ,EAAE,wBAAwB,GAChC,MAAM,CAGR;AAED,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,eAAe,GACpB,kBAAkB,CAgBpB;AAyBD,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,SAAa,GACd,MAAM,CAuBR;AAED,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,eAAe,EACtB,GAAG,SAAa,GACd,MAAM,CAuBR"}
1
+ {"version":3,"file":"quota-formatters.d.ts","sourceRoot":"","sources":["../../../../lib/codex-manager/formatters/quota-formatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACN,KAAK,kBAAkB,EACvB,uBAAuB,EAGvB,MAAM,sBAAsB,CAAC;AAY9B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmCzD;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC9C,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,EAC7D,QAAQ,EAAE,wBAAwB,EAClC,GAAG,SAAa,GACd,MAAM,CAGR;AAED,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,eAAe,GACpB,kBAAkB,CAgBpB;AAaD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,SAAa,EAChB,OAAO,GAAE,yBAA8B,GACrC,MAAM,CA6BR;AAED,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,eAAe,EACtB,GAAG,SAAa,EAChB,OAAO,GAAE,yBAA8B,GACrC,MAAM,CA6BR"}
@@ -1,4 +1,4 @@
1
- import { formatQuotaSnapshotLine, } from "../../quota-probe.js";
1
+ import { formatQuotaResetAt, formatQuotaSnapshotLine, } from "../../quota-probe.js";
2
2
  import { isQuotaCacheEntryExhausted, quotaLeftPercentFromUsed, } from "../../quota-readiness.js";
3
3
  import { quotaToneFromLeftPercent } from "../../ui/format.js";
4
4
  import { collapseWhitespace, joinStyledSegments, stylePromptText, } from "./text-style.js";
@@ -16,7 +16,7 @@ export function styleQuotaSummary(summary) {
16
16
  if (/rate-limited/i.test(segment)) {
17
17
  return stylePromptText(segment, "danger");
18
18
  }
19
- const match = segment.match(/^([0-9a-zA-Z]+)\s+(\d{1,3})%$/);
19
+ const match = segment.match(/^([0-9a-zA-Z]+)\s+(\d{1,3})%(,\s*resets\s+\S.*)?$/);
20
20
  if (!match) {
21
21
  return stylePromptText(segment, "muted");
22
22
  }
@@ -26,14 +26,24 @@ export function styleQuotaSummary(summary) {
26
26
  return stylePromptText(segment, "muted");
27
27
  }
28
28
  const tone = quotaToneFromLeftPercent(leftPercent);
29
- return `${stylePromptText(windowLabel, "muted")} ${stylePromptText(`${leftPercent}%`, tone)}`;
29
+ const resetSuffix = match[3]
30
+ ? stylePromptText(match[3], "muted")
31
+ : "";
32
+ return `${stylePromptText(windowLabel, "muted")} ${stylePromptText(`${leftPercent}%`, tone)}${resetSuffix}`;
30
33
  });
31
34
  return joinStyledSegments(rendered);
32
35
  }
33
- export function formatQuotaSnapshotForDashboard(snapshot, settings) {
36
+ /**
37
+ * Render the per-account health line printed by `codex-multi-auth check`.
38
+ *
39
+ * `check` is the only caller, and unlike the dashboard rows and account menu it
40
+ * prints one account per line with no width pressure, so it opts into the
41
+ * absolute reset clock for each quota window.
42
+ */
43
+ export function formatQuotaSnapshotForDashboard(snapshot, settings, now = Date.now()) {
34
44
  if (!settings.showQuotaDetails)
35
45
  return "live session OK";
36
- return `live session OK (${formatCompactQuotaSnapshot(snapshot)})`;
46
+ return `live session OK (${formatCompactQuotaSnapshot(snapshot, now, { showReset: true })})`;
37
47
  }
38
48
  export function quotaCacheEntryToSnapshot(entry) {
39
49
  return {
@@ -62,18 +72,23 @@ function formatCompactQuotaWindowLabel(windowMinutes) {
62
72
  return `${windowMinutes / 60}h`;
63
73
  return `${windowMinutes}m`;
64
74
  }
65
- function formatCompactQuotaPart(windowMinutes, usedPercent) {
75
+ function formatCompactQuotaPart(windowMinutes, usedPercent, resetAtMs, options, now) {
66
76
  const label = formatCompactQuotaWindowLabel(windowMinutes);
67
77
  if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) {
68
78
  return null;
69
79
  }
70
80
  const left = quotaLeftPercentFromUsed(usedPercent);
71
- return `${label} ${left}%`;
81
+ const part = `${label} ${left}%`;
82
+ if (!options.showReset)
83
+ return part;
84
+ // A missing or malformed reset timestamp must never drop the percentage.
85
+ const reset = formatQuotaResetAt(resetAtMs, now);
86
+ return reset ? `${part}, resets ${reset}` : part;
72
87
  }
73
- export function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
88
+ export function formatCompactQuotaSnapshot(snapshot, now = Date.now(), options = {}) {
74
89
  const parts = [
75
- formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent),
76
- formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent),
90
+ formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent, snapshot.primary.resetAtMs, options, now),
91
+ formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent, snapshot.secondary.resetAtMs, options, now),
77
92
  ].filter((value) => typeof value === "string" && value.length > 0);
78
93
  if (snapshot.status === 429) {
79
94
  parts.push("rate-limited");
@@ -86,10 +101,10 @@ export function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
86
101
  }
87
102
  return formatQuotaSnapshotLine(snapshot);
88
103
  }
89
- export function formatAccountQuotaSummary(entry, now = Date.now()) {
104
+ export function formatAccountQuotaSummary(entry, now = Date.now(), options = {}) {
90
105
  const parts = [
91
- formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent),
92
- formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent),
106
+ formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent, entry.primary.resetAtMs, options, now),
107
+ formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent, entry.secondary.resetAtMs, options, now),
93
108
  ].filter((value) => typeof value === "string" && value.length > 0);
94
109
  if (entry.status === 429) {
95
110
  parts.push("rate-limited");
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAkDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAgEtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAsDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAgEtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
@@ -6,46 +6,50 @@ export function printUsage() {
6
6
  "",
7
7
  "Start here:",
8
8
  " codex-multi-auth login [--device-auth|--manual|--no-browser] [--org <org_id>]",
9
- " codex-multi-auth status",
10
- " codex-multi-auth check",
9
+ " codex-multi-auth status [--json] (list is the same command)",
10
+ " codex-multi-auth check (always live-probes)",
11
11
  "",
12
12
  "Daily use:",
13
- " codex-multi-auth list",
13
+ " codex-multi-auth list [--json]",
14
14
  " codex-multi-auth switch <index> (pins the account for runtime routing)",
15
15
  " codex-multi-auth unpin (clears the manual pin set by switch)",
16
16
  " codex-multi-auth workspace <account> [workspace] (list or switch an account's workspaces)",
17
17
  " codex-multi-auth best [--live] [--json] [--model <model>] (clears any manual pin set by switch)",
18
- " codex-multi-auth forecast [--live] [--json] [--model <model>]",
19
- " codex-multi-auth account tag|untag|weight|pause|unpause|drain|undrain|note ...",
18
+ " codex-multi-auth forecast [--live] [--json] [--explain] [--model <model>] [--no-runtime-overlay]",
19
+ " codex-multi-auth account tag|untag|weight|pause|unpause|drain|undrain|note|policy list ...",
20
20
  "",
21
21
  "Repair:",
22
22
  " codex-multi-auth uninstall [--dry-run] [--json] [--clear-accounts]",
23
- " codex-multi-auth verify-flagged [--dry-run] [--json] [--no-restore]",
23
+ " codex-multi-auth verify-flagged [--dry-run|-n] [--json] [--no-restore]",
24
24
  " codex-multi-auth verify [--paths | --flagged | --all] [--json]",
25
- " codex-multi-auth fix [--dry-run] [--json] [--live] [--model <model>]",
26
- " codex-multi-auth doctor [--json] [--fix] [--dry-run]",
25
+ " codex-multi-auth fix [--dry-run|-n] [--json] [--live] [--model <model>]",
26
+ " codex-multi-auth doctor [--json] [--fix] [--dry-run|-n] (--dry-run requires --fix)",
27
27
  "",
28
28
  "Diagnostics:",
29
29
  " codex-multi-auth usage [--since <time|duration>] [--by <group>] [--json|--csv] [--out <path>]",
30
- " codex-multi-auth budget limit|check|list ...",
31
- " codex-multi-auth bridge token create|list|rotate|revoke",
30
+ " codex-multi-auth usage rotate [--if-larger-than-bytes <n>] [--json]",
31
+ " codex-multi-auth budget limit <key> --window <hour|day|week|month> [--requests N] [--tokens N] [--cost USD]",
32
+ " codex-multi-auth budget check|list [--json]",
33
+ " codex-multi-auth bridge token create|list|rotate|revoke [--json]",
32
34
  " codex-multi-auth integrations [--kind <name>] [--base-url <url>] [--model <model>] [--json]",
33
35
  " codex-multi-auth models [--json] [--model <model>]",
34
36
  " codex-multi-auth monitor [--json]",
35
- " codex-multi-auth rotation <enable|disable|status|bind-app|unbind-app>",
37
+ " codex-multi-auth rotation <enable|disable|status|bind-app|unbind-app|reset-runtime>",
36
38
  " codex-multi-auth rotation reset-rate-limits [--all | --account <idx>] [--dry-run] [--json]",
37
- " codex-multi-auth why-selected [--now | --last] [--json]",
39
+ " codex-multi-auth why-selected [--now|-n | --last|-l] [--json]",
38
40
  " codex-multi-auth history [list|show <id>] [--json] (all local sessions, ignores /resume provider filtering)",
39
41
  "",
40
42
  "Advanced:",
41
- " codex-multi-auth report [--live] [--json] [--explain] [--model <model>] [--out <path>]",
43
+ " codex-multi-auth report [--live] [--json] [--explain] [--model <model>] [--max-accounts <n>] [--max-probes <n>] [--cached-only] [--out <path>]",
42
44
  " codex-multi-auth config explain [--json]",
45
+ " codex-multi-auth config template|init-config [modern|legacy|minimal] [--write <path>] [--stdout]",
43
46
  " codex-multi-auth debug bundle [--json]",
44
- " codex-multi-auth features",
47
+ " codex-multi-auth features (partial built-in checklist; see docs/features.md for the full product map)",
45
48
  "",
46
49
  "Notes:",
47
50
  " - Uses ~/.codex/multi-auth/openai-codex-accounts.json",
48
51
  " - Syncs active account into Codex CLI auth state",
52
+ " - Default live probe model: " + DEFAULT_LIVE_PROBE_MODEL,
49
53
  " - See docs/reference/commands.md for the full command and flag matrix",
50
54
  ].join("\n"));
51
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"login-oauth.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-oauth.ts"],"names":[],"mappings":"AAEA,OAAO,EAON,KAAK,SAAS,EACd,MAAM,gBAAgB,CAAC;AAqBxB,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EACN,KAAK,uBAAuB,EAG5B,MAAM,yBAAyB,CAAC;AAOjC;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACrE,gBAAgB;AAChB,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAIF,gBAAgB;AAChB,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC/C,OAAO,CAGT;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,YAAY,EACpB,WAAW,CAAC,EAAE,MAAM,GAClB,uBAAuB,CA+DzB;AA0FD,gBAAgB;AAChB,MAAM,MAAM,eAAe,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,gBAAgB,GAChB,QAAQ,CAAC;AACZ,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,gBAAgB;AAChB,wBAAsB,YAAY,CACjC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,GACxD,OAAO,CAAC,WAAW,CAAC,CAwHtB;AAED,gBAAgB;AAChB,wBAAsB,aAAa,CAClC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,EACrE,OAAO,GAAE,iBAAsB,GAC7B,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,gBAAgB;AAChB,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAEhE,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,uBAAuB,EAAE,EAClC,UAAU,EAAE,OAAO,GACjB,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAuD3C;AAED,gBAAgB;AAChB,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
1
+ {"version":3,"file":"login-oauth.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-oauth.ts"],"names":[],"mappings":"AAEA,OAAO,EAON,KAAK,SAAS,EACd,MAAM,gBAAgB,CAAC;AAsBxB,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EACN,KAAK,uBAAuB,EAG5B,MAAM,yBAAyB,CAAC;AAOjC;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACrE,gBAAgB;AAChB,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAIF,gBAAgB;AAChB,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC/C,OAAO,CAGT;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,YAAY,EACpB,WAAW,CAAC,EAAE,MAAM,GAClB,uBAAuB,CA+DzB;AA0FD,gBAAgB;AAChB,MAAM,MAAM,eAAe,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,gBAAgB,GAChB,QAAQ,CAAC;AACZ,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,gBAAgB;AAChB,wBAAsB,YAAY,CACjC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,GACxD,OAAO,CAAC,WAAW,CAAC,CAsItB;AAED,gBAAgB;AAChB,wBAAsB,aAAa,CAClC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,EACrE,OAAO,GAAE,iBAAsB,GAC7B,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,gBAAgB;AAChB,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAEhE,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,uBAAuB,EAAE,EAClC,UAAU,EAAE,OAAO,GACjB,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAuD3C;AAED,gBAAgB;AAChB,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -6,6 +6,7 @@ import { copyTextToClipboard, openBrowserUrl, } from "../auth/browser.js";
6
6
  import { runDeviceAuthFlow } from "../auth/device-auth.js";
7
7
  import { resolveOrgOverride } from "../auth/org-override.js";
8
8
  import { startLocalOAuthServer } from "../auth/server.js";
9
+ import { describeCallbackFailure } from "../auth/callback-guidance.js";
9
10
  import { setCodexCliActiveSelection } from "../codex-cli/writer.js";
10
11
  import { createLogger } from "../logger.js";
11
12
  import { MODEL_FAMILIES } from "../prompts/codex.js";
@@ -230,6 +231,16 @@ export async function runOAuthFlow(forceNewLogin, signInMode) {
230
231
  : signInMode === "manual"
231
232
  ? UI_COPY.oauth.callbackBypassed
232
233
  : UI_COPY.oauth.callbackUnavailable, "warning"));
234
+ // Explain *why* the callback failed before dropping the user into the
235
+ // manual-paste prompt. A contended port 1455 — most often a Windows
236
+ // listener shadowing a WSL one, or vice versa — otherwise presents as
237
+ // an unexplained broken login.
238
+ if (signInMode === "browser") {
239
+ const failureLines = describeCallbackFailure(waitingForCallback ? "callback-timeout" : "bind-failed", { bindErrorCode: oauthServer?.bindErrorCode });
240
+ for (const line of failureLines) {
241
+ console.log(line.length > 0 ? stylePromptText(line, "muted") : "");
242
+ }
243
+ }
233
244
  const manualResult = await promptManualCallback(state, {
234
245
  allowNonTty: signInMode === "manual",
235
246
  });
@@ -1 +1 @@
1
- {"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"AAqFA,OAAO,EAEN,mBAAmB,EAMnB,sBAAsB,EAGtB,MAAM,qCAAqC,CAAC;AAiD7C,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,CAAC;AA+HvD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAuGrE;AAwRD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C7E"}
1
+ {"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"AAqFA,OAAO,EAEN,mBAAmB,EAMnB,sBAAsB,EAGtB,MAAM,qCAAqC,CAAC;AAiD7C,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,CAAC;AA4IvD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAuGrE;AAwRD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C7E"}
@@ -113,6 +113,19 @@ const IMPLEMENTED_FEATURES = [
113
113
  { id: 39, name: "Unified color/theme runtime (v2 UI)" },
114
114
  { id: 40, name: "OAuth browser-first flow with manual callback fallback" },
115
115
  { id: 41, name: "Auto-switch to best account command" },
116
+ { id: 42, name: "Device-code OAuth login (--device-auth)" },
117
+ { id: 43, name: "Workspace/org-bound login (--org)" },
118
+ { id: 44, name: "Manual pin, unpin, and affinity-generation invalidation" },
119
+ { id: 45, name: "Per-account workspace selection command" },
120
+ { id: 46, name: "Default-on runtime Responses rotation proxy" },
121
+ { id: 47, name: "Reversible packaged Codex app bind and launcher routing" },
122
+ { id: 48, name: "Local usage ledger, budgets, and account policies" },
123
+ { id: 49, name: "Routing profiles and model capability matrix" },
124
+ { id: 50, name: "Local bridge tokens and integration snippets" },
125
+ { id: 51, name: "Provider-agnostic local history listing" },
126
+ { id: 52, name: "Why-selected and monitor diagnostics" },
127
+ { id: 53, name: "Config explain, init-config templates, and debug bundle" },
128
+ { id: 54, name: "mcodex convenience launcher (monitor/tmux/forward)" },
116
129
  ];
117
130
  async function runForecast(args) {
118
131
  return runForecastCommand(args, {
@@ -32,6 +32,19 @@ export interface CodexQuotaSnapshot {
32
32
  secondary: CodexQuotaWindow;
33
33
  model: string;
34
34
  }
35
+ /**
36
+ * Format a millisecond epoch timestamp into a concise human-readable reset time.
37
+ *
38
+ * Returns a 24-hour time "HH:MM" when the timestamp is today, otherwise "HH:MM on Mon DD".
39
+ *
40
+ * This function has no side effects, is safe for concurrent use, does not access the filesystem,
41
+ * and produces no sensitive token data.
42
+ *
43
+ * @param resetAtMs - Timestamp in milliseconds since epoch; if `undefined`, non-finite, or <= 0, the function returns `undefined`.
44
+ * @param nowMs - Reference timestamp used for the "is today" comparison; defaults to the current time.
45
+ * @returns The formatted reset time string, or `undefined` if `resetAtMs` is invalid or not provided.
46
+ */
47
+ export declare function formatQuotaResetAt(resetAtMs: number | undefined, nowMs?: number): string | undefined;
35
48
  /**
36
49
  * Produce a single-line human-readable summary of a Codex quota snapshot.
37
50
  *
@@ -1 +1 @@
1
- {"version":3,"file":"quota-probe.d.ts","sourceRoot":"","sources":["../../lib/quota-probe.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,yCAAyC,CAAC;AAEnF;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GACrC,MAAM,CAMR;AAED,MAAM,WAAW,gBAAgB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACd;AAiQD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAa5E;AAED,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC5C,OAAO,EAAE,sBAAsB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CA4G7B"}
1
+ {"version":3,"file":"quota-probe.d.ts","sourceRoot":"","sources":["../../lib/quota-probe.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,yCAAyC,CAAC;AAEnF;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GACrC,MAAM,CAMR;AAED,MAAM,WAAW,gBAAgB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACd;AA0MD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,SAAa,GAChB,MAAM,GAAG,SAAS,CAqBpB;AAwBD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAa5E;AAED,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC5C,OAAO,EAAE,sBAAsB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CA4G7B"}