dexto 1.8.1 → 1.8.2
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/dist/cli/commands/connect.d.ts +9 -0
- package/dist/cli/commands/connect.d.ts.map +1 -0
- package/dist/cli/commands/connect.js +244 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +30 -394
- package/dist/index-main.js +25 -1
- package/dist/webui/assets/{index-CiIdc81a.js → index-CpolTCmd.js} +1 -1
- package/dist/webui/index.html +1 -1
- package/package.json +13 -13
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// packages/cli/src/cli/commands/setup.ts
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
2
|
import chalk from 'chalk';
|
|
5
3
|
import { z } from 'zod';
|
|
6
|
-
import open from 'open';
|
|
7
4
|
import { acceptsAnyModel, getDefaultModelForProvider, getReasoningProfile, getSupportedModels, isValidProviderModel, LLM_PROVIDERS, LLM_REGISTRY, requiresApiKey, supportsCustomModels, } from '@dexto/llm';
|
|
8
|
-
import {
|
|
9
|
-
import { createInitialPreferences, saveGlobalPreferences, loadGlobalPreferences, getGlobalPreferencesPath, updateGlobalPreferences, setActiveModel, isDextoAuthEnabled, loadCustomModels, saveCustomModel, deleteCustomModel, globalPreferencesExist,
|
|
5
|
+
import { getCuratedModelsForProvider, logger, resolveApiKeyForProvider } from '@dexto/core';
|
|
6
|
+
import { createInitialPreferences, saveGlobalPreferences, loadGlobalPreferences, getGlobalPreferencesPath, updateGlobalPreferences, setActiveModel, isDextoAuthEnabled, loadCustomModels, saveCustomModel, deleteCustomModel, globalPreferencesExist, getDefaultModelAuthProfile, loadModelAuthProfilesSync, } from '@dexto/agent-management';
|
|
10
7
|
import { interactiveApiKeySetup, hasApiKeyConfigured } from '../utils/api-key-setup.js';
|
|
11
8
|
import { selectProvider, getProviderDisplayName, getProviderEnvVar, providerRequiresBaseURL, getDefaultModel, } from '../utils/provider-setup.js';
|
|
12
9
|
import { setupLocalModels, setupOllamaModels, hasSelectedModel, getModelFromResult, } from '../utils/local-model-setup.js';
|
|
13
|
-
import { executeCommand } from '../utils/self-management.js';
|
|
14
10
|
import { requiresSetup } from '../utils/setup-utils.js';
|
|
15
11
|
import { canUseDextoProvider } from '../utils/dexto-setup.js';
|
|
16
12
|
import { handleAutoLogin } from './auth/login.js';
|
|
@@ -77,13 +73,6 @@ const REASONING_VARIANT_HINTS = {
|
|
|
77
73
|
max: 'Maximum reasoning within provider limits',
|
|
78
74
|
xhigh: 'Extra high reasoning',
|
|
79
75
|
};
|
|
80
|
-
const OPENAI_CODEX_PACKAGE = '@openai/codex';
|
|
81
|
-
const DEXTO_DEPS_PACKAGE_JSON = {
|
|
82
|
-
name: 'dexto-deps',
|
|
83
|
-
version: '1.0.0',
|
|
84
|
-
private: true,
|
|
85
|
-
description: 'Managed dependencies for Dexto',
|
|
86
|
-
};
|
|
87
76
|
function toReasoningVariantLabel(variant, defaultVariant) {
|
|
88
77
|
const normalized = variant.toLowerCase();
|
|
89
78
|
const withKnownCasing = normalized === 'xhigh'
|
|
@@ -104,6 +93,16 @@ function getReasoningVariantSelectOptions(variants, defaultVariant) {
|
|
|
104
93
|
hint: REASONING_VARIANT_HINTS[variant] ?? 'Model/provider-native reasoning variant',
|
|
105
94
|
}));
|
|
106
95
|
}
|
|
96
|
+
function hasUsableModelAuthProfile(provider) {
|
|
97
|
+
const profile = getDefaultModelAuthProfile(loadModelAuthProfilesSync(), provider);
|
|
98
|
+
if (!profile) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (profile.credential.type === 'api_key_env') {
|
|
102
|
+
return Boolean(process.env[profile.credential.envVar]?.trim());
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
107
106
|
/**
|
|
108
107
|
* Get the steps to display for the current provider and model.
|
|
109
108
|
* - Local/Ollama providers skip the API Key step.
|
|
@@ -358,7 +357,7 @@ async function handleQuickStart(options = { onCancel: 'exit' }) {
|
|
|
358
357
|
const apiKeyVar = getProviderEnvVar(provider);
|
|
359
358
|
let apiKeySkipped = false;
|
|
360
359
|
// Check if API key exists
|
|
361
|
-
const hasKey = hasApiKeyConfigured(provider);
|
|
360
|
+
const hasKey = hasApiKeyConfigured(provider) || hasUsableModelAuthProfile(provider);
|
|
362
361
|
if (!hasKey) {
|
|
363
362
|
const providerName = getProviderDisplayName(provider);
|
|
364
363
|
p.note(`${providerName} is ${chalk.green('free')} to use!\n\n` +
|
|
@@ -411,7 +410,7 @@ async function handleQuickStart(options = { onCancel: 'exit' }) {
|
|
|
411
410
|
apiKeyPending: apiKeySkipped,
|
|
412
411
|
};
|
|
413
412
|
// Only include apiKeyVar if not skipped
|
|
414
|
-
if (!apiKeySkipped) {
|
|
413
|
+
if (!apiKeySkipped && hasApiKeyConfigured(provider)) {
|
|
415
414
|
preferencesOptions.apiKeyVar = apiKeyVar;
|
|
416
415
|
}
|
|
417
416
|
const preferences = createInitialPreferences(preferencesOptions);
|
|
@@ -428,316 +427,6 @@ async function handleQuickStart(options = { onCancel: 'exit' }) {
|
|
|
428
427
|
return 'completed';
|
|
429
428
|
}
|
|
430
429
|
}
|
|
431
|
-
function getConfiguredProviderDisplayName(provider, baseURL) {
|
|
432
|
-
if (provider === 'openai-compatible') {
|
|
433
|
-
const codex = parseCodexBaseURL(baseURL);
|
|
434
|
-
if (codex) {
|
|
435
|
-
return getCodexProviderDisplayName(codex.authMode);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return getProviderDisplayName(provider);
|
|
439
|
-
}
|
|
440
|
-
function isCodexConfigured(provider, baseURL) {
|
|
441
|
-
return provider === 'openai-compatible' && isCodexBaseURL(baseURL);
|
|
442
|
-
}
|
|
443
|
-
async function ensureDextoDepsPackageJson() {
|
|
444
|
-
const depsDir = getDextoGlobalPath('deps');
|
|
445
|
-
await fs.mkdir(depsDir, { recursive: true });
|
|
446
|
-
const packageJsonPath = path.join(depsDir, 'package.json');
|
|
447
|
-
try {
|
|
448
|
-
await fs.access(packageJsonPath);
|
|
449
|
-
}
|
|
450
|
-
catch (error) {
|
|
451
|
-
if (error.code !== 'ENOENT') {
|
|
452
|
-
throw error;
|
|
453
|
-
}
|
|
454
|
-
await fs.writeFile(packageJsonPath, JSON.stringify(DEXTO_DEPS_PACKAGE_JSON, null, 2), 'utf-8');
|
|
455
|
-
}
|
|
456
|
-
return depsDir;
|
|
457
|
-
}
|
|
458
|
-
function isMissingCodexCliError(error) {
|
|
459
|
-
if (!(error instanceof Error)) {
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
const code = error.code;
|
|
463
|
-
return (error.message.includes('Codex CLI not found on PATH') ||
|
|
464
|
-
error.message.includes('spawn codex ENOENT') ||
|
|
465
|
-
(code === 'ENOENT' && error.message.includes('spawn')));
|
|
466
|
-
}
|
|
467
|
-
function getCodexSetupErrorMessage(error) {
|
|
468
|
-
if (isMissingCodexCliError(error)) {
|
|
469
|
-
return 'Codex CLI not found on PATH. Install Codex to use ChatGPT Login in Dexto.';
|
|
470
|
-
}
|
|
471
|
-
return error instanceof Error ? error.message : String(error);
|
|
472
|
-
}
|
|
473
|
-
async function resolveCodexInstaller() {
|
|
474
|
-
const candidates = [
|
|
475
|
-
{
|
|
476
|
-
command: 'npm',
|
|
477
|
-
args: ['install', OPENAI_CODEX_PACKAGE, '--no-audit', '--no-fund'],
|
|
478
|
-
label: 'npm',
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
command: 'pnpm',
|
|
482
|
-
args: ['add', OPENAI_CODEX_PACKAGE],
|
|
483
|
-
label: 'pnpm',
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
command: 'bun',
|
|
487
|
-
args: ['add', OPENAI_CODEX_PACKAGE],
|
|
488
|
-
label: 'bun',
|
|
489
|
-
},
|
|
490
|
-
];
|
|
491
|
-
for (const candidate of candidates) {
|
|
492
|
-
const probe = await executeCommand(candidate.command, ['--version']);
|
|
493
|
-
if (probe.code === 0) {
|
|
494
|
-
return candidate;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return null;
|
|
498
|
-
}
|
|
499
|
-
function getCodexInstallerFailureMessage(installer, result) {
|
|
500
|
-
const details = `${result.stderr}\n${result.stdout}`
|
|
501
|
-
.split(/\r?\n/)
|
|
502
|
-
.map((line) => line.trim())
|
|
503
|
-
.filter((line) => line.length > 0);
|
|
504
|
-
const lastLine = details.at(-1);
|
|
505
|
-
return lastLine
|
|
506
|
-
? `Failed to install the OpenAI Codex CLI via ${installer.label}: ${lastLine}`
|
|
507
|
-
: `Failed to install the OpenAI Codex CLI via ${installer.label}.`;
|
|
508
|
-
}
|
|
509
|
-
async function installManagedCodexCli() {
|
|
510
|
-
const depsDir = await ensureDextoDepsPackageJson();
|
|
511
|
-
const installer = await resolveCodexInstaller();
|
|
512
|
-
if (!installer) {
|
|
513
|
-
throw new Error('Could not find npm, pnpm, or bun to install the OpenAI Codex CLI automatically.');
|
|
514
|
-
}
|
|
515
|
-
const result = await executeCommand(installer.command, installer.args, { cwd: depsDir });
|
|
516
|
-
if (result.code !== 0) {
|
|
517
|
-
throw new Error(getCodexInstallerFailureMessage(installer, result));
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
async function createCodexClientForSetup() {
|
|
521
|
-
try {
|
|
522
|
-
return await CodexAppServerClient.create();
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
if (!isMissingCodexCliError(error)) {
|
|
526
|
-
throw error;
|
|
527
|
-
}
|
|
528
|
-
const spinner = p.spinner();
|
|
529
|
-
spinner.start('Installing OpenAI Codex CLI...');
|
|
530
|
-
try {
|
|
531
|
-
await installManagedCodexCli();
|
|
532
|
-
spinner.stop('OpenAI Codex CLI installed');
|
|
533
|
-
}
|
|
534
|
-
catch (installError) {
|
|
535
|
-
spinner.stop('OpenAI Codex CLI installation failed');
|
|
536
|
-
throw installError;
|
|
537
|
-
}
|
|
538
|
-
return await CodexAppServerClient.create();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
async function ensureCodexChatGPTLogin(client) {
|
|
542
|
-
const spinner = p.spinner();
|
|
543
|
-
spinner.start('Starting ChatGPT login with Codex...');
|
|
544
|
-
const login = await client.startLogin({ type: 'chatgpt' });
|
|
545
|
-
if (login.type !== 'chatgpt') {
|
|
546
|
-
spinner.stop('ChatGPT login failed');
|
|
547
|
-
throw new Error('Codex did not return a ChatGPT login URL');
|
|
548
|
-
}
|
|
549
|
-
spinner.stop('ChatGPT login ready');
|
|
550
|
-
try {
|
|
551
|
-
await open(login.authUrl);
|
|
552
|
-
p.log.success('Opened your browser for ChatGPT login');
|
|
553
|
-
}
|
|
554
|
-
catch (error) {
|
|
555
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
556
|
-
p.log.warn(`Could not open browser automatically: ${errorMessage}`);
|
|
557
|
-
}
|
|
558
|
-
p.note(`Finish the ChatGPT login in your browser.\n\n${chalk.dim(login.authUrl)}`, 'ChatGPT Login');
|
|
559
|
-
const waitSpinner = p.spinner();
|
|
560
|
-
waitSpinner.start('Waiting for ChatGPT login to complete...');
|
|
561
|
-
const completed = await client.waitForLoginCompleted(login.loginId, {
|
|
562
|
-
timeoutMs: 5 * 60 * 1000,
|
|
563
|
-
});
|
|
564
|
-
if (!completed.success) {
|
|
565
|
-
waitSpinner.stop('ChatGPT login failed');
|
|
566
|
-
throw new Error(completed.error ?? 'Codex ChatGPT login failed');
|
|
567
|
-
}
|
|
568
|
-
waitSpinner.stop('ChatGPT login complete');
|
|
569
|
-
return await client.readAccount(true);
|
|
570
|
-
}
|
|
571
|
-
async function ensureCodexChatGPTSession(client) {
|
|
572
|
-
const current = await client.readAccount(false);
|
|
573
|
-
if (current.account?.type === 'chatgpt') {
|
|
574
|
-
return current;
|
|
575
|
-
}
|
|
576
|
-
if (current.account) {
|
|
577
|
-
const currentLabel = 'OpenAI API key';
|
|
578
|
-
const shouldSwitch = await p.confirm({
|
|
579
|
-
message: `Codex is currently using ${currentLabel}. Switch to ChatGPT login?`,
|
|
580
|
-
initialValue: true,
|
|
581
|
-
});
|
|
582
|
-
if (p.isCancel(shouldSwitch) || !shouldSwitch) {
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
585
|
-
await client.logout();
|
|
586
|
-
}
|
|
587
|
-
return await ensureCodexChatGPTLogin(client);
|
|
588
|
-
}
|
|
589
|
-
function getCodexModelOptions(models) {
|
|
590
|
-
const visibleModels = models.filter((model) => !model.hidden);
|
|
591
|
-
const defaultModel = visibleModels.find((model) => model.isDefault) ?? visibleModels[0] ?? null;
|
|
592
|
-
if (!defaultModel) {
|
|
593
|
-
return [];
|
|
594
|
-
}
|
|
595
|
-
const secondaryModels = visibleModels
|
|
596
|
-
.filter((model) => model.model !== defaultModel.model)
|
|
597
|
-
.slice(0, 9);
|
|
598
|
-
return [
|
|
599
|
-
{
|
|
600
|
-
value: defaultModel.model,
|
|
601
|
-
label: defaultModel.displayName,
|
|
602
|
-
hint: `${defaultModel.description || 'Recommended'} (recommended)`,
|
|
603
|
-
},
|
|
604
|
-
...secondaryModels.map((model) => ({
|
|
605
|
-
value: model.model,
|
|
606
|
-
label: model.displayName,
|
|
607
|
-
hint: model.description || model.model,
|
|
608
|
-
})),
|
|
609
|
-
];
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* ChatGPT Login setup flow - authenticate with ChatGPT through Codex, choose a model, save preferences
|
|
613
|
-
*
|
|
614
|
-
* Config storage:
|
|
615
|
-
* - provider: 'openai-compatible'
|
|
616
|
-
* - baseURL: special 'codex://chatgpt' URI resolved at runtime to Codex app-server
|
|
617
|
-
* - model: model ID returned by Codex model/list
|
|
618
|
-
*/
|
|
619
|
-
async function handleCodexProviderSetup(options = {}) {
|
|
620
|
-
const exitOnCancel = options.exitOnCancel ?? true;
|
|
621
|
-
const abort = (message, exitCode = 0) => {
|
|
622
|
-
p.cancel(message);
|
|
623
|
-
if (exitOnCancel) {
|
|
624
|
-
process.exit(exitCode);
|
|
625
|
-
}
|
|
626
|
-
return false;
|
|
627
|
-
};
|
|
628
|
-
console.log(chalk.cyan('\nChatGPT Login Setup\n'));
|
|
629
|
-
let client = null;
|
|
630
|
-
try {
|
|
631
|
-
client = await createCodexClientForSetup();
|
|
632
|
-
const account = await ensureCodexChatGPTSession(client);
|
|
633
|
-
if (!account || account.account?.type !== 'chatgpt') {
|
|
634
|
-
return abort('Setup cancelled');
|
|
635
|
-
}
|
|
636
|
-
p.log.success(`Codex authenticated with ChatGPT as ${account.account.email} (${account.account.planType})`);
|
|
637
|
-
const models = await client.listModels();
|
|
638
|
-
const modelOptions = getCodexModelOptions(models);
|
|
639
|
-
if (modelOptions.length === 0) {
|
|
640
|
-
p.log.error('Codex did not return any available models.');
|
|
641
|
-
return abort('Setup cancelled', 1);
|
|
642
|
-
}
|
|
643
|
-
const model = await p.select({
|
|
644
|
-
message: 'Select a model to start with',
|
|
645
|
-
options: modelOptions,
|
|
646
|
-
});
|
|
647
|
-
if (p.isCancel(model)) {
|
|
648
|
-
return abort('Setup cancelled');
|
|
649
|
-
}
|
|
650
|
-
const selectedModel = model;
|
|
651
|
-
const provider = 'openai-compatible';
|
|
652
|
-
const baseURL = createCodexBaseURL('chatgpt');
|
|
653
|
-
const codexReasoningProfile = getReasoningProfile(provider, selectedModel);
|
|
654
|
-
let reasoningPreset;
|
|
655
|
-
if (codexReasoningProfile.capable) {
|
|
656
|
-
const selectedReasoning = await p.select({
|
|
657
|
-
message: 'Select reasoning variant',
|
|
658
|
-
options: getReasoningVariantSelectOptions(codexReasoningProfile.supportedVariants, codexReasoningProfile.defaultVariant),
|
|
659
|
-
...(codexReasoningProfile.defaultVariant
|
|
660
|
-
? { initialValue: codexReasoningProfile.defaultVariant }
|
|
661
|
-
: {}),
|
|
662
|
-
});
|
|
663
|
-
if (p.isCancel(selectedReasoning)) {
|
|
664
|
-
return abort('Setup cancelled');
|
|
665
|
-
}
|
|
666
|
-
if (selectedReasoning !== codexReasoningProfile.defaultVariant) {
|
|
667
|
-
reasoningPreset = selectedReasoning;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
const defaultMode = await selectDefaultMode();
|
|
671
|
-
if (defaultMode === null) {
|
|
672
|
-
return abort('Setup cancelled');
|
|
673
|
-
}
|
|
674
|
-
const preferences = createInitialPreferences({
|
|
675
|
-
provider,
|
|
676
|
-
model: selectedModel,
|
|
677
|
-
baseURL,
|
|
678
|
-
defaultMode,
|
|
679
|
-
setupCompleted: true,
|
|
680
|
-
apiKeyPending: false,
|
|
681
|
-
...(reasoningPreset ? { reasoning: { variant: reasoningPreset } } : {}),
|
|
682
|
-
});
|
|
683
|
-
await saveGlobalPreferences(preferences);
|
|
684
|
-
capture('dexto_setup', {
|
|
685
|
-
provider,
|
|
686
|
-
model: selectedModel,
|
|
687
|
-
setupMode: 'interactive',
|
|
688
|
-
setupVariant: 'codex-chatgpt',
|
|
689
|
-
defaultMode,
|
|
690
|
-
hasBaseURL: true,
|
|
691
|
-
});
|
|
692
|
-
await showSetupComplete(provider, selectedModel, defaultMode, false, {
|
|
693
|
-
providerLabel: getCodexProviderDisplayName('chatgpt'),
|
|
694
|
-
authLabel: getCodexAuthModeLabel('chatgpt'),
|
|
695
|
-
baseURL,
|
|
696
|
-
});
|
|
697
|
-
return true;
|
|
698
|
-
}
|
|
699
|
-
catch (error) {
|
|
700
|
-
const errorMessage = getCodexSetupErrorMessage(error);
|
|
701
|
-
p.log.error(`ChatGPT Login setup failed: ${errorMessage}`);
|
|
702
|
-
return abort('Setup cancelled', 1);
|
|
703
|
-
}
|
|
704
|
-
finally {
|
|
705
|
-
if (client) {
|
|
706
|
-
await client.close().catch(() => undefined);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
async function handleCodexChatGPTLoginRefresh(options = {}) {
|
|
711
|
-
const exitOnCancel = options.exitOnCancel ?? true;
|
|
712
|
-
const abort = (message, exitCode = 0) => {
|
|
713
|
-
p.cancel(message);
|
|
714
|
-
if (exitOnCancel) {
|
|
715
|
-
process.exit(exitCode);
|
|
716
|
-
}
|
|
717
|
-
return false;
|
|
718
|
-
};
|
|
719
|
-
console.log(chalk.cyan('\nChatGPT Login\n'));
|
|
720
|
-
let client = null;
|
|
721
|
-
try {
|
|
722
|
-
client = await createCodexClientForSetup();
|
|
723
|
-
const account = await ensureCodexChatGPTSession(client);
|
|
724
|
-
if (!account || account.account?.type !== 'chatgpt') {
|
|
725
|
-
return abort('ChatGPT login cancelled');
|
|
726
|
-
}
|
|
727
|
-
p.log.success(`Codex authenticated with ChatGPT as ${account.account.email} (${account.account.planType})`);
|
|
728
|
-
return true;
|
|
729
|
-
}
|
|
730
|
-
catch (error) {
|
|
731
|
-
const errorMessage = getCodexSetupErrorMessage(error);
|
|
732
|
-
p.log.error(`ChatGPT Login failed: ${errorMessage}`);
|
|
733
|
-
return abort('ChatGPT login cancelled', 1);
|
|
734
|
-
}
|
|
735
|
-
finally {
|
|
736
|
-
if (client) {
|
|
737
|
-
await client.close().catch(() => undefined);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
430
|
/**
|
|
742
431
|
* Dexto setup flow - login if needed, select model, save preferences
|
|
743
432
|
*
|
|
@@ -990,10 +679,6 @@ async function wizardStepSetupType(state) {
|
|
|
990
679
|
});
|
|
991
680
|
}
|
|
992
681
|
options.push({
|
|
993
|
-
value: 'openai-codex',
|
|
994
|
-
label: `${chalk.green('●')} ChatGPT Login`,
|
|
995
|
-
hint: 'Use your ChatGPT account through Codex',
|
|
996
|
-
}, {
|
|
997
682
|
value: 'quick',
|
|
998
683
|
label: `${chalk.blue('●')} Quick Start`,
|
|
999
684
|
hint: 'Google Gemini (free) - no account needed',
|
|
@@ -1015,13 +700,6 @@ async function wizardStepSetupType(state) {
|
|
|
1015
700
|
await handleDextoProviderSetup();
|
|
1016
701
|
return { ...state, step: 'complete', quickStartHandled: true };
|
|
1017
702
|
}
|
|
1018
|
-
if (setupType === 'openai-codex') {
|
|
1019
|
-
const completed = await handleCodexProviderSetup({ exitOnCancel: false });
|
|
1020
|
-
if (!completed) {
|
|
1021
|
-
return { ...state, step: 'setupType' };
|
|
1022
|
-
}
|
|
1023
|
-
return { ...state, step: 'complete', quickStartHandled: true };
|
|
1024
|
-
}
|
|
1025
703
|
if (setupType === 'quick') {
|
|
1026
704
|
// Quick start bypasses the wizard - handle it directly
|
|
1027
705
|
const result = await handleQuickStart({
|
|
@@ -1140,7 +818,7 @@ async function wizardStepApiKey(state) {
|
|
|
1140
818
|
const provider = state.provider;
|
|
1141
819
|
const model = state.model;
|
|
1142
820
|
showStepProgress('apiKey', provider, model);
|
|
1143
|
-
const hasKey = hasApiKeyConfigured(provider);
|
|
821
|
+
const hasKey = hasApiKeyConfigured(provider) || hasUsableModelAuthProfile(provider);
|
|
1144
822
|
const needsApiKey = requiresApiKey(provider);
|
|
1145
823
|
if (needsApiKey && !hasKey) {
|
|
1146
824
|
const result = await interactiveApiKeySetup(provider, {
|
|
@@ -1184,7 +862,9 @@ async function wizardStepMode(state) {
|
|
|
1184
862
|
defaultMode: undefined,
|
|
1185
863
|
};
|
|
1186
864
|
}
|
|
1187
|
-
const canShowApiKeyStep = requiresApiKey(provider) &&
|
|
865
|
+
const canShowApiKeyStep = requiresApiKey(provider) &&
|
|
866
|
+
!hasApiKeyConfigured(provider) &&
|
|
867
|
+
!hasUsableModelAuthProfile(provider);
|
|
1188
868
|
let prevStep = 'model';
|
|
1189
869
|
if (canShowApiKeyStep) {
|
|
1190
870
|
prevStep = 'apiKey';
|
|
@@ -1611,7 +1291,7 @@ async function saveWizardPreferences(state) {
|
|
|
1611
1291
|
setupCompleted: true,
|
|
1612
1292
|
apiKeyPending: apiKeySkipped,
|
|
1613
1293
|
};
|
|
1614
|
-
if (needsApiKey && !apiKeySkipped) {
|
|
1294
|
+
if (needsApiKey && !apiKeySkipped && hasApiKeyConfigured(provider)) {
|
|
1615
1295
|
preferencesOptions.apiKeyVar = apiKeyVar;
|
|
1616
1296
|
}
|
|
1617
1297
|
if (state.baseURL) {
|
|
@@ -1632,17 +1312,7 @@ async function saveWizardPreferences(state) {
|
|
|
1632
1312
|
hasBaseURL: Boolean(state.baseURL),
|
|
1633
1313
|
apiKeySkipped,
|
|
1634
1314
|
});
|
|
1635
|
-
|
|
1636
|
-
const codexSetupOptions = codex && typeof state.baseURL === 'string'
|
|
1637
|
-
? {
|
|
1638
|
-
providerLabel: getCodexProviderDisplayName(codex.authMode),
|
|
1639
|
-
authLabel: getCodexAuthModeLabel(codex.authMode),
|
|
1640
|
-
baseURL: state.baseURL,
|
|
1641
|
-
}
|
|
1642
|
-
: {};
|
|
1643
|
-
await showSetupComplete(provider, model, defaultMode, apiKeySkipped, {
|
|
1644
|
-
...codexSetupOptions,
|
|
1645
|
-
});
|
|
1315
|
+
await showSetupComplete(provider, model, defaultMode, apiKeySkipped);
|
|
1646
1316
|
}
|
|
1647
1317
|
/**
|
|
1648
1318
|
* Non-interactive setup with CLI options
|
|
@@ -1709,14 +1379,10 @@ async function showSettingsMenu() {
|
|
|
1709
1379
|
}
|
|
1710
1380
|
// Show current configuration
|
|
1711
1381
|
if (currentPrefs) {
|
|
1712
|
-
const codex = parseCodexBaseURL(currentPrefs.llm.baseURL);
|
|
1713
1382
|
const currentConfig = [
|
|
1714
|
-
`Provider: ${chalk.cyan(
|
|
1383
|
+
`Provider: ${chalk.cyan(getProviderDisplayName(currentPrefs.llm.provider))}`,
|
|
1715
1384
|
`Model: ${chalk.cyan(currentPrefs.llm.model)}`,
|
|
1716
1385
|
`Default Mode: ${chalk.cyan(currentPrefs.defaults.defaultMode)}`,
|
|
1717
|
-
...(codex
|
|
1718
|
-
? [`Authentication: ${chalk.cyan(getCodexAuthModeLabel(codex.authMode))}`]
|
|
1719
|
-
: []),
|
|
1720
1386
|
...(currentPrefs.llm.baseURL
|
|
1721
1387
|
? [`Base URL: ${chalk.cyan(currentPrefs.llm.baseURL)}`]
|
|
1722
1388
|
: []),
|
|
@@ -1732,14 +1398,9 @@ async function showSettingsMenu() {
|
|
|
1732
1398
|
p.note(currentConfig, 'Current Configuration');
|
|
1733
1399
|
}
|
|
1734
1400
|
const currentProviderLabel = currentPrefs
|
|
1735
|
-
?
|
|
1401
|
+
? getProviderDisplayName(currentPrefs.llm.provider)
|
|
1736
1402
|
: 'not set';
|
|
1737
1403
|
const currentModelLabel = currentPrefs?.llm.model || 'not set';
|
|
1738
|
-
const currentCodex = currentPrefs ? parseCodexBaseURL(currentPrefs.llm.baseURL) : null;
|
|
1739
|
-
const authActionLabel = currentCodex ? 'Manage ChatGPT login' : 'Update API key';
|
|
1740
|
-
const authActionHint = currentCodex
|
|
1741
|
-
? 'Verify or reconnect your ChatGPT login for Codex'
|
|
1742
|
-
: 'Re-enter your API key';
|
|
1743
1404
|
const options = [
|
|
1744
1405
|
{
|
|
1745
1406
|
value: 'model',
|
|
@@ -1753,8 +1414,8 @@ async function showSettingsMenu() {
|
|
|
1753
1414
|
},
|
|
1754
1415
|
{
|
|
1755
1416
|
value: 'auth',
|
|
1756
|
-
label:
|
|
1757
|
-
hint:
|
|
1417
|
+
label: 'Update API key',
|
|
1418
|
+
hint: 'Re-enter your API key',
|
|
1758
1419
|
},
|
|
1759
1420
|
{
|
|
1760
1421
|
value: 'reset',
|
|
@@ -1800,7 +1461,7 @@ async function showSettingsMenu() {
|
|
|
1800
1461
|
await openCreditsPage();
|
|
1801
1462
|
break;
|
|
1802
1463
|
case 'auth':
|
|
1803
|
-
await updateApiKey(currentPrefs?.llm.provider
|
|
1464
|
+
await updateApiKey(currentPrefs?.llm.provider);
|
|
1804
1465
|
break;
|
|
1805
1466
|
case 'reset': {
|
|
1806
1467
|
// Reset exits the menu after completion, but returns to menu if cancelled
|
|
@@ -1821,15 +1482,8 @@ async function showSettingsMenu() {
|
|
|
1821
1482
|
/**
|
|
1822
1483
|
* Change model setting (includes provider selection)
|
|
1823
1484
|
*/
|
|
1824
|
-
async function changeModel(currentProvider
|
|
1485
|
+
async function changeModel(currentProvider) {
|
|
1825
1486
|
let provider = currentProvider ?? null;
|
|
1826
|
-
if (isCodexConfigured(provider ?? undefined, currentBaseURL)) {
|
|
1827
|
-
const completed = await handleCodexProviderSetup({ exitOnCancel: false });
|
|
1828
|
-
if (!completed) {
|
|
1829
|
-
p.log.warn('Model change cancelled');
|
|
1830
|
-
}
|
|
1831
|
-
return;
|
|
1832
|
-
}
|
|
1833
1487
|
// If no provider specified, show selection
|
|
1834
1488
|
if (!provider) {
|
|
1835
1489
|
const sourceOptions = [];
|
|
@@ -1841,10 +1495,6 @@ async function changeModel(currentProvider, currentBaseURL) {
|
|
|
1841
1495
|
});
|
|
1842
1496
|
}
|
|
1843
1497
|
sourceOptions.push({
|
|
1844
|
-
value: 'openai-codex',
|
|
1845
|
-
label: `${chalk.green('●')} ChatGPT Login`,
|
|
1846
|
-
hint: 'Use your ChatGPT account through Codex',
|
|
1847
|
-
}, {
|
|
1848
1498
|
value: 'other',
|
|
1849
1499
|
label: `${chalk.blue('●')} Other providers`,
|
|
1850
1500
|
hint: 'OpenAI, Anthropic, Gemini, Ollama, etc.',
|
|
@@ -1865,13 +1515,6 @@ async function changeModel(currentProvider, currentBaseURL) {
|
|
|
1865
1515
|
}
|
|
1866
1516
|
return;
|
|
1867
1517
|
}
|
|
1868
|
-
if (providerChoice === 'openai-codex') {
|
|
1869
|
-
const completed = await handleCodexProviderSetup({ exitOnCancel: false });
|
|
1870
|
-
if (!completed) {
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
1518
|
// 'other' - fall through to normal provider selection
|
|
1876
1519
|
}
|
|
1877
1520
|
// Get provider if not already set
|
|
@@ -1950,7 +1593,7 @@ async function changeModel(currentProvider, currentBaseURL) {
|
|
|
1950
1593
|
}
|
|
1951
1594
|
const apiKeyVar = getProviderEnvVar(provider);
|
|
1952
1595
|
const needsApiKey = requiresApiKey(provider);
|
|
1953
|
-
const hasKey = hasApiKeyConfigured(provider);
|
|
1596
|
+
const hasKey = hasApiKeyConfigured(provider) || hasUsableModelAuthProfile(provider);
|
|
1954
1597
|
// Check if API key is needed and missing - prompt for it
|
|
1955
1598
|
if (needsApiKey && !hasKey) {
|
|
1956
1599
|
const result = await interactiveApiKeySetup(provider, {
|
|
@@ -1971,7 +1614,7 @@ async function changeModel(currentProvider, currentBaseURL) {
|
|
|
1971
1614
|
model,
|
|
1972
1615
|
};
|
|
1973
1616
|
// Only include apiKey for providers that need it
|
|
1974
|
-
if (needsApiKey) {
|
|
1617
|
+
if (needsApiKey && hasApiKeyConfigured(provider)) {
|
|
1975
1618
|
llmUpdate.apiKey = `$${apiKeyVar}`;
|
|
1976
1619
|
}
|
|
1977
1620
|
// Ask for reasoning variant if applicable
|
|
@@ -2007,20 +1650,13 @@ async function changeDefaultMode() {
|
|
|
2007
1650
|
/**
|
|
2008
1651
|
* Update authentication for current provider
|
|
2009
1652
|
*/
|
|
2010
|
-
async function updateApiKey(currentProvider
|
|
1653
|
+
async function updateApiKey(currentProvider) {
|
|
2011
1654
|
const provider = currentProvider || (await selectProvider());
|
|
2012
1655
|
// Handle cancellation or back from selectProvider
|
|
2013
1656
|
if (provider === null || provider === '_back') {
|
|
2014
1657
|
p.log.warn('API key update cancelled');
|
|
2015
1658
|
return;
|
|
2016
1659
|
}
|
|
2017
|
-
if (isCodexConfigured(provider, currentBaseURL)) {
|
|
2018
|
-
const completed = await handleCodexChatGPTLoginRefresh({ exitOnCancel: false });
|
|
2019
|
-
if (!completed) {
|
|
2020
|
-
p.log.warn('ChatGPT login update cancelled');
|
|
2021
|
-
}
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
2024
1660
|
// Handle providers that use non-API-key authentication
|
|
2025
1661
|
if (provider === 'vertex') {
|
|
2026
1662
|
p.note(`Google Vertex AI uses Application Default Credentials (ADC).\n\n` +
|
|
@@ -2305,7 +1941,7 @@ async function promptForBaseURL(provider) {
|
|
|
2305
1941
|
async function showSetupComplete(provider, model, defaultMode, apiKeySkipped = false, options = {}) {
|
|
2306
1942
|
const modeCommand = defaultMode === 'web' ? 'dexto' : `dexto --mode ${defaultMode}`;
|
|
2307
1943
|
const isLocalProvider = provider === 'local' || provider === 'ollama';
|
|
2308
|
-
const providerLabel = options.providerLabel ??
|
|
1944
|
+
const providerLabel = options.providerLabel ?? getProviderDisplayName(provider);
|
|
2309
1945
|
if (apiKeySkipped) {
|
|
2310
1946
|
console.log(chalk.rgb(255, 165, 0)('\n⚠️ Setup complete (API key pending)\n'));
|
|
2311
1947
|
}
|
package/dist/index-main.js
CHANGED
|
@@ -46,7 +46,7 @@ process.env.DEXTO_CLI_VERSION = cliVersion;
|
|
|
46
46
|
import { logger, startLlmRegistryAutoUpdate, DextoAgent, isPath } from '@dexto/core';
|
|
47
47
|
import { getAllSupportedModels, getProviderFromModel } from '@dexto/llm';
|
|
48
48
|
import { applyImageDefaults, cleanNullValues, AgentConfigSchema, loadImage, resolveServicesFromConfig, toDextoAgentOptions, } from '@dexto/agent-config';
|
|
49
|
-
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, findDextoProjectRoot, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, enrichAgentConfig, isDextoAuthEnabled, resolveApiKeyForProvider, getPrimaryApiKeyEnvVar, } from '@dexto/agent-management';
|
|
49
|
+
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, findDextoProjectRoot, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, enrichAgentConfig, isDextoAuthEnabled, createModelAuthResolver, resolveApiKeyForProvider, getPrimaryApiKeyEnvVar, } from '@dexto/agent-management';
|
|
50
50
|
import { validateCliOptions, handleCliOptionsError } from './cli/utils/options.js';
|
|
51
51
|
import { validateAgentConfig } from './cli/utils/config-validation.js';
|
|
52
52
|
import { applyCLIOverrides, applyStartupLLMFallback, applyUserPreferences, } from './config/cli-overrides.js';
|
|
@@ -208,6 +208,26 @@ program
|
|
|
208
208
|
safeExit('setup', 1, 'error');
|
|
209
209
|
}
|
|
210
210
|
}));
|
|
211
|
+
program
|
|
212
|
+
.command('connect')
|
|
213
|
+
.description('Connect a model provider auth method')
|
|
214
|
+
.option('--provider <provider>', 'Model provider id')
|
|
215
|
+
.option('--method <method>', 'Auth method id')
|
|
216
|
+
.option('--action <action>', 'Existing profile action: use, replace, or delete')
|
|
217
|
+
.option('--no-interactive', 'Require provider and method flags')
|
|
218
|
+
.action(withAnalytics('connect', async (options) => {
|
|
219
|
+
try {
|
|
220
|
+
const { handleConnectCommand } = await import('./cli/commands/connect.js');
|
|
221
|
+
await handleConnectCommand(options);
|
|
222
|
+
safeExit('connect', 0);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
if (err instanceof ExitSignal)
|
|
226
|
+
throw err;
|
|
227
|
+
console.error(`❌ dexto connect command failed: ${err}`);
|
|
228
|
+
safeExit('connect', 1, 'error');
|
|
229
|
+
}
|
|
230
|
+
}));
|
|
211
231
|
registerAgentsCommand({ program });
|
|
212
232
|
// 7) `upgrade` SUB-COMMAND
|
|
213
233
|
program
|
|
@@ -357,6 +377,9 @@ async function bootstrapAgentFromGlobalOpts(options) {
|
|
|
357
377
|
services,
|
|
358
378
|
image,
|
|
359
379
|
hostContext: { workspaceRoot },
|
|
380
|
+
overrides: {
|
|
381
|
+
authResolver: createModelAuthResolver(),
|
|
382
|
+
},
|
|
360
383
|
}));
|
|
361
384
|
await agent.start();
|
|
362
385
|
await (await import('./utils/workspace.js')).applyWorkspaceToAgent(agent, workspaceRoot);
|
|
@@ -842,6 +865,7 @@ program
|
|
|
842
865
|
overrides: {
|
|
843
866
|
sessionLoggerFactory,
|
|
844
867
|
mcpAuthProviderFactory,
|
|
868
|
+
authResolver: createModelAuthResolver(),
|
|
845
869
|
},
|
|
846
870
|
}));
|
|
847
871
|
// Start the agent (initialize async services)
|