dexto 1.4.0 → 1.5.1
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 +62 -7
- package/dist/agents/agent-template.yml +2 -2
- package/dist/agents/coding-agent/coding-agent.yml +22 -16
- package/dist/agents/database-agent/database-agent.yml +2 -2
- package/dist/agents/default-agent.yml +7 -5
- package/dist/agents/github-agent/github-agent.yml +2 -2
- package/dist/agents/product-name-researcher/product-name-researcher.yml +2 -2
- package/dist/agents/talk2pdf-agent/talk2pdf-agent.yml +2 -2
- package/dist/analytics/events.d.ts +13 -6
- package/dist/analytics/events.d.ts.map +1 -1
- package/dist/analytics/index.d.ts +1 -1
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +6 -2
- package/dist/api/server-hono.d.ts.map +1 -1
- package/dist/api/server-hono.js +27 -5
- package/dist/cli/cli-subscriber.d.ts +4 -0
- package/dist/cli/cli-subscriber.d.ts.map +1 -1
- package/dist/cli/cli-subscriber.js +40 -2
- package/dist/cli/commands/create-app.d.ts +16 -14
- package/dist/cli/commands/create-app.d.ts.map +1 -1
- package/dist/cli/commands/create-app.js +626 -102
- package/dist/cli/commands/create-image.d.ts +7 -0
- package/dist/cli/commands/create-image.d.ts.map +1 -0
- package/dist/cli/commands/create-image.js +201 -0
- package/dist/cli/commands/helpers/formatters.js +7 -7
- package/dist/cli/commands/index.d.ts +2 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/init-app.js +7 -7
- package/dist/cli/commands/install.d.ts +0 -3
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +10 -35
- package/dist/cli/commands/interactive-commands/command-parser.d.ts +1 -1
- package/dist/cli/commands/interactive-commands/command-parser.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/command-parser.js +18 -8
- package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/general-commands.js +64 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.js +11 -11
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/system/system-commands.js +6 -5
- package/dist/cli/commands/list-agents.js +2 -2
- package/dist/cli/commands/session-commands.js +16 -16
- package/dist/cli/commands/setup.d.ts +13 -5
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +995 -65
- package/dist/cli/commands/which.js +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts +2 -0
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.js +29 -7
- package/dist/cli/ink-cli/components/CustomInput.js +1 -1
- package/dist/cli/ink-cli/components/EditableMultiLineInput.js +4 -4
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ElicitationForm.js +6 -6
- package/dist/cli/ink-cli/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
- package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/Footer.js +1 -1
- package/dist/cli/ink-cli/components/HistorySearchBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/HistorySearchBar.js +1 -1
- package/dist/cli/ink-cli/components/MultiLineInput.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/MultiLineInput.js +3 -3
- package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ResourceAutocomplete.js +4 -4
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +3 -3
- package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/StatusBar.js +7 -5
- package/dist/cli/ink-cli/components/TextBufferInput.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/TextBufferInput.js +13 -9
- package/dist/cli/ink-cli/components/base/BaseAutocomplete.js +4 -4
- package/dist/cli/ink-cli/components/base/BaseSelector.js +2 -2
- package/dist/cli/ink-cli/components/chat/Footer.js +1 -1
- package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/Header.js +2 -4
- package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.js +9 -6
- package/dist/cli/ink-cli/components/chat/MessageList.js +1 -1
- package/dist/cli/ink-cli/components/chat/QueuedMessagesDisplay.js +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.js +4 -4
- package/dist/cli/ink-cli/components/chat/styled-boxes/ConfigBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/HelpBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +2 -2
- package/dist/cli/ink-cli/components/chat/styled-boxes/SessionHistoryBox.js +5 -5
- package/dist/cli/ink-cli/components/chat/styled-boxes/SessionListBox.js +2 -2
- package/dist/cli/ink-cli/components/chat/styled-boxes/ShortcutsBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/StatsBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/StyledBox.js +2 -2
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ApiKeyInput.js +1 -1
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts +10 -2
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +209 -89
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpAddChoice.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpAddChoice.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpAddSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpCustomWizard.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerActions.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpServerList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerList.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +6 -5
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +284 -68
- package/dist/cli/ink-cli/components/overlays/PromptAddChoice.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptAddChoice.js +2 -2
- package/dist/cli/ink-cli/components/overlays/PromptAddWizard.js +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptList.js +2 -2
- package/dist/cli/ink-cli/components/overlays/SearchOverlay.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/SearchOverlay.js +4 -4
- package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/StreamSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/StreamSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +12 -12
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts +25 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +609 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts +15 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.js +14 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +33 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +462 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts +25 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.js +29 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts +17 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.js +11 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts +20 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.js +10 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts +30 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.js +13 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts +8 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.js +7 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts +85 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.js +38 -0
- package/dist/cli/ink-cli/components/renderers/DiffRenderer.js +2 -2
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +1 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.js +4 -4
- package/dist/cli/ink-cli/components/renderers/GenericRenderer.js +2 -2
- package/dist/cli/ink-cli/components/renderers/SearchRenderer.js +1 -1
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +3 -3
- package/dist/cli/ink-cli/components/renderers/diff-shared.js +1 -1
- package/dist/cli/ink-cli/components/shared/MarkdownText.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/shared/MarkdownText.js +8 -6
- package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/InputContainer.js +23 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.js +81 -25
- package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts +9 -2
- package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useAgentEvents.js +148 -6
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.js +7 -2
- package/dist/cli/ink-cli/hooks/useTokenCounter.d.ts +11 -7
- package/dist/cli/ink-cli/hooks/useTokenCounter.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useTokenCounter.js +41 -18
- package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
- package/dist/cli/ink-cli/services/processStream.js +97 -17
- package/dist/cli/ink-cli/state/types.d.ts +5 -4
- package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts +5 -0
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.js +59 -1
- package/dist/cli/ink-cli/utils/toolUtils.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/toolUtils.js +2 -0
- package/dist/cli/utils/api-key-setup.d.ts +54 -4
- package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
- package/dist/cli/utils/api-key-setup.js +433 -107
- package/dist/cli/utils/api-key-verification.d.ts +17 -0
- package/dist/cli/utils/api-key-verification.d.ts.map +1 -0
- package/dist/cli/utils/api-key-verification.js +211 -0
- package/dist/cli/utils/config-validation.d.ts +22 -2
- package/dist/cli/utils/config-validation.d.ts.map +1 -1
- package/dist/cli/utils/config-validation.js +354 -25
- package/dist/cli/utils/local-model-setup.d.ts +46 -0
- package/dist/cli/utils/local-model-setup.d.ts.map +1 -0
- package/dist/cli/utils/local-model-setup.js +662 -0
- package/dist/cli/utils/options.js +1 -1
- package/dist/cli/utils/prompt-helpers.d.ts +47 -0
- package/dist/cli/utils/prompt-helpers.d.ts.map +1 -0
- package/dist/cli/utils/prompt-helpers.js +66 -0
- package/dist/cli/utils/provider-setup.d.ts +66 -8
- package/dist/cli/utils/provider-setup.d.ts.map +1 -1
- package/dist/cli/utils/provider-setup.js +324 -84
- package/dist/cli/utils/scaffolding-utils.d.ts +76 -0
- package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -0
- package/dist/cli/utils/scaffolding-utils.js +246 -0
- package/dist/cli/utils/setup-utils.d.ts +16 -0
- package/dist/cli/utils/setup-utils.d.ts.map +1 -1
- package/dist/cli/utils/setup-utils.js +72 -21
- package/dist/cli/utils/template-engine.d.ts +65 -0
- package/dist/cli/utils/template-engine.d.ts.map +1 -0
- package/dist/cli/utils/template-engine.js +1089 -0
- package/dist/config/cli-overrides.d.ts +44 -1
- package/dist/config/cli-overrides.d.ts.map +1 -1
- package/dist/config/cli-overrides.js +102 -0
- package/dist/index.js +339 -56
- package/dist/webui/assets/index-8j-KMkX1.js +2054 -0
- package/dist/webui/assets/index-c_AX24V4.css +1 -0
- package/dist/webui/index.html +3 -9
- package/dist/webui/logos/aws-color.svg +1 -0
- package/dist/webui/logos/dexto/dexto_logo.svg +1 -1
- package/dist/webui/logos/dexto/dexto_logo_light.svg +6 -6
- package/dist/webui/logos/glama.svg +7 -0
- package/dist/webui/logos/litellm.svg +7 -0
- package/dist/webui/logos/openrouter.svg +1 -0
- package/package.json +8 -7
- package/dist/webui/assets/index-BkwPkZpd.css +0 -1
- package/dist/webui/assets/index-D9u1XfyH.js +0 -2025
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// packages/cli/src/cli/commands/setup.ts
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { getDefaultModelForProvider, LLM_PROVIDERS, isValidProviderModel, getSupportedModels, } from '@dexto/core';
|
|
5
|
-
import {
|
|
6
|
-
import { createInitialPreferences, saveGlobalPreferences } from '@dexto/agent-management';
|
|
7
|
-
import { interactiveApiKeySetup } from '../utils/api-key-setup.js';
|
|
8
|
-
import { selectProvider } from '../utils/provider-setup.js';
|
|
4
|
+
import { getDefaultModelForProvider, LLM_PROVIDERS, LLM_REGISTRY, isValidProviderModel, getSupportedModels, acceptsAnyModel, supportsCustomModels, requiresApiKey, isReasoningCapableModel, } from '@dexto/core';
|
|
5
|
+
import { resolveApiKeyForProvider } from '@dexto/core';
|
|
6
|
+
import { createInitialPreferences, saveGlobalPreferences, loadGlobalPreferences, getGlobalPreferencesPath, updateGlobalPreferences, setActiveModel, } from '@dexto/agent-management';
|
|
7
|
+
import { interactiveApiKeySetup, hasApiKeyConfigured } from '../utils/api-key-setup.js';
|
|
8
|
+
import { selectProvider, getProviderDisplayName, getProviderEnvVar, providerRequiresBaseURL, getDefaultModel, } from '../utils/provider-setup.js';
|
|
9
|
+
import { setupLocalModels, setupOllamaModels, hasSelectedModel, getModelFromResult, } from '../utils/local-model-setup.js';
|
|
9
10
|
import { requiresSetup } from '../utils/setup-utils.js';
|
|
10
11
|
import * as p from '@clack/prompts';
|
|
11
12
|
import { logger } from '@dexto/core';
|
|
@@ -25,107 +26,1036 @@ const SetupCommandSchema = z
|
|
|
25
26
|
defaultAgent: z
|
|
26
27
|
.string()
|
|
27
28
|
.min(1, 'Default agent name cannot be empty')
|
|
28
|
-
.default('
|
|
29
|
+
.default('coding-agent')
|
|
29
30
|
.describe('Registry agent id to use when none is specified'),
|
|
30
31
|
interactive: z.boolean().default(true).describe('Enable interactive prompts'),
|
|
31
32
|
force: z
|
|
32
33
|
.boolean()
|
|
33
34
|
.default(false)
|
|
34
35
|
.describe('Overwrite existing setup when already configured'),
|
|
36
|
+
quickStart: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.default(false)
|
|
39
|
+
.describe('Use quick start with Google Gemini (recommended for new users)'),
|
|
35
40
|
})
|
|
36
41
|
.strict()
|
|
37
42
|
.superRefine((data, ctx) => {
|
|
38
43
|
// Validate model against provider when both are provided
|
|
39
44
|
if (data.provider && data.model) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Skip validation for providers that accept any model
|
|
46
|
+
if (!acceptsAnyModel(data.provider) && !supportsCustomModels(data.provider)) {
|
|
47
|
+
if (!isValidProviderModel(data.provider, data.model)) {
|
|
48
|
+
const supportedModels = getSupportedModels(data.provider);
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: z.ZodIssueCode.custom,
|
|
51
|
+
path: ['model'],
|
|
52
|
+
message: `Model '${data.model}' is not supported by provider '${data.provider}'. Supported models: ${supportedModels.join(', ')}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
});
|
|
58
|
+
/**
|
|
59
|
+
* Get the steps to display for the current provider and model.
|
|
60
|
+
* - Local/Ollama providers skip the API Key step.
|
|
61
|
+
* - Reasoning-capable models (o1, o3, codex, gpt-5.x) show the Reasoning step.
|
|
62
|
+
*/
|
|
63
|
+
function getWizardSteps(provider, model) {
|
|
64
|
+
const isLocalProvider = provider === 'local' || provider === 'ollama';
|
|
65
|
+
const showReasoningStep = model && isReasoningCapableModel(model);
|
|
66
|
+
if (isLocalProvider) {
|
|
67
|
+
const steps = [
|
|
68
|
+
{ key: 'provider', label: 'Provider' },
|
|
69
|
+
{ key: 'model', label: 'Model' },
|
|
70
|
+
];
|
|
71
|
+
if (showReasoningStep) {
|
|
72
|
+
steps.push({ key: 'reasoningEffort', label: 'Reasoning' });
|
|
73
|
+
}
|
|
74
|
+
steps.push({ key: 'mode', label: 'Mode' });
|
|
75
|
+
return steps;
|
|
76
|
+
}
|
|
77
|
+
const steps = [
|
|
78
|
+
{ key: 'provider', label: 'Provider' },
|
|
79
|
+
{ key: 'model', label: 'Model' },
|
|
80
|
+
];
|
|
81
|
+
if (showReasoningStep) {
|
|
82
|
+
steps.push({ key: 'reasoningEffort', label: 'Reasoning' });
|
|
83
|
+
}
|
|
84
|
+
steps.push({ key: 'apiKey', label: 'API Key' });
|
|
85
|
+
steps.push({ key: 'mode', label: 'Mode' });
|
|
86
|
+
return steps;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Display step progress indicator for the setup wizard
|
|
90
|
+
*/
|
|
91
|
+
function showStepProgress(currentStep, provider, model) {
|
|
92
|
+
// Don't show progress for setupType (it's the entry point)
|
|
93
|
+
if (currentStep === 'setupType' || currentStep === 'complete') {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const steps = getWizardSteps(provider, model);
|
|
97
|
+
const currentIndex = steps.findIndex((s) => s.key === currentStep);
|
|
98
|
+
if (currentIndex === -1) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const progress = steps
|
|
102
|
+
.map((step, i) => {
|
|
103
|
+
if (i < currentIndex)
|
|
104
|
+
return chalk.green(`✓ ${step.label}`);
|
|
105
|
+
if (i === currentIndex)
|
|
106
|
+
return chalk.cyan(`● ${step.label}`);
|
|
107
|
+
return chalk.gray(`○ ${step.label}`);
|
|
108
|
+
})
|
|
109
|
+
.join(' ');
|
|
110
|
+
console.log(`\n ${progress}\n`);
|
|
111
|
+
}
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Setup Command Validation
|
|
114
|
+
// ============================================================================
|
|
50
115
|
/**
|
|
51
116
|
* Validate setup command options with comprehensive validation
|
|
52
117
|
*/
|
|
53
118
|
function validateSetupCommand(options) {
|
|
54
|
-
// Basic structure validation
|
|
55
119
|
const validated = SetupCommandSchema.parse(options);
|
|
56
120
|
// Business logic validation
|
|
57
|
-
if (!validated.interactive && !validated.provider) {
|
|
58
|
-
throw new Error('Provider required in non-interactive mode. Use --provider option.');
|
|
121
|
+
if (!validated.interactive && !validated.provider && !validated.quickStart) {
|
|
122
|
+
throw new Error('Provider required in non-interactive mode. Use --provider or --quick-start option.');
|
|
59
123
|
}
|
|
60
124
|
return validated;
|
|
61
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Main setup command handler
|
|
128
|
+
*/
|
|
62
129
|
export async function handleSetupCommand(options) {
|
|
63
|
-
// Validate command with Zod
|
|
64
130
|
const validated = validateSetupCommand(options);
|
|
65
131
|
logger.debug(`Validated setup command options: ${JSON.stringify(validated, null, 2)}`);
|
|
66
|
-
// Check if setup is already complete
|
|
132
|
+
// Check if setup is already complete
|
|
67
133
|
const needsSetup = await requiresSetup();
|
|
68
|
-
if (!needsSetup) {
|
|
69
|
-
// Setup is already complete - handle based on mode
|
|
134
|
+
if (!needsSetup && !validated.force) {
|
|
70
135
|
if (!validated.interactive) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
console.error(chalk.red('❌ Setup is already complete.'));
|
|
137
|
+
console.error(chalk.gray(' Use --force to overwrite, or run interactively for options.'));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
// Interactive mode: show settings menu
|
|
141
|
+
await showSettingsMenu();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Handle quick start
|
|
145
|
+
if (validated.quickStart) {
|
|
146
|
+
await handleQuickStart();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Handle interactive full setup
|
|
150
|
+
if (validated.interactive && !validated.provider) {
|
|
151
|
+
await handleInteractiveSetup(validated);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Handle non-interactive setup with provided options
|
|
155
|
+
await handleNonInteractiveSetup(validated);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Quick start flow - uses Google Gemini with minimal prompts
|
|
159
|
+
*/
|
|
160
|
+
async function handleQuickStart() {
|
|
161
|
+
console.log(chalk.cyan('\n🚀 Quick Start with Google Gemini\n'));
|
|
162
|
+
p.intro(chalk.cyan('Quick Setup'));
|
|
163
|
+
const provider = 'google';
|
|
164
|
+
const model = getDefaultModelForProvider(provider) || 'gemini-2.5-pro';
|
|
165
|
+
const apiKeyVar = getProviderEnvVar(provider);
|
|
166
|
+
let apiKeySkipped = false;
|
|
167
|
+
// Check if API key exists
|
|
168
|
+
const hasKey = hasApiKeyConfigured(provider);
|
|
169
|
+
if (!hasKey) {
|
|
170
|
+
p.note(`Google Gemini is ${chalk.green('free')} to use!\n\n` +
|
|
171
|
+
`We'll help you get an API key in just a few seconds.`, 'Free AI Access');
|
|
172
|
+
const result = await interactiveApiKeySetup(provider, {
|
|
173
|
+
exitOnCancel: false, // Don't exit - allow skipping
|
|
174
|
+
model,
|
|
175
|
+
});
|
|
176
|
+
if (result.cancelled) {
|
|
177
|
+
p.cancel('Setup cancelled');
|
|
178
|
+
process.exit(0);
|
|
93
179
|
}
|
|
180
|
+
if (result.skipped || !result.success) {
|
|
181
|
+
apiKeySkipped = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
p.log.success(`API key for ${getProviderDisplayName(provider)} already configured`);
|
|
94
186
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
187
|
+
// Ask about default mode
|
|
188
|
+
const defaultMode = await selectDefaultMode();
|
|
189
|
+
// Handle cancellation
|
|
190
|
+
if (defaultMode === null) {
|
|
191
|
+
p.cancel('Setup cancelled');
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
194
|
+
// Save preferences
|
|
195
|
+
const preferencesOptions = {
|
|
196
|
+
provider,
|
|
197
|
+
model,
|
|
198
|
+
defaultMode,
|
|
199
|
+
setupCompleted: true,
|
|
200
|
+
apiKeyPending: apiKeySkipped,
|
|
201
|
+
};
|
|
202
|
+
// Only include apiKeyVar if not skipped
|
|
203
|
+
if (!apiKeySkipped) {
|
|
204
|
+
preferencesOptions.apiKeyVar = apiKeyVar;
|
|
205
|
+
}
|
|
206
|
+
const preferences = createInitialPreferences(preferencesOptions);
|
|
207
|
+
await saveGlobalPreferences(preferences);
|
|
208
|
+
capture('dexto_setup', {
|
|
209
|
+
provider,
|
|
210
|
+
model,
|
|
211
|
+
setupMode: 'interactive',
|
|
212
|
+
setupVariant: 'quick-start',
|
|
213
|
+
defaultMode,
|
|
214
|
+
apiKeySkipped,
|
|
215
|
+
});
|
|
216
|
+
showSetupComplete(provider, model, defaultMode, apiKeySkipped);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Full interactive setup flow with wizard navigation.
|
|
220
|
+
* Users can go back to previous steps to change their selections.
|
|
221
|
+
*/
|
|
222
|
+
async function handleInteractiveSetup(_options) {
|
|
223
|
+
console.log(chalk.cyan('\n🗿 Dexto Setup\n'));
|
|
224
|
+
p.intro(chalk.cyan("Let's configure your AI agent"));
|
|
225
|
+
// Initialize wizard state
|
|
226
|
+
let state = { step: 'setupType' };
|
|
227
|
+
// Wizard loop - process steps until complete
|
|
228
|
+
while (state.step !== 'complete') {
|
|
229
|
+
switch (state.step) {
|
|
230
|
+
case 'setupType':
|
|
231
|
+
state = await wizardStepSetupType(state);
|
|
232
|
+
break;
|
|
233
|
+
case 'provider':
|
|
234
|
+
state = await wizardStepProvider(state);
|
|
235
|
+
break;
|
|
236
|
+
case 'model':
|
|
237
|
+
state = await wizardStepModel(state);
|
|
238
|
+
break;
|
|
239
|
+
case 'reasoningEffort':
|
|
240
|
+
state = await wizardStepReasoningEffort(state);
|
|
241
|
+
break;
|
|
242
|
+
case 'apiKey':
|
|
243
|
+
state = await wizardStepApiKey(state);
|
|
244
|
+
break;
|
|
245
|
+
case 'mode':
|
|
246
|
+
state = await wizardStepMode(state);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Save preferences and show completion
|
|
251
|
+
// Quick start handles its own saving, so skip for that path
|
|
252
|
+
if (!state.quickStartHandled) {
|
|
253
|
+
await saveWizardPreferences(state);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Wizard Step: Setup Type (Quick Start vs Custom)
|
|
258
|
+
*/
|
|
259
|
+
async function wizardStepSetupType(state) {
|
|
260
|
+
const setupType = await p.select({
|
|
261
|
+
message: 'How would you like to set up Dexto?',
|
|
262
|
+
options: [
|
|
263
|
+
{
|
|
264
|
+
value: 'quick',
|
|
265
|
+
label: `${chalk.green('●')} Quick Start`,
|
|
266
|
+
hint: 'Google Gemini (free) - recommended for new users',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
value: 'custom',
|
|
270
|
+
label: `${chalk.blue('●')} Custom Setup`,
|
|
271
|
+
hint: 'Choose your provider (OpenAI, Anthropic, Ollama, etc.)',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
if (p.isCancel(setupType)) {
|
|
276
|
+
p.cancel('Setup cancelled');
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
if (setupType === 'quick') {
|
|
280
|
+
// Quick start bypasses the wizard - handle it directly
|
|
281
|
+
await handleQuickStart();
|
|
282
|
+
return { ...state, step: 'complete', quickStartHandled: true };
|
|
283
|
+
}
|
|
284
|
+
return { ...state, step: 'provider', setupType: 'custom' };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Wizard Step: Provider Selection
|
|
288
|
+
*/
|
|
289
|
+
async function wizardStepProvider(state) {
|
|
290
|
+
showStepProgress('provider', state.provider);
|
|
291
|
+
const provider = await selectProvider();
|
|
292
|
+
if (provider === null) {
|
|
293
|
+
p.cancel('Setup cancelled');
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
if (provider === '_back') {
|
|
297
|
+
return { ...state, step: 'setupType', provider: undefined };
|
|
298
|
+
}
|
|
299
|
+
return { ...state, step: 'model', provider };
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Wizard Step: Model Selection
|
|
303
|
+
*/
|
|
304
|
+
async function wizardStepModel(state) {
|
|
305
|
+
const provider = state.provider;
|
|
306
|
+
showStepProgress('model', provider);
|
|
307
|
+
// Handle local providers with special setup flow
|
|
308
|
+
if (provider === 'local') {
|
|
309
|
+
const localResult = await setupLocalModels();
|
|
310
|
+
// Only proceed if user actually selected a model
|
|
311
|
+
// (handles cancelled, back, skipped, and any other incomplete states)
|
|
312
|
+
if (!hasSelectedModel(localResult)) {
|
|
313
|
+
return { ...state, step: 'provider', model: undefined };
|
|
314
|
+
}
|
|
315
|
+
const model = getModelFromResult(localResult);
|
|
316
|
+
// Check if model supports reasoning effort
|
|
317
|
+
const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'mode';
|
|
318
|
+
return { ...state, step: nextStep, model };
|
|
319
|
+
}
|
|
320
|
+
if (provider === 'ollama') {
|
|
321
|
+
const ollamaResult = await setupOllamaModels();
|
|
322
|
+
// Only proceed if user actually selected a model
|
|
323
|
+
if (!hasSelectedModel(ollamaResult)) {
|
|
324
|
+
return { ...state, step: 'provider', model: undefined };
|
|
325
|
+
}
|
|
326
|
+
const model = getModelFromResult(ollamaResult);
|
|
327
|
+
// Check if model supports reasoning effort
|
|
328
|
+
const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'mode';
|
|
329
|
+
return { ...state, step: nextStep, model };
|
|
330
|
+
}
|
|
331
|
+
// Handle baseURL for providers that need it
|
|
332
|
+
let baseURL;
|
|
333
|
+
if (providerRequiresBaseURL(provider)) {
|
|
334
|
+
const result = await promptForBaseURL(provider);
|
|
335
|
+
if (result === null) {
|
|
336
|
+
// User cancelled - go back to provider selection
|
|
337
|
+
return { ...state, step: 'provider', model: undefined, baseURL: undefined };
|
|
338
|
+
}
|
|
339
|
+
baseURL = result;
|
|
340
|
+
}
|
|
341
|
+
// Cloud provider model selection with back option
|
|
342
|
+
const model = await selectModelWithBack(provider);
|
|
343
|
+
if (model === '_back') {
|
|
344
|
+
return { ...state, step: 'provider', model: undefined, baseURL: undefined };
|
|
345
|
+
}
|
|
346
|
+
// Check if model supports reasoning effort
|
|
347
|
+
const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'apiKey';
|
|
348
|
+
return { ...state, step: nextStep, model, baseURL };
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Wizard Step: Reasoning Effort Selection (for OpenAI reasoning models)
|
|
352
|
+
*/
|
|
353
|
+
async function wizardStepReasoningEffort(state) {
|
|
354
|
+
const provider = state.provider;
|
|
355
|
+
const model = state.model;
|
|
356
|
+
const isLocalProvider = provider === 'local' || provider === 'ollama';
|
|
357
|
+
showStepProgress('reasoningEffort', provider, model);
|
|
358
|
+
const result = await p.select({
|
|
359
|
+
message: 'Select reasoning effort level',
|
|
360
|
+
options: [
|
|
361
|
+
{
|
|
362
|
+
value: 'medium',
|
|
363
|
+
label: 'Medium (Recommended)',
|
|
364
|
+
hint: 'Balanced reasoning for most tasks',
|
|
365
|
+
},
|
|
366
|
+
{ value: 'low', label: 'Low', hint: 'Light reasoning, fast responses' },
|
|
367
|
+
{
|
|
368
|
+
value: 'minimal',
|
|
369
|
+
label: 'Minimal',
|
|
370
|
+
hint: 'Barely any reasoning, very fast',
|
|
371
|
+
},
|
|
372
|
+
{ value: 'high', label: 'High', hint: 'More thorough reasoning' },
|
|
373
|
+
{
|
|
374
|
+
value: 'xhigh',
|
|
375
|
+
label: 'Extra High',
|
|
376
|
+
hint: 'Maximum reasoning, slower responses',
|
|
377
|
+
},
|
|
378
|
+
{ value: 'none', label: 'None', hint: 'Disable reasoning' },
|
|
379
|
+
{ value: '_back', label: chalk.gray('← Back'), hint: 'Change model' },
|
|
380
|
+
],
|
|
381
|
+
});
|
|
382
|
+
if (p.isCancel(result)) {
|
|
383
|
+
p.cancel('Setup cancelled');
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
if (result === '_back') {
|
|
387
|
+
return { ...state, step: 'model', reasoningEffort: undefined };
|
|
388
|
+
}
|
|
389
|
+
// Determine next step based on provider type
|
|
390
|
+
const nextStep = isLocalProvider ? 'mode' : 'apiKey';
|
|
391
|
+
return { ...state, step: nextStep, reasoningEffort: result };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Wizard Step: API Key Configuration
|
|
395
|
+
*/
|
|
396
|
+
async function wizardStepApiKey(state) {
|
|
397
|
+
const provider = state.provider;
|
|
398
|
+
const model = state.model;
|
|
399
|
+
showStepProgress('apiKey', provider, model);
|
|
400
|
+
const hasKey = hasApiKeyConfigured(provider);
|
|
401
|
+
const needsApiKey = requiresApiKey(provider);
|
|
402
|
+
if (needsApiKey && !hasKey) {
|
|
403
|
+
const result = await interactiveApiKeySetup(provider, {
|
|
404
|
+
exitOnCancel: false,
|
|
405
|
+
model,
|
|
406
|
+
});
|
|
407
|
+
if (result.cancelled) {
|
|
408
|
+
// Go back to reasoning effort if model supports it, otherwise model selection
|
|
409
|
+
const prevStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'model';
|
|
410
|
+
return { ...state, step: prevStep, apiKeySkipped: undefined };
|
|
411
|
+
}
|
|
412
|
+
const apiKeySkipped = result.skipped || !result.success;
|
|
413
|
+
return { ...state, step: 'mode', apiKeySkipped };
|
|
414
|
+
}
|
|
415
|
+
else if (needsApiKey && hasKey) {
|
|
416
|
+
p.log.success(`API key for ${getProviderDisplayName(provider)} already configured`);
|
|
417
|
+
}
|
|
418
|
+
else if (!needsApiKey) {
|
|
419
|
+
p.log.info(`${getProviderDisplayName(provider)} does not require an API key`);
|
|
420
|
+
}
|
|
421
|
+
return { ...state, step: 'mode', apiKeySkipped: false };
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Wizard Step: Default Mode Selection
|
|
425
|
+
*/
|
|
426
|
+
async function wizardStepMode(state) {
|
|
427
|
+
const provider = state.provider;
|
|
428
|
+
const model = state.model;
|
|
429
|
+
const isLocalProvider = provider === 'local' || provider === 'ollama';
|
|
430
|
+
const hasReasoningStep = isReasoningCapableModel(model);
|
|
431
|
+
showStepProgress('mode', provider, model);
|
|
432
|
+
const mode = await selectDefaultModeWithBack();
|
|
433
|
+
if (mode === '_back') {
|
|
434
|
+
// Go back to previous step based on provider type and model capabilities
|
|
435
|
+
if (isLocalProvider) {
|
|
436
|
+
// Local: reasoning effort -> model
|
|
437
|
+
return {
|
|
438
|
+
...state,
|
|
439
|
+
step: hasReasoningStep ? 'reasoningEffort' : 'model',
|
|
440
|
+
defaultMode: undefined,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
// Cloud: always go back to apiKey (reasoning effort comes before apiKey)
|
|
444
|
+
return { ...state, step: 'apiKey', defaultMode: undefined };
|
|
445
|
+
}
|
|
446
|
+
return { ...state, step: 'complete', defaultMode: mode };
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Select model with back option
|
|
450
|
+
*/
|
|
451
|
+
async function selectModelWithBack(provider) {
|
|
452
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
453
|
+
if (providerInfo?.models && providerInfo.models.length > 0) {
|
|
454
|
+
const modelOptions = providerInfo.models.map((m) => ({
|
|
455
|
+
value: m.name,
|
|
456
|
+
label: m.displayName || m.name,
|
|
457
|
+
}));
|
|
458
|
+
const result = await p.select({
|
|
459
|
+
message: `Select a model for ${getProviderDisplayName(provider)}`,
|
|
460
|
+
options: [
|
|
461
|
+
...modelOptions,
|
|
462
|
+
{ value: '_back', label: chalk.gray('← Back'), hint: 'Change provider' },
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
if (p.isCancel(result)) {
|
|
466
|
+
p.cancel('Setup cancelled');
|
|
467
|
+
process.exit(0);
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
// For providers that accept any model, show text input with back hint
|
|
472
|
+
p.log.info(chalk.gray('Press Ctrl+C to go back'));
|
|
473
|
+
const defaultModel = providerInfo?.models?.find((m) => m.default)?.name;
|
|
474
|
+
const model = await p.text({
|
|
475
|
+
message: `Enter model name for ${getProviderDisplayName(provider)}`,
|
|
476
|
+
placeholder: defaultModel || 'e.g., gpt-4-turbo',
|
|
477
|
+
validate: (value) => {
|
|
478
|
+
if (!value.trim())
|
|
479
|
+
return 'Model name is required';
|
|
480
|
+
return undefined;
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
if (p.isCancel(model)) {
|
|
484
|
+
return '_back';
|
|
485
|
+
}
|
|
486
|
+
return model;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Select default mode with back option
|
|
490
|
+
*/
|
|
491
|
+
async function selectDefaultModeWithBack() {
|
|
492
|
+
const result = await p.select({
|
|
493
|
+
message: 'How do you want to use Dexto by default?',
|
|
494
|
+
options: [
|
|
495
|
+
{
|
|
496
|
+
value: 'web',
|
|
497
|
+
label: `${chalk.blue('●')} Web UI`,
|
|
498
|
+
hint: 'Opens in browser at localhost:3000 (recommended)',
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
value: 'cli',
|
|
502
|
+
label: `${chalk.green('●')} Terminal CLI`,
|
|
503
|
+
hint: 'Interactive command-line interface',
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
value: 'server',
|
|
507
|
+
label: `${chalk.rgb(255, 165, 0)('●')} API Server`,
|
|
508
|
+
hint: 'REST API for programmatic access',
|
|
509
|
+
},
|
|
510
|
+
{ value: '_back', label: chalk.gray('← Back'), hint: 'Go to previous step' },
|
|
511
|
+
],
|
|
512
|
+
});
|
|
513
|
+
if (p.isCancel(result)) {
|
|
514
|
+
return '_back';
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Save wizard state to preferences
|
|
520
|
+
*/
|
|
521
|
+
async function saveWizardPreferences(state) {
|
|
522
|
+
const provider = state.provider;
|
|
523
|
+
const model = state.model;
|
|
524
|
+
const defaultMode = state.defaultMode;
|
|
525
|
+
const apiKeySkipped = state.apiKeySkipped || false;
|
|
526
|
+
const needsApiKey = requiresApiKey(provider);
|
|
527
|
+
const apiKeyVar = getProviderEnvVar(provider);
|
|
528
|
+
// For local provider, sync the active model in state.json
|
|
529
|
+
if (provider === 'local') {
|
|
530
|
+
await setActiveModel(model);
|
|
531
|
+
}
|
|
532
|
+
// Build preferences options
|
|
533
|
+
const preferencesOptions = {
|
|
534
|
+
provider,
|
|
535
|
+
model,
|
|
536
|
+
defaultMode,
|
|
537
|
+
setupCompleted: true,
|
|
538
|
+
apiKeyPending: apiKeySkipped,
|
|
539
|
+
};
|
|
540
|
+
if (needsApiKey && !apiKeySkipped) {
|
|
541
|
+
preferencesOptions.apiKeyVar = apiKeyVar;
|
|
100
542
|
}
|
|
101
|
-
|
|
102
|
-
|
|
543
|
+
if (state.baseURL) {
|
|
544
|
+
preferencesOptions.baseURL = state.baseURL;
|
|
545
|
+
}
|
|
546
|
+
if (state.reasoningEffort) {
|
|
547
|
+
preferencesOptions.reasoningEffort = state.reasoningEffort;
|
|
548
|
+
}
|
|
549
|
+
const preferences = createInitialPreferences(preferencesOptions);
|
|
550
|
+
await saveGlobalPreferences(preferences);
|
|
551
|
+
// Analytics
|
|
552
|
+
capture('dexto_setup', {
|
|
553
|
+
provider,
|
|
554
|
+
model,
|
|
555
|
+
setupMode: 'interactive',
|
|
556
|
+
setupVariant: 'custom',
|
|
557
|
+
defaultMode,
|
|
558
|
+
hasBaseURL: Boolean(state.baseURL),
|
|
559
|
+
apiKeySkipped,
|
|
560
|
+
});
|
|
561
|
+
showSetupComplete(provider, model, defaultMode, apiKeySkipped);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Non-interactive setup with CLI options
|
|
565
|
+
*/
|
|
566
|
+
async function handleNonInteractiveSetup(options) {
|
|
567
|
+
const provider = options.provider;
|
|
568
|
+
const model = options.model || getDefaultModel(provider);
|
|
103
569
|
if (!model) {
|
|
104
|
-
|
|
570
|
+
console.error(chalk.red(`❌ Model is required for provider '${provider}'.`));
|
|
571
|
+
console.error(chalk.gray(` Use --model option to specify a model.`));
|
|
572
|
+
process.exit(1);
|
|
105
573
|
}
|
|
106
|
-
const apiKeyVar =
|
|
574
|
+
const apiKeyVar = getProviderEnvVar(provider);
|
|
107
575
|
const hadApiKeyBefore = Boolean(resolveApiKeyForProvider(provider));
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
576
|
+
const preferences = createInitialPreferences({
|
|
577
|
+
provider,
|
|
578
|
+
model,
|
|
579
|
+
apiKeyVar,
|
|
580
|
+
defaultAgent: options.defaultAgent,
|
|
581
|
+
setupCompleted: true,
|
|
582
|
+
});
|
|
111
583
|
await saveGlobalPreferences(preferences);
|
|
112
|
-
//
|
|
584
|
+
// For local provider, sync the active model in state.json
|
|
585
|
+
if (provider === 'local') {
|
|
586
|
+
await setActiveModel(model);
|
|
587
|
+
}
|
|
113
588
|
capture('dexto_setup', {
|
|
114
589
|
provider,
|
|
115
590
|
model,
|
|
116
591
|
hadApiKeyBefore,
|
|
117
|
-
setupMode:
|
|
592
|
+
setupMode: 'non-interactive',
|
|
118
593
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
594
|
+
console.log(chalk.green('\n✨ Setup complete! Dexto is ready to use.\n'));
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Settings menu for users who already have setup complete.
|
|
598
|
+
* Loops back to menu after each action until user exits.
|
|
599
|
+
*/
|
|
600
|
+
async function showSettingsMenu() {
|
|
601
|
+
p.intro(chalk.cyan('⚙️ Dexto Settings'));
|
|
602
|
+
// Settings menu loop - returns to menu after each action
|
|
603
|
+
while (true) {
|
|
604
|
+
// Load current preferences (refresh each iteration)
|
|
605
|
+
let currentPrefs;
|
|
606
|
+
try {
|
|
607
|
+
currentPrefs = await loadGlobalPreferences();
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
currentPrefs = null;
|
|
124
611
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
612
|
+
// Show current configuration
|
|
613
|
+
if (currentPrefs) {
|
|
614
|
+
const currentConfig = [
|
|
615
|
+
`Provider: ${chalk.cyan(getProviderDisplayName(currentPrefs.llm.provider))}`,
|
|
616
|
+
`Model: ${chalk.cyan(currentPrefs.llm.model)}`,
|
|
617
|
+
`Default Mode: ${chalk.cyan(currentPrefs.defaults.defaultMode)}`,
|
|
618
|
+
...(currentPrefs.llm.baseURL
|
|
619
|
+
? [`Base URL: ${chalk.cyan(currentPrefs.llm.baseURL)}`]
|
|
620
|
+
: []),
|
|
621
|
+
...(currentPrefs.llm.reasoningEffort
|
|
622
|
+
? [`Reasoning Effort: ${chalk.cyan(currentPrefs.llm.reasoningEffort)}`]
|
|
623
|
+
: []),
|
|
624
|
+
].join('\n');
|
|
625
|
+
p.note(currentConfig, 'Current Configuration');
|
|
128
626
|
}
|
|
627
|
+
const action = await p.select({
|
|
628
|
+
message: 'What would you like to do?',
|
|
629
|
+
options: [
|
|
630
|
+
{
|
|
631
|
+
value: 'model',
|
|
632
|
+
label: 'Change model',
|
|
633
|
+
hint: `Currently: ${currentPrefs?.llm.provider || 'not set'} / ${currentPrefs?.llm.model || 'not set'}`,
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
value: 'mode',
|
|
637
|
+
label: 'Change default mode',
|
|
638
|
+
hint: `Currently: ${currentPrefs?.defaults.defaultMode || 'web'}`,
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
value: 'apikey',
|
|
642
|
+
label: 'Update API key',
|
|
643
|
+
hint: 'Re-enter your API key',
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
value: 'reset',
|
|
647
|
+
label: 'Reset to defaults',
|
|
648
|
+
hint: 'Start fresh with a new configuration',
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
value: 'file',
|
|
652
|
+
label: 'View preferences file',
|
|
653
|
+
hint: 'See where your settings are stored',
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
value: 'exit',
|
|
657
|
+
label: 'Exit',
|
|
658
|
+
hint: 'Done making changes',
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
});
|
|
662
|
+
// Exit conditions
|
|
663
|
+
if (p.isCancel(action) || action === 'exit') {
|
|
664
|
+
p.outro(chalk.gray('Settings closed'));
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
// Execute action and loop back (except for reset which exits)
|
|
668
|
+
switch (action) {
|
|
669
|
+
case 'model':
|
|
670
|
+
await changeModel(); // Always prompt for provider selection
|
|
671
|
+
break;
|
|
672
|
+
case 'mode':
|
|
673
|
+
await changeDefaultMode();
|
|
674
|
+
break;
|
|
675
|
+
case 'apikey':
|
|
676
|
+
await updateApiKey(currentPrefs?.llm.provider);
|
|
677
|
+
break;
|
|
678
|
+
case 'reset': {
|
|
679
|
+
// Reset exits the menu after completion, but returns to menu if cancelled
|
|
680
|
+
const resetCompleted = await resetSetup();
|
|
681
|
+
if (resetCompleted) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case 'file':
|
|
687
|
+
showPreferencesFilePath();
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
// Add separator before next iteration
|
|
691
|
+
console.log('');
|
|
129
692
|
}
|
|
130
|
-
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Change model setting (includes provider selection)
|
|
696
|
+
*/
|
|
697
|
+
async function changeModel(currentProvider) {
|
|
698
|
+
const provider = currentProvider || (await selectProvider());
|
|
699
|
+
// Handle cancellation or back from selectProvider
|
|
700
|
+
if (provider === null || provider === '_back') {
|
|
701
|
+
p.log.warn('Model change cancelled');
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
// Special handling for local providers - use dedicated setup flows
|
|
705
|
+
if (provider === 'local') {
|
|
706
|
+
const localResult = await setupLocalModels();
|
|
707
|
+
// Only proceed if user actually selected a model
|
|
708
|
+
// (handles cancelled, back, skipped - returns to menu without saving)
|
|
709
|
+
if (!hasSelectedModel(localResult)) {
|
|
710
|
+
p.log.warn('Model change cancelled');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const model = getModelFromResult(localResult);
|
|
714
|
+
const llmUpdate = {
|
|
715
|
+
provider,
|
|
716
|
+
model,
|
|
717
|
+
};
|
|
718
|
+
// Ask for reasoning effort if applicable
|
|
719
|
+
if (isReasoningCapableModel(model)) {
|
|
720
|
+
const reasoningEffort = await selectReasoningEffort();
|
|
721
|
+
if (reasoningEffort !== null) {
|
|
722
|
+
llmUpdate.reasoningEffort = reasoningEffort;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
await updateGlobalPreferences({ llm: llmUpdate });
|
|
726
|
+
p.log.success(`Model changed to ${model}`);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (provider === 'ollama') {
|
|
730
|
+
const ollamaResult = await setupOllamaModels();
|
|
731
|
+
// Only proceed if user actually selected a model
|
|
732
|
+
if (!hasSelectedModel(ollamaResult)) {
|
|
733
|
+
p.log.warn('Model change cancelled');
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const model = getModelFromResult(ollamaResult);
|
|
737
|
+
const llmUpdate = {
|
|
738
|
+
provider,
|
|
739
|
+
model,
|
|
740
|
+
};
|
|
741
|
+
// Ask for reasoning effort if applicable
|
|
742
|
+
if (isReasoningCapableModel(model)) {
|
|
743
|
+
const reasoningEffort = await selectReasoningEffort();
|
|
744
|
+
if (reasoningEffort !== null) {
|
|
745
|
+
llmUpdate.reasoningEffort = reasoningEffort;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
await updateGlobalPreferences({ llm: llmUpdate });
|
|
749
|
+
p.log.success(`Model changed to ${model}`);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
// Standard flow for cloud providers
|
|
753
|
+
const model = await selectModel(provider);
|
|
754
|
+
// Handle cancellation from selectModel
|
|
755
|
+
if (model === null) {
|
|
756
|
+
p.log.warn('Model change cancelled');
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
const apiKeyVar = getProviderEnvVar(provider);
|
|
760
|
+
const needsApiKey = requiresApiKey(provider);
|
|
761
|
+
const llmUpdate = {
|
|
762
|
+
provider,
|
|
763
|
+
model,
|
|
764
|
+
};
|
|
765
|
+
// Only include apiKey for providers that need it
|
|
766
|
+
if (needsApiKey) {
|
|
767
|
+
llmUpdate.apiKey = `$${apiKeyVar}`;
|
|
768
|
+
}
|
|
769
|
+
// Ask for reasoning effort if applicable
|
|
770
|
+
if (isReasoningCapableModel(model)) {
|
|
771
|
+
const reasoningEffort = await selectReasoningEffort();
|
|
772
|
+
if (reasoningEffort !== null) {
|
|
773
|
+
llmUpdate.reasoningEffort = reasoningEffort;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
await updateGlobalPreferences({ llm: llmUpdate });
|
|
777
|
+
p.log.success(`Model changed to ${model}`);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Change default mode setting
|
|
781
|
+
*/
|
|
782
|
+
async function changeDefaultMode() {
|
|
783
|
+
const mode = await selectDefaultMode();
|
|
784
|
+
// Handle cancellation
|
|
785
|
+
if (mode === null) {
|
|
786
|
+
p.log.warn('Mode change cancelled');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
await updateGlobalPreferences({
|
|
790
|
+
defaults: { defaultMode: mode },
|
|
791
|
+
});
|
|
792
|
+
p.log.success(`Default mode changed to ${mode}`);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Update API key for current provider
|
|
796
|
+
*/
|
|
797
|
+
async function updateApiKey(currentProvider) {
|
|
798
|
+
const provider = currentProvider || (await selectProvider());
|
|
799
|
+
// Handle cancellation or back from selectProvider
|
|
800
|
+
if (provider === null || provider === '_back') {
|
|
801
|
+
p.log.warn('API key update cancelled');
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
// Handle providers that use non-API-key authentication
|
|
805
|
+
if (provider === 'vertex') {
|
|
806
|
+
p.note(`Google Vertex AI uses Application Default Credentials (ADC).\n\n` +
|
|
807
|
+
`To authenticate:\n` +
|
|
808
|
+
` 1. Install gcloud CLI: https://cloud.google.com/sdk/docs/install\n` +
|
|
809
|
+
` 2. Run: gcloud auth application-default login\n` +
|
|
810
|
+
` 3. Set GOOGLE_VERTEX_PROJECT environment variable`, 'Google Cloud Authentication');
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (provider === 'bedrock') {
|
|
814
|
+
p.note(`Amazon Bedrock uses AWS credentials.\n\n` +
|
|
815
|
+
`To authenticate, set these environment variables:\n` +
|
|
816
|
+
` • AWS_REGION (required)\n` +
|
|
817
|
+
` • AWS_ACCESS_KEY_ID (required)\n` +
|
|
818
|
+
` • AWS_SECRET_ACCESS_KEY (required)\n` +
|
|
819
|
+
` • AWS_SESSION_TOKEN (optional, for temporary credentials)`, 'AWS Authentication');
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
// For openai-compatible and litellm, API keys are optional but allowed
|
|
823
|
+
// Show a note but still allow setting one
|
|
824
|
+
if (provider === 'openai-compatible' || provider === 'litellm') {
|
|
825
|
+
const wantsKey = await p.confirm({
|
|
826
|
+
message: `API key is optional for ${getProviderDisplayName(provider)}. Set one anyway?`,
|
|
827
|
+
initialValue: true,
|
|
828
|
+
});
|
|
829
|
+
if (p.isCancel(wantsKey) || !wantsKey) {
|
|
830
|
+
p.log.info('Skipped API key setup');
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const result = await interactiveApiKeySetup(provider, {
|
|
835
|
+
exitOnCancel: false,
|
|
836
|
+
skipVerification: false,
|
|
837
|
+
});
|
|
838
|
+
if (result.success) {
|
|
839
|
+
p.log.success(`API key updated for ${getProviderDisplayName(provider)}`);
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
p.log.warn('API key update cancelled');
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Reset setup to start fresh
|
|
847
|
+
*/
|
|
848
|
+
async function resetSetup() {
|
|
849
|
+
const confirm = await p.confirm({
|
|
850
|
+
message: 'This will erase your current configuration. Continue?',
|
|
851
|
+
initialValue: false,
|
|
852
|
+
});
|
|
853
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
854
|
+
p.log.info('Reset cancelled');
|
|
855
|
+
return false; // Indicate reset was cancelled
|
|
856
|
+
}
|
|
857
|
+
// Run full setup - this will complete a fresh setup
|
|
858
|
+
await handleInteractiveSetup({
|
|
859
|
+
interactive: true,
|
|
860
|
+
force: true,
|
|
861
|
+
defaultAgent: 'coding-agent',
|
|
862
|
+
quickStart: false,
|
|
863
|
+
});
|
|
864
|
+
return true; // Indicate reset completed
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Show preferences file path
|
|
868
|
+
*/
|
|
869
|
+
function showPreferencesFilePath() {
|
|
870
|
+
const prefsPath = getGlobalPreferencesPath();
|
|
871
|
+
p.note([
|
|
872
|
+
`Your preferences are stored at:`,
|
|
873
|
+
``,
|
|
874
|
+
` ${chalk.cyan(prefsPath)}`,
|
|
875
|
+
``,
|
|
876
|
+
`You can edit this file directly with any text editor.`,
|
|
877
|
+
`Changes take effect on the next run of dexto.`,
|
|
878
|
+
``,
|
|
879
|
+
chalk.gray('Example commands:'),
|
|
880
|
+
chalk.gray(` code ${prefsPath} # Open in VS Code`),
|
|
881
|
+
chalk.gray(` nano ${prefsPath} # Edit in terminal`),
|
|
882
|
+
chalk.gray(` cat ${prefsPath} # View contents`),
|
|
883
|
+
].join('\n'), 'Preferences File Location');
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Select default mode interactively
|
|
887
|
+
* Returns null if user cancels
|
|
888
|
+
*/
|
|
889
|
+
async function selectDefaultMode() {
|
|
890
|
+
const mode = await p.select({
|
|
891
|
+
message: 'How do you want to use Dexto by default?',
|
|
892
|
+
options: [
|
|
893
|
+
{
|
|
894
|
+
value: 'web',
|
|
895
|
+
label: `${chalk.blue('●')} Web UI`,
|
|
896
|
+
hint: 'Opens in browser at localhost:3000 (recommended)',
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
value: 'cli',
|
|
900
|
+
label: `${chalk.green('●')} Terminal CLI`,
|
|
901
|
+
hint: 'Interactive command-line interface',
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
value: 'server',
|
|
905
|
+
label: `${chalk.rgb(255, 165, 0)('●')} API Server`,
|
|
906
|
+
hint: 'REST API for programmatic access',
|
|
907
|
+
},
|
|
908
|
+
],
|
|
909
|
+
});
|
|
910
|
+
if (p.isCancel(mode)) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
return mode;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Select reasoning effort level for reasoning-capable models
|
|
917
|
+
* Used in settings menu when changing to a reasoning-capable model
|
|
918
|
+
*/
|
|
919
|
+
async function selectReasoningEffort() {
|
|
920
|
+
const effort = await p.select({
|
|
921
|
+
message: 'Select reasoning effort level',
|
|
922
|
+
options: [
|
|
923
|
+
{
|
|
924
|
+
value: 'medium',
|
|
925
|
+
label: 'Medium (Recommended)',
|
|
926
|
+
hint: 'Balanced reasoning for most tasks',
|
|
927
|
+
},
|
|
928
|
+
{ value: 'low', label: 'Low', hint: 'Light reasoning, fast responses' },
|
|
929
|
+
{
|
|
930
|
+
value: 'minimal',
|
|
931
|
+
label: 'Minimal',
|
|
932
|
+
hint: 'Barely any reasoning, very fast',
|
|
933
|
+
},
|
|
934
|
+
{ value: 'high', label: 'High', hint: 'More thorough reasoning' },
|
|
935
|
+
{
|
|
936
|
+
value: 'xhigh',
|
|
937
|
+
label: 'Extra High',
|
|
938
|
+
hint: 'Maximum reasoning, slower responses',
|
|
939
|
+
},
|
|
940
|
+
{ value: 'none', label: 'None', hint: 'Disable reasoning' },
|
|
941
|
+
],
|
|
942
|
+
});
|
|
943
|
+
if (p.isCancel(effort)) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
return effort;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Select model interactively
|
|
950
|
+
* Returns null if user cancels
|
|
951
|
+
*/
|
|
952
|
+
async function selectModel(provider) {
|
|
953
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
954
|
+
// For providers with a fixed model list
|
|
955
|
+
if (providerInfo?.models && providerInfo.models.length > 0) {
|
|
956
|
+
const options = providerInfo.models.map((m) => {
|
|
957
|
+
const option = {
|
|
958
|
+
value: m.name,
|
|
959
|
+
label: m.displayName || m.name,
|
|
960
|
+
};
|
|
961
|
+
if (m.default) {
|
|
962
|
+
option.hint = '(default)';
|
|
963
|
+
}
|
|
964
|
+
return option;
|
|
965
|
+
});
|
|
966
|
+
const selected = await p.select({
|
|
967
|
+
message: `Select a model for ${getProviderDisplayName(provider)}`,
|
|
968
|
+
options,
|
|
969
|
+
initialValue: providerInfo.models.find((m) => m.default)?.name,
|
|
970
|
+
});
|
|
971
|
+
if (p.isCancel(selected)) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
return selected;
|
|
975
|
+
}
|
|
976
|
+
// For providers that accept any model (openai-compatible, openrouter, etc.)
|
|
977
|
+
const modelInput = await p.text({
|
|
978
|
+
message: `Enter model name for ${getProviderDisplayName(provider)}`,
|
|
979
|
+
placeholder: provider === 'openrouter' ? 'e.g., anthropic/claude-3.5-sonnet' : 'e.g., llama-3-70b',
|
|
980
|
+
validate: (value) => {
|
|
981
|
+
if (!value || value.trim().length === 0) {
|
|
982
|
+
return 'Model name is required';
|
|
983
|
+
}
|
|
984
|
+
return undefined;
|
|
985
|
+
},
|
|
986
|
+
});
|
|
987
|
+
if (p.isCancel(modelInput)) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
return modelInput.trim();
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Prompt for base URL for custom endpoints
|
|
994
|
+
* Returns null if user cancels (to go back)
|
|
995
|
+
*/
|
|
996
|
+
async function promptForBaseURL(provider) {
|
|
997
|
+
p.log.info(chalk.gray('Press Ctrl+C to go back'));
|
|
998
|
+
const placeholder = provider === 'openai-compatible'
|
|
999
|
+
? 'http://localhost:11434/v1'
|
|
1000
|
+
: provider === 'litellm'
|
|
1001
|
+
? 'http://localhost:4000'
|
|
1002
|
+
: 'https://your-api-endpoint.com/v1';
|
|
1003
|
+
const baseURL = await p.text({
|
|
1004
|
+
message: `Enter base URL for ${getProviderDisplayName(provider)}`,
|
|
1005
|
+
placeholder,
|
|
1006
|
+
validate: (value) => {
|
|
1007
|
+
if (!value || value.trim().length === 0) {
|
|
1008
|
+
return 'Base URL is required for this provider';
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
new URL(value.trim());
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
return 'Please enter a valid URL';
|
|
1015
|
+
}
|
|
1016
|
+
return undefined;
|
|
1017
|
+
},
|
|
1018
|
+
});
|
|
1019
|
+
if (p.isCancel(baseURL)) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
return baseURL.trim();
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Show setup complete message
|
|
1026
|
+
*/
|
|
1027
|
+
function showSetupComplete(provider, model, defaultMode, apiKeySkipped = false) {
|
|
1028
|
+
const modeCommand = defaultMode === 'web' ? 'dexto' : `dexto --mode ${defaultMode}`;
|
|
1029
|
+
const isLocalProvider = provider === 'local' || provider === 'ollama';
|
|
1030
|
+
if (apiKeySkipped) {
|
|
1031
|
+
console.log(chalk.rgb(255, 165, 0)('\n⚠️ Setup complete (API key pending)\n'));
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
console.log(chalk.green('\n✨ Setup complete! Dexto is ready to use.\n'));
|
|
1035
|
+
}
|
|
1036
|
+
const summary = [
|
|
1037
|
+
`${chalk.bold('Configuration:')}`,
|
|
1038
|
+
` Provider: ${chalk.cyan(getProviderDisplayName(provider))}`,
|
|
1039
|
+
` Model: ${chalk.cyan(model)}`,
|
|
1040
|
+
` Mode: ${chalk.cyan(defaultMode)}`,
|
|
1041
|
+
...(apiKeySkipped
|
|
1042
|
+
? [
|
|
1043
|
+
` API Key: ${chalk.rgb(255, 165, 0)('Not configured')}`,
|
|
1044
|
+
``,
|
|
1045
|
+
`${chalk.bold('To complete setup:')}`,
|
|
1046
|
+
` Run ${chalk.cyan('dexto setup')} to add your API key`,
|
|
1047
|
+
` Or set ${chalk.cyan(getProviderEnvVar(provider))} in your environment`,
|
|
1048
|
+
]
|
|
1049
|
+
: []),
|
|
1050
|
+
``,
|
|
1051
|
+
`${chalk.bold('Next steps:')}`,
|
|
1052
|
+
` Run ${chalk.cyan(modeCommand)} to start`,
|
|
1053
|
+
` Run ${chalk.cyan('dexto setup')} to change settings`,
|
|
1054
|
+
...(isLocalProvider
|
|
1055
|
+
? [` Run ${chalk.cyan('dexto setup')} again to manage local models`]
|
|
1056
|
+
: []),
|
|
1057
|
+
` Run ${chalk.cyan('dexto --help')} for more options`,
|
|
1058
|
+
].join('\n');
|
|
1059
|
+
console.log(summary);
|
|
1060
|
+
console.log('');
|
|
131
1061
|
}
|