oc-codex-multi-auth 6.15.0 → 6.17.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.
- package/README.md +15 -11
- package/config/README.md +36 -13
- package/config/opencode-legacy.json +150 -0
- package/config/opencode-modern.json +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +145 -55
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +53 -3
- package/dist/lib/accounts/persistence.d.ts.map +1 -1
- package/dist/lib/accounts/persistence.js +110 -4
- package/dist/lib/accounts/persistence.js.map +1 -1
- package/dist/lib/accounts/state.d.ts +1 -0
- package/dist/lib/accounts/state.d.ts.map +1 -1
- package/dist/lib/accounts/state.js +11 -0
- package/dist/lib/accounts/state.js.map +1 -1
- package/dist/lib/accounts.d.ts +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +1 -1
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/login-runner.d.ts +2 -0
- package/dist/lib/auth/login-runner.d.ts.map +1 -1
- package/dist/lib/auth/login-runner.js +44 -12
- package/dist/lib/auth/login-runner.js.map +1 -1
- package/dist/lib/auth/loopback-flow.d.ts +124 -0
- package/dist/lib/auth/loopback-flow.d.ts.map +1 -0
- package/dist/lib/auth/loopback-flow.js +112 -0
- package/dist/lib/auth/loopback-flow.js.map +1 -0
- package/dist/lib/auth/plan-tier.d.ts +21 -0
- package/dist/lib/auth/plan-tier.d.ts.map +1 -0
- package/dist/lib/auth/plan-tier.js +92 -0
- package/dist/lib/auth/plan-tier.js.map +1 -0
- package/dist/lib/auth/token-utils.d.ts +8 -10
- package/dist/lib/auth/token-utils.d.ts.map +1 -1
- package/dist/lib/auth/token-utils.js +27 -18
- package/dist/lib/auth/token-utils.js.map +1 -1
- package/dist/lib/constants.d.ts +5 -2
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +5 -2
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/prompts/codex.d.ts +1 -1
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +51 -4
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/quota-notifications.d.ts +15 -0
- package/dist/lib/quota-notifications.d.ts.map +1 -1
- package/dist/lib/quota-notifications.js +42 -12
- package/dist/lib/quota-notifications.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +88 -9
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/client-identity.d.ts.map +1 -1
- package/dist/lib/request/helpers/client-identity.js +9 -2
- package/dist/lib/request/helpers/client-identity.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +23 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +92 -2
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/request/helpers/responses-lite.d.ts.map +1 -1
- package/dist/lib/request/helpers/responses-lite.js +34 -1
- package/dist/lib/request/helpers/responses-lite.js.map +1 -1
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +78 -13
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/schemas.d.ts +15 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/flagged.d.ts.map +1 -1
- package/dist/lib/storage/flagged.js +5 -0
- package/dist/lib/storage/flagged.js.map +1 -1
- package/dist/lib/storage/migrations.d.ts +2 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/tools/codex-limits.d.ts.map +1 -1
- package/dist/lib/tools/codex-limits.js +7 -4
- package/dist/lib/tools/codex-limits.js.map +1 -1
- package/dist/lib/tools/codex-list.d.ts.map +1 -1
- package/dist/lib/tools/codex-list.js +13 -1
- package/dist/lib/tools/codex-list.js.map +1 -1
- package/dist/lib/tools/codex-status.d.ts.map +1 -1
- package/dist/lib/tools/codex-status.js +8 -0
- package/dist/lib/tools/codex-status.js.map +1 -1
- package/dist/lib/tui-status.d.ts.map +1 -1
- package/dist/lib/tui-status.js +6 -2
- package/dist/lib/tui-status.js.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/install-oc-codex-multi-auth-core.js +5 -5
package/dist/index.js
CHANGED
|
@@ -27,11 +27,11 @@ import { createHash } from "node:crypto";
|
|
|
27
27
|
import { homedir } from "node:os";
|
|
28
28
|
import { join } from "node:path";
|
|
29
29
|
import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, REDIRECT_URI, } from "./lib/auth/auth.js";
|
|
30
|
+
import { formatPlanType } from "./lib/auth/plan-tier.js";
|
|
31
|
+
import { startLoopbackFlow, } from "./lib/auth/loopback-flow.js";
|
|
30
32
|
import { buildDeviceCodeInstructions, completeDeviceCodeSession, createDeviceCodeSession, } from "./lib/auth/device-code.js";
|
|
31
33
|
import { applyAccountSelectionFallbacks, persistResolvedAccountSelection, persistAccountPool, resolveAndPersistAccountSelection, resolveAccountSelection, } from "./lib/auth/login-runner.js";
|
|
32
34
|
import { coordinateFlaggedPersistedRefresh, coordinatePersistedRefresh, } from "./lib/storage/coordinated-refresh.js";
|
|
33
|
-
import { openBrowserUrl } from "./lib/auth/browser.js";
|
|
34
|
-
import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
35
35
|
import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
|
|
36
36
|
import { getCodexMode, getRequestTransformMode, getFastSession, getFastSessionStrategy, getFastSessionMaxInputItems, getRetryProfile, getRetryBudgetOverrides, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getFallbackToGpt52OnUnsupportedGpt53, getUnsupportedCodexPolicy, getUnsupportedCodexFallbackChain, getTokenRefreshSkewMs, getSessionRecovery, getAutoResume, getAutoUpdate, getToastDurationMs, getAccountToastsEnabled, getPerProjectAccounts, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getPidOffsetEnabled, getRotationStrategy, getModelAccountPool, getModelAccountPoolMode, getFetchTimeoutMs, getStreamStallTimeoutMs, getCodexTuiV2, getCodexTuiColorProfile, getCodexTuiGlyphMode, getBeginnerSafeMode, getCodexTuiMaskEmail, loadPluginConfig, } from "./lib/config.js";
|
|
37
37
|
import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, ACCOUNT_LIMITS, } from "./lib/constants.js";
|
|
@@ -305,39 +305,76 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
};
|
|
308
|
+
const listenerUnavailableMessage = () => `OAuth callback server failed to start on localhost loopback port 1455. ` +
|
|
309
|
+
`Retry with "${AUTH_LABELS.OAUTH_DEVICE_CODE}" or "${AUTH_LABELS.OAUTH_MANUAL}".`;
|
|
310
|
+
const callbackCancelledMessage = () => `OAuth callback timed out or was cancelled. ` +
|
|
311
|
+
`If you are on SSH, WSL, or a headless machine, retry with "${AUTH_LABELS.OAUTH_DEVICE_CODE}" or "${AUTH_LABELS.OAUTH_MANUAL}".`;
|
|
312
|
+
// The login is still live when this fires: the listener is bound and
|
|
313
|
+
// waiting, so opening the URL printed above finishes it. That is the
|
|
314
|
+
// whole recovery path on a host with no opener on PATH.
|
|
315
|
+
const browserOpenFailedMessage = () => `Could not launch your default browser. Open the OAuth URL above in any browser to finish signing in; ` +
|
|
316
|
+
`this login is still waiting on the localhost callback. ` +
|
|
317
|
+
`"${AUTH_LABELS.OAUTH_MANUAL_BROWSER}" does the same thing without trying to launch a browser, ` +
|
|
318
|
+
`and "${AUTH_LABELS.OAUTH_DEVICE_CODE}" or "${AUTH_LABELS.OAUTH_MANUAL}" avoid the callback entirely.`;
|
|
319
|
+
const unavailableMessage = (lifecycle) => {
|
|
320
|
+
switch (lifecycle) {
|
|
321
|
+
case "listener_unavailable":
|
|
322
|
+
return listenerUnavailableMessage();
|
|
323
|
+
default: {
|
|
324
|
+
const unreachable = lifecycle;
|
|
325
|
+
return unreachable;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Every accepted paste must carry this attempt's `state`, a raw code
|
|
331
|
+
* included.
|
|
332
|
+
*
|
|
333
|
+
* That comparison is the manual flow's only in-plugin binding between
|
|
334
|
+
* the pasted value and this login. Accepting a bare code delegates the
|
|
335
|
+
* binding entirely to the authorization server's PKCE enforcement, and
|
|
336
|
+
* hands anything a user is talked into pasting to
|
|
337
|
+
* `exchangeAuthorizationCode` with this attempt's verifier — which is
|
|
338
|
+
* the shape of the attack the state check exists to stop. The raw
|
|
339
|
+
* branch of `parseAuthorizationInput` also returns the whole trimmed
|
|
340
|
+
* input as the code, so a noisy paste would be forwarded verbatim.
|
|
341
|
+
*/
|
|
342
|
+
const manualInputRejection = (parsed, expectedState) => {
|
|
343
|
+
if (!parsed.code) {
|
|
344
|
+
return "No authorization code found. Paste the full callback URL (e.g., http://localhost:1455/auth/callback?code=...&state=...). If browser callback keeps failing, retry with Device Code.";
|
|
345
|
+
}
|
|
346
|
+
switch (parsed.source) {
|
|
347
|
+
case "raw":
|
|
348
|
+
return "That is a bare authorization code. This flow needs the full callback URL, including the state parameter (e.g., http://localhost:1455/auth/callback?code=...&state=...), which is what ties the code to this login attempt. If needed, retry with Device Code.";
|
|
349
|
+
case "url":
|
|
350
|
+
case "query":
|
|
351
|
+
case "fragment":
|
|
352
|
+
if (!parsed.state) {
|
|
353
|
+
return "That callback URL carries no OAuth state, so it cannot be matched to this login attempt. Paste the complete callback URL including its state parameter, or retry with Device Code.";
|
|
354
|
+
}
|
|
355
|
+
if (parsed.state !== expectedState) {
|
|
356
|
+
return "OAuth state mismatch. Restart login and paste the callback URL generated for this login attempt, or retry with Device Code.";
|
|
357
|
+
}
|
|
358
|
+
return undefined;
|
|
359
|
+
default: {
|
|
360
|
+
const unreachable = parsed;
|
|
361
|
+
return unreachable;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
308
365
|
const buildManualOAuthFlow = (pkce, url, expectedState, replaceAll) => ({
|
|
309
366
|
url,
|
|
310
367
|
method: "code",
|
|
311
368
|
instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
|
|
312
|
-
validate: (input) =>
|
|
313
|
-
const parsed = parseAuthorizationInput(input);
|
|
314
|
-
if (!parsed.code) {
|
|
315
|
-
return "No authorization code found. Paste the full callback URL (e.g., http://localhost:1455/auth/callback?code=...). If browser callback keeps failing, retry with Device Code.";
|
|
316
|
-
}
|
|
317
|
-
if (!parsed.state) {
|
|
318
|
-
return parsed.source === "raw"
|
|
319
|
-
? "That is a bare authorization code. This flow needs the full callback URL, including the state parameter (e.g., http://localhost:1455/auth/callback?code=...&state=...). If needed, retry with Device Code."
|
|
320
|
-
: "Missing OAuth state. Paste the full callback URL including both code and state parameters. If needed, retry with Device Code.";
|
|
321
|
-
}
|
|
322
|
-
if (parsed.state !== expectedState) {
|
|
323
|
-
return "OAuth state mismatch. Restart login and paste the callback URL generated for this login attempt, or retry with Device Code.";
|
|
324
|
-
}
|
|
325
|
-
return undefined;
|
|
326
|
-
},
|
|
369
|
+
validate: (input) => manualInputRejection(parseAuthorizationInput(input), expectedState),
|
|
327
370
|
callback: async (input) => {
|
|
328
371
|
const parsed = parseAuthorizationInput(input);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
type: "failed",
|
|
332
|
-
reason: "invalid_response",
|
|
333
|
-
message: "Missing authorization code or OAuth state",
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
if (parsed.state !== expectedState) {
|
|
372
|
+
const rejection = manualInputRejection(parsed, expectedState);
|
|
373
|
+
if (rejection !== undefined || !parsed.code) {
|
|
337
374
|
return {
|
|
338
375
|
type: "failed",
|
|
339
376
|
reason: "invalid_response",
|
|
340
|
-
message:
|
|
377
|
+
message: rejection ?? "Missing authorization code",
|
|
341
378
|
};
|
|
342
379
|
}
|
|
343
380
|
const tokens = await exchangeAuthorizationCode(parsed.code, pkce.verifier, REDIRECT_URI);
|
|
@@ -354,21 +391,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
354
391
|
},
|
|
355
392
|
});
|
|
356
393
|
const runOAuthFlow = async (forceNewLogin = false) => {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
catch (err) {
|
|
364
|
-
logDebug(`[${PLUGIN_NAME}] Failed to start OAuth server: ${err?.message ?? String(err)}`);
|
|
365
|
-
serverInfo = null;
|
|
366
|
-
}
|
|
367
|
-
openBrowserUrl(url);
|
|
368
|
-
if (!serverInfo || !serverInfo.ready) {
|
|
369
|
-
serverInfo?.close();
|
|
370
|
-
const message = `OAuth callback server failed to start on localhost loopback port 1455. ` +
|
|
371
|
-
`Retry with "${AUTH_LABELS.OAUTH_DEVICE_CODE}" or "${AUTH_LABELS.OAUTH_MANUAL}".`;
|
|
394
|
+
const session = await startLoopbackFlow({
|
|
395
|
+
openBrowser: true,
|
|
396
|
+
forceNewLogin,
|
|
397
|
+
});
|
|
398
|
+
if (session.type === "unavailable") {
|
|
399
|
+
const message = unavailableMessage(session.lifecycle);
|
|
372
400
|
logWarn(`\n[${PLUGIN_NAME}] ${message}\n`);
|
|
373
401
|
return {
|
|
374
402
|
type: "failed",
|
|
@@ -376,17 +404,21 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
376
404
|
message,
|
|
377
405
|
};
|
|
378
406
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
407
|
+
logInfo(`OAuth URL: ${session.url}`);
|
|
408
|
+
// Printed first, so the recovery instruction points at a URL the
|
|
409
|
+
// user can already see. The session stays open either way.
|
|
410
|
+
if (!session.browserOpened) {
|
|
411
|
+
logWarn(`\n[${PLUGIN_NAME}] ${browserOpenFailedMessage()}\n`);
|
|
412
|
+
}
|
|
413
|
+
const result = await session.waitAndExchange();
|
|
414
|
+
if (result.type === "cancelled") {
|
|
382
415
|
return {
|
|
383
416
|
type: "failed",
|
|
384
417
|
reason: "unknown",
|
|
385
|
-
message:
|
|
386
|
-
`If you are on SSH, WSL, or a headless machine, retry with "${AUTH_LABELS.OAUTH_DEVICE_CODE}" or "${AUTH_LABELS.OAUTH_MANUAL}".`,
|
|
418
|
+
message: callbackCancelledMessage(),
|
|
387
419
|
};
|
|
388
420
|
}
|
|
389
|
-
return
|
|
421
|
+
return result;
|
|
390
422
|
};
|
|
391
423
|
const showToast = async (message, variant = "success", options) => {
|
|
392
424
|
try {
|
|
@@ -2544,9 +2576,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2544
2576
|
const authPerProjectAccounts = getPerProjectAccounts(authPluginConfig);
|
|
2545
2577
|
setStoragePath(authPerProjectAccounts ? process.cwd() : null);
|
|
2546
2578
|
const accounts = [];
|
|
2579
|
+
// Programmatic input, not a menu choice: a headless caller or
|
|
2580
|
+
// script passes it to say "never launch a browser". Ignoring it
|
|
2581
|
+
// would silently do the opposite — enter the multi-account loop,
|
|
2582
|
+
// try to launch a browser, and bind port 1455 — and would also
|
|
2583
|
+
// drop the replaceAll semantics the manual path carries.
|
|
2547
2584
|
const noBrowser = inputs?.noBrowser === "true" ||
|
|
2548
2585
|
inputs?.["no-browser"] === "true";
|
|
2549
|
-
const useManualMode = noBrowser;
|
|
2550
2586
|
const explicitLoginMode = inputs?.loginMode === "fresh" || inputs?.loginMode === "add"
|
|
2551
2587
|
? inputs.loginMode
|
|
2552
2588
|
: null;
|
|
@@ -2594,8 +2630,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2594
2630
|
parts.push(`${limit.name} ${formatUsageLimitSummary(limit.window)}`);
|
|
2595
2631
|
}
|
|
2596
2632
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2633
|
+
const planLabel = formatPlanType(usage.planType);
|
|
2634
|
+
if (planLabel)
|
|
2635
|
+
parts.push(`plan:${planLabel}`);
|
|
2599
2636
|
if (usage.credits)
|
|
2600
2637
|
parts.push(`credits:${usage.credits}`);
|
|
2601
2638
|
return parts.length > 0 ? parts.join(", ") : "quota unavailable";
|
|
@@ -3180,10 +3217,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
3180
3217
|
if (refreshAccountIndex !== undefined) {
|
|
3181
3218
|
targetCount = 1;
|
|
3182
3219
|
}
|
|
3183
|
-
if (
|
|
3220
|
+
if (noBrowser) {
|
|
3184
3221
|
targetCount = 1;
|
|
3185
|
-
|
|
3186
|
-
|
|
3222
|
+
// Paste-the-code, not the loopback listener: this is the one
|
|
3223
|
+
// path that needs neither a browser on this host nor a
|
|
3224
|
+
// reachable localhost:1455 from wherever the browser runs.
|
|
3187
3225
|
const { pkce, state, url } = await createAuthorizationFlow();
|
|
3188
3226
|
return buildManualOAuthFlow(pkce, url, state, startFresh);
|
|
3189
3227
|
}
|
|
@@ -3274,6 +3312,57 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
3274
3312
|
};
|
|
3275
3313
|
},
|
|
3276
3314
|
},
|
|
3315
|
+
{
|
|
3316
|
+
label: AUTH_LABELS.OAUTH_MANUAL_BROWSER,
|
|
3317
|
+
type: "oauth",
|
|
3318
|
+
authorize: async () => {
|
|
3319
|
+
// Must happen BEFORE the callback persists, to ensure correct
|
|
3320
|
+
// storage location: OpenCode invokes callback() separately from
|
|
3321
|
+
// authorize(), so the path has to be pinned here.
|
|
3322
|
+
const manualBrowserPluginConfig = loadPluginConfig();
|
|
3323
|
+
applyUiRuntimeFromConfig(manualBrowserPluginConfig);
|
|
3324
|
+
const manualBrowserPerProjectAccounts = getPerProjectAccounts(manualBrowserPluginConfig);
|
|
3325
|
+
setStoragePath(manualBrowserPerProjectAccounts ? process.cwd() : null);
|
|
3326
|
+
const session = await startLoopbackFlow({ openBrowser: false });
|
|
3327
|
+
if (session.type === "unavailable") {
|
|
3328
|
+
const message = unavailableMessage(session.lifecycle);
|
|
3329
|
+
logWarn(`\n[${PLUGIN_NAME}] ${message}\n`);
|
|
3330
|
+
return {
|
|
3331
|
+
url: "",
|
|
3332
|
+
instructions: message,
|
|
3333
|
+
method: "auto",
|
|
3334
|
+
callback: () => Promise.resolve({
|
|
3335
|
+
type: "failed",
|
|
3336
|
+
reason: "invalid_response",
|
|
3337
|
+
message,
|
|
3338
|
+
}),
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
return {
|
|
3342
|
+
url: session.url,
|
|
3343
|
+
instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL_BROWSER,
|
|
3344
|
+
method: "auto",
|
|
3345
|
+
callback: async () => {
|
|
3346
|
+
const result = await session.waitAndExchange();
|
|
3347
|
+
if (result.type === "cancelled") {
|
|
3348
|
+
return {
|
|
3349
|
+
type: "failed",
|
|
3350
|
+
reason: "unknown",
|
|
3351
|
+
message: callbackCancelledMessage(),
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
if (result.type !== "success") {
|
|
3355
|
+
return result;
|
|
3356
|
+
}
|
|
3357
|
+
const resolved = await resolveAndPersistAccountSelection(result, {
|
|
3358
|
+
persistSelections: persistAuthenticatedSelections,
|
|
3359
|
+
replaceAll: false,
|
|
3360
|
+
});
|
|
3361
|
+
return resolved.primary;
|
|
3362
|
+
},
|
|
3363
|
+
};
|
|
3364
|
+
},
|
|
3365
|
+
},
|
|
3277
3366
|
{
|
|
3278
3367
|
label: AUTH_LABELS.OAUTH_DEVICE_CODE,
|
|
3279
3368
|
type: "oauth",
|
|
@@ -3313,8 +3402,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
3313
3402
|
label: AUTH_LABELS.OAUTH_MANUAL,
|
|
3314
3403
|
type: "oauth",
|
|
3315
3404
|
authorize: async () => {
|
|
3316
|
-
//
|
|
3317
|
-
//
|
|
3405
|
+
// Must happen BEFORE the callback persists, to ensure correct
|
|
3406
|
+
// storage location: OpenCode invokes callback() separately from
|
|
3407
|
+
// authorize(), so the path has to be pinned here.
|
|
3318
3408
|
const manualPluginConfig = loadPluginConfig();
|
|
3319
3409
|
applyUiRuntimeFromConfig(manualPluginConfig);
|
|
3320
3410
|
const manualPerProjectAccounts = getPerProjectAccounts(manualPluginConfig);
|