dexto 1.6.20 → 1.6.21

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 CHANGED
@@ -116,7 +116,7 @@ Upgrade/uninstall and migration troubleshooting live in docs:
116
116
  ### Run
117
117
 
118
118
  ```bash
119
- # Start Dexto (launches setup wizard on first run)
119
+ # Start Dexto
120
120
  dexto
121
121
  ```
122
122
 
@@ -132,6 +132,8 @@ dexto --help # Explore all options
132
132
 
133
133
  **Inside the interactive CLI**, type `/` to explore commands—switch models, manage sessions, configure tools, and more.
134
134
 
135
+ If Dexto has not been set up yet, the first interactive launch opens the generic `dexto setup` flow before starting. Existing provider keys in your environment are detected there, so you can keep startup simple without inheriting the wrong bundled provider by accident.
136
+
135
137
  ### Manage Settings
136
138
 
137
139
  ```bash
@@ -595,9 +597,8 @@ Test tools before deploying:
595
597
  Usage: dexto [options] [command] [prompt...]
596
598
 
597
599
  Basic Usage:
598
- dexto Start web UI (default)
599
- dexto "query" Run one-shot query
600
- dexto --mode cli Interactive CLI
600
+ dexto or dexto --mode cli Start interactive CLI (default)
601
+ dexto "query" Run one-shot query
601
602
 
602
603
  Session Management:
603
604
  dexto -c Continue last conversation
@@ -5,6 +5,7 @@ declare const SetupCommandSchema: z.ZodEffects<z.ZodObject<{
5
5
  defaultAgent: z.ZodDefault<z.ZodString>;
6
6
  interactive: z.ZodDefault<z.ZodBoolean>;
7
7
  force: z.ZodDefault<z.ZodBoolean>;
8
+ defaultMode: z.ZodOptional<z.ZodEnum<["cli", "web", "server", "discord", "telegram", "mcp"]>>;
8
9
  quickStart: z.ZodDefault<z.ZodBoolean>;
9
10
  }, "strict", z.ZodTypeAny, {
10
11
  interactive: boolean;
@@ -13,12 +14,14 @@ declare const SetupCommandSchema: z.ZodEffects<z.ZodObject<{
13
14
  quickStart: boolean;
14
15
  provider?: "dexto-nova" | "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
15
16
  model?: string | undefined;
17
+ defaultMode?: "cli" | "web" | "server" | "discord" | "telegram" | "mcp" | undefined;
16
18
  }, {
17
19
  interactive?: boolean | undefined;
18
20
  provider?: "dexto-nova" | "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
19
21
  model?: string | undefined;
20
22
  force?: boolean | undefined;
21
23
  defaultAgent?: string | undefined;
24
+ defaultMode?: "cli" | "web" | "server" | "discord" | "telegram" | "mcp" | undefined;
22
25
  quickStart?: boolean | undefined;
23
26
  }>, {
24
27
  interactive: boolean;
@@ -27,12 +30,14 @@ declare const SetupCommandSchema: z.ZodEffects<z.ZodObject<{
27
30
  quickStart: boolean;
28
31
  provider?: "dexto-nova" | "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
29
32
  model?: string | undefined;
33
+ defaultMode?: "cli" | "web" | "server" | "discord" | "telegram" | "mcp" | undefined;
30
34
  }, {
31
35
  interactive?: boolean | undefined;
32
36
  provider?: "dexto-nova" | "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
33
37
  model?: string | undefined;
34
38
  force?: boolean | undefined;
35
39
  defaultAgent?: string | undefined;
40
+ defaultMode?: "cli" | "web" | "server" | "discord" | "telegram" | "mcp" | undefined;
36
41
  quickStart?: boolean | undefined;
37
42
  }>;
38
43
  export type CLISetupOptions = z.output<typeof SetupCommandSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA+DxB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0ClB,CAAC;AAEP,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAClE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAqKtE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9F"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA+DxB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8ClB,CAAC;AAEP,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAClE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAsKtE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC9F"}
@@ -39,6 +39,10 @@ const SetupCommandSchema = z
39
39
  .boolean()
40
40
  .default(false)
41
41
  .describe('Overwrite existing setup when already configured'),
42
+ defaultMode: z
43
+ .enum(['cli', 'web', 'server', 'discord', 'telegram', 'mcp'])
44
+ .optional()
45
+ .describe('Preferred default mode for interactive setup flows'),
42
46
  quickStart: z
43
47
  .boolean()
44
48
  .default(false)
@@ -189,7 +193,10 @@ export async function handleSetupCommand(options) {
189
193
  }
190
194
  // Handle quick start
191
195
  if (validated.quickStart) {
192
- await handleQuickStart({ onCancel: 'exit' });
196
+ await handleQuickStart({
197
+ onCancel: 'exit',
198
+ preferredDefaultMode: validated.defaultMode,
199
+ });
193
200
  return;
194
201
  }
195
202
  // Handle interactive full setup
@@ -260,7 +267,9 @@ async function handleQuickStart(options = { onCancel: 'exit' }) {
260
267
  }
261
268
  continue;
262
269
  }
263
- const defaultMode = useCli ? 'cli' : await selectDefaultMode();
270
+ const defaultMode = useCli
271
+ ? 'cli'
272
+ : await selectDefaultMode(options.preferredDefaultMode);
264
273
  if (defaultMode === null) {
265
274
  if (options.onCancel === 'exit') {
266
275
  p.cancel('Setup cancelled');
@@ -383,7 +392,7 @@ async function handleQuickStart(options = { onCancel: 'exit' }) {
383
392
  }
384
393
  continue;
385
394
  }
386
- const defaultMode = useCli ? 'cli' : await selectDefaultMode();
395
+ const defaultMode = useCli ? 'cli' : await selectDefaultMode(options.preferredDefaultMode);
387
396
  // Handle cancellation
388
397
  if (defaultMode === null) {
389
398
  if (options.onCancel === 'exit') {
@@ -934,11 +943,14 @@ async function getCreditsBalance() {
934
943
  * Full interactive setup flow with wizard navigation.
935
944
  * Users can go back to previous steps to change their selections.
936
945
  */
937
- async function handleInteractiveSetup(_options) {
946
+ async function handleInteractiveSetup(options) {
938
947
  console.log(chalk.cyan('\nDexto Setup\n'));
939
948
  p.intro(chalk.cyan("Let's configure your AI agent"));
940
949
  // Initialize wizard state
941
- let state = { step: 'setupType' };
950
+ let state = {
951
+ step: 'setupType',
952
+ preferredDefaultMode: options.defaultMode,
953
+ };
942
954
  // Wizard loop - process steps until complete
943
955
  while (state.step !== 'complete') {
944
956
  switch (state.step) {
@@ -1016,7 +1028,10 @@ async function wizardStepSetupType(state) {
1016
1028
  }
1017
1029
  if (setupType === 'quick') {
1018
1030
  // Quick start bypasses the wizard - handle it directly
1019
- const result = await handleQuickStart({ onCancel: 'back' });
1031
+ const result = await handleQuickStart({
1032
+ onCancel: 'back',
1033
+ preferredDefaultMode: state.preferredDefaultMode,
1034
+ });
1020
1035
  if (result === 'cancelled') {
1021
1036
  return { ...state, step: 'setupType' };
1022
1037
  }
@@ -1161,7 +1176,7 @@ async function wizardStepMode(state) {
1161
1176
  const isLocalProvider = provider === 'local' || provider === 'ollama';
1162
1177
  const hasReasoningStep = getReasoningProfile(provider, model).capable;
1163
1178
  showStepProgress('mode', provider, model);
1164
- const mode = await selectDefaultModeWithBack();
1179
+ const mode = await selectDefaultModeWithBack(state.preferredDefaultMode);
1165
1180
  if (mode === '_back') {
1166
1181
  // Go back to the previous *interactive* step. Some steps (like apiKey) may be
1167
1182
  // auto-skipped when not required or already configured, so "back" from mode
@@ -1550,7 +1565,7 @@ async function promptCustomModelValues(initialModel, providerOverride) {
1550
1565
  ...(reasoningPreset ? { reasoningPreset } : {}),
1551
1566
  };
1552
1567
  }
1553
- async function selectDefaultModeWithBack() {
1568
+ async function selectDefaultModeWithBack(preferredDefaultMode) {
1554
1569
  const result = await p.select({
1555
1570
  message: 'How do you want to use Dexto by default?',
1556
1571
  options: [
@@ -1571,6 +1586,7 @@ async function selectDefaultModeWithBack() {
1571
1586
  },
1572
1587
  { value: '_back', label: chalk.gray('← Back'), hint: 'Go to previous step' },
1573
1588
  ],
1589
+ ...(preferredDefaultMode ? { initialValue: preferredDefaultMode } : {}),
1574
1590
  });
1575
1591
  if (p.isCancel(result)) {
1576
1592
  return '_back';
@@ -1645,13 +1661,17 @@ async function handleNonInteractiveSetup(options) {
1645
1661
  }
1646
1662
  const apiKeyVar = getProviderEnvVar(provider);
1647
1663
  const hadApiKeyBefore = Boolean(resolveApiKeyForProvider(provider));
1648
- const preferences = createInitialPreferences({
1664
+ const preferencesOptions = {
1649
1665
  provider,
1650
1666
  model,
1651
1667
  apiKeyVar,
1652
1668
  defaultAgent: options.defaultAgent,
1653
1669
  setupCompleted: true,
1654
- });
1670
+ };
1671
+ if (options.defaultMode) {
1672
+ preferencesOptions.defaultMode = options.defaultMode;
1673
+ }
1674
+ const preferences = createInitialPreferences(preferencesOptions);
1655
1675
  await saveGlobalPreferences(preferences);
1656
1676
  // For local provider, sync the active model in state.json
1657
1677
  if (provider === 'local') {
@@ -1733,7 +1753,7 @@ async function showSettingsMenu() {
1733
1753
  {
1734
1754
  value: 'mode',
1735
1755
  label: 'Change default mode',
1736
- hint: `Currently: ${currentPrefs?.defaults.defaultMode || 'web'}`,
1756
+ hint: `Currently: ${currentPrefs?.defaults.defaultMode || 'cli'}`,
1737
1757
  },
1738
1758
  {
1739
1759
  value: 'auth',
@@ -2090,7 +2110,7 @@ function showPreferencesFilePath() {
2090
2110
  * Select default mode interactively
2091
2111
  * Returns null if user cancels
2092
2112
  */
2093
- async function selectDefaultMode() {
2113
+ async function selectDefaultMode(preferredDefaultMode) {
2094
2114
  const mode = await p.select({
2095
2115
  message: 'How do you want to use Dexto by default?',
2096
2116
  options: [
@@ -2110,6 +2130,7 @@ async function selectDefaultMode() {
2110
2130
  hint: 'REST API for integrations',
2111
2131
  },
2112
2132
  ],
2133
+ ...(preferredDefaultMode ? { initialValue: preferredDefaultMode } : {}),
2113
2134
  });
2114
2135
  if (p.isCancel(mode)) {
2115
2136
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli/modes/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAuBpD,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgLxE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli/modes/cli.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAuBpD,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuQxE"}
@@ -1,6 +1,8 @@
1
1
  import chalk from 'chalk';
2
+ import * as p from '@clack/prompts';
2
3
  import { logger } from '@dexto/core';
3
4
  import { safeExit, ExitSignal } from '../../analytics/wrapper.js';
5
+ import { hasUsableCredentials } from '../../config/cli-overrides.js';
4
6
  import { applyWorkspaceToAgent } from '../../utils/workspace.js';
5
7
  async function getMostRecentSessionId(agent) {
6
8
  const sessionIds = await agent.listSessions();
@@ -31,27 +33,99 @@ export async function runCliMode(context) {
31
33
  await agent.start();
32
34
  await applyWorkspaceToAgent(agent, workspaceRoot);
33
35
  const llmConfig = agent.getCurrentLLMConfig();
34
- const { requiresApiKey } = await import('@dexto/core');
35
- if (requiresApiKey(llmConfig.provider) && !llmConfig.apiKey?.trim()) {
36
+ if (!hasUsableCredentials(llmConfig.provider, llmConfig)) {
37
+ const { globalPreferencesExist, loadGlobalPreferences } = await import('@dexto/agent-management');
36
38
  const { interactiveApiKeySetup } = await import('../utils/api-key-setup.js');
37
- console.log(chalk.yellow(`\n⚠️ API key required for provider '${llmConfig.provider}'\n`));
38
- const setupResult = await interactiveApiKeySetup(llmConfig.provider, {
39
- exitOnCancel: false,
40
- model: llmConfig.model,
41
- });
42
- if (setupResult.cancelled) {
43
- safeExit('main', 0, 'api-key-setup-cancelled');
39
+ const { getProviderDisplayName, getProviderEnvVar } = await import('../utils/provider-setup.js');
40
+ let hasCompletedSetup = false;
41
+ if (globalPreferencesExist()) {
42
+ try {
43
+ const preferences = await loadGlobalPreferences();
44
+ hasCompletedSetup = preferences.setup.completed;
45
+ }
46
+ catch {
47
+ hasCompletedSetup = false;
48
+ }
44
49
  }
45
- if (setupResult.skipped) {
46
- safeExit('main', 0, 'api-key-pending');
50
+ console.log(chalk.yellow(`\n⚠️ API key required for provider '${getProviderDisplayName(llmConfig.provider)}'\n`));
51
+ if (!hasCompletedSetup) {
52
+ console.log(chalk.gray(`Dexto started with the bundled coding-agent defaults. ` +
53
+ `Set ${getProviderEnvVar(llmConfig.provider)} or run ${chalk.cyan('dexto setup')} to choose a different provider.`));
47
54
  }
48
- if (setupResult.success && setupResult.apiKey) {
49
- await agent.switchLLM({
50
- provider: llmConfig.provider,
55
+ const runProviderKeySetup = async () => {
56
+ const setupResult = await interactiveApiKeySetup(llmConfig.provider, {
57
+ exitOnCancel: false,
51
58
  model: llmConfig.model,
52
- apiKey: setupResult.apiKey,
53
59
  });
54
- logger.info('API key configured successfully, continuing...');
60
+ if (setupResult.cancelled) {
61
+ safeExit('main', 0, 'api-key-setup-cancelled');
62
+ }
63
+ if (setupResult.skipped) {
64
+ safeExit('main', 0, 'api-key-pending');
65
+ }
66
+ if (setupResult.success && setupResult.apiKey) {
67
+ await agent.switchLLM({
68
+ provider: llmConfig.provider,
69
+ model: llmConfig.model,
70
+ apiKey: setupResult.apiKey,
71
+ });
72
+ logger.info('API key configured successfully, continuing...');
73
+ }
74
+ };
75
+ if (hasCompletedSetup) {
76
+ await runProviderKeySetup();
77
+ }
78
+ else {
79
+ const action = await p.select({
80
+ message: 'How would you like to continue?',
81
+ options: [
82
+ {
83
+ value: 'key',
84
+ label: `Paste a ${getProviderDisplayName(llmConfig.provider)} key`,
85
+ hint: 'Continue in this session',
86
+ },
87
+ {
88
+ value: 'setup',
89
+ label: 'Run dexto setup',
90
+ hint: 'Choose a different provider or save defaults',
91
+ },
92
+ {
93
+ value: 'exit',
94
+ label: 'Exit',
95
+ hint: 'Configure later',
96
+ },
97
+ ],
98
+ });
99
+ if (p.isCancel(action) || action === 'exit') {
100
+ safeExit('main', 0, 'api-key-setup-cancelled');
101
+ }
102
+ if (action === 'setup') {
103
+ const { handleSetupCommand } = await import('../commands/setup.js');
104
+ await handleSetupCommand({ interactive: true, force: true });
105
+ let preferences;
106
+ try {
107
+ preferences = await loadGlobalPreferences();
108
+ }
109
+ catch {
110
+ safeExit('main', 0, 'setup-incomplete');
111
+ }
112
+ if (!preferences.setup.completed) {
113
+ safeExit('main', 0, 'setup-incomplete');
114
+ }
115
+ if (preferences.setup.apiKeyPending) {
116
+ safeExit('main', 0, 'api-key-pending');
117
+ }
118
+ await agent.switchLLM({
119
+ provider: preferences.llm.provider,
120
+ model: preferences.llm.model,
121
+ ...(preferences.llm.apiKey && { apiKey: preferences.llm.apiKey }),
122
+ ...(preferences.llm.baseURL && { baseURL: preferences.llm.baseURL }),
123
+ });
124
+ logger.info('Provider configured successfully, continuing...');
125
+ }
126
+ else {
127
+ await runProviderKeySetup();
128
+ }
55
129
  }
56
130
  }
57
131
  let cliSessionId;
@@ -23,9 +23,8 @@ export declare function getSetupState(): Promise<SetupState>;
23
23
  * Check if user requires setup (missing, corrupted, or incomplete preferences)
24
24
  * Context-aware:
25
25
  * - Dev mode (source + DEXTO_DEV_MODE): Skip setup, uses repo configs
26
- * - Project context: Skip setup (might have project-local config)
27
- * - First-time user (source/global-cli): Require setup
28
- * - Has preferences (source/global-cli): Validate them
26
+ * - First-time user: Require setup
27
+ * - Has preferences: Validate them
29
28
  * @returns true if setup is required
30
29
  */
31
30
  export declare function requiresSetup(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"setup-utils.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/setup-utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAGH,KAAK,iBAAiB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAgFzD;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAqB/D"}
1
+ {"version":3,"file":"setup-utils.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/setup-utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAKH,KAAK,iBAAiB,EACzB,MAAM,yBAAyB,CAAC;AAgBjC;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACzC;AAaD;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAkFzD;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAqB/D"}
@@ -1,6 +1,18 @@
1
1
  // packages/cli/src/cli/utils/setup-utils.ts
2
- import { globalPreferencesExist, loadGlobalPreferences, } from '@dexto/agent-management';
2
+ import { findDextoProjectRoot, findProjectRegistryPathSync, globalPreferencesExist, loadGlobalPreferences, } from '@dexto/agent-management';
3
3
  import { getExecutionContext } from '@dexto/core';
4
+ import { existsSync, statSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ const PROJECT_LOCAL_CODING_AGENT_RELATIVE_PATHS = [
7
+ path.join('agents', 'coding-agent', 'coding-agent.yml'),
8
+ path.join('agents', 'coding-agent', 'coding-agent.yaml'),
9
+ 'coding-agent.yml',
10
+ 'coding-agent.yaml',
11
+ path.join('agents', 'coding-agent.yml'),
12
+ path.join('agents', 'coding-agent.yaml'),
13
+ path.join('src', 'dexto', 'agents', 'coding-agent.yml'),
14
+ path.join('src', 'dexto', 'agents', 'coding-agent.yaml'),
15
+ ];
4
16
  /**
5
17
  * Check if this is a first-time user (no preferences file exists)
6
18
  * @returns true if user has never run setup
@@ -8,6 +20,15 @@ import { getExecutionContext } from '@dexto/core';
8
20
  export function isFirstTimeUser() {
9
21
  return !globalPreferencesExist();
10
22
  }
23
+ function hasProjectLocalStartupConfig(projectRoot) {
24
+ if (findProjectRegistryPathSync(projectRoot)) {
25
+ return true;
26
+ }
27
+ return PROJECT_LOCAL_CODING_AGENT_RELATIVE_PATHS.some((relativePath) => {
28
+ const absolutePath = path.join(projectRoot, relativePath);
29
+ return existsSync(absolutePath) && statSync(absolutePath).isFile();
30
+ });
31
+ }
11
32
  /**
12
33
  * Get detailed setup state including pending items
13
34
  * @returns Setup state with detailed flags
@@ -24,15 +45,17 @@ export async function getSetupState() {
24
45
  preferences: null,
25
46
  };
26
47
  }
27
- // Project context: skip (might have project-local config)
28
48
  if (context === 'dexto-project') {
29
- return {
30
- needsSetup: false,
31
- isFirstTime: false,
32
- apiKeyPending: false,
33
- baseURLPending: false,
34
- preferences: null,
35
- };
49
+ const projectRoot = findDextoProjectRoot();
50
+ if (projectRoot && hasProjectLocalStartupConfig(projectRoot)) {
51
+ return {
52
+ needsSetup: false,
53
+ isFirstTime: false,
54
+ apiKeyPending: false,
55
+ baseURLPending: false,
56
+ preferences: null,
57
+ };
58
+ }
36
59
  }
37
60
  // First-time user (no preferences)
38
61
  if (isFirstTimeUser()) {
@@ -91,9 +114,8 @@ export async function getSetupState() {
91
114
  * Check if user requires setup (missing, corrupted, or incomplete preferences)
92
115
  * Context-aware:
93
116
  * - Dev mode (source + DEXTO_DEV_MODE): Skip setup, uses repo configs
94
- * - Project context: Skip setup (might have project-local config)
95
- * - First-time user (source/global-cli): Require setup
96
- * - Has preferences (source/global-cli): Validate them
117
+ * - First-time user: Require setup
118
+ * - Has preferences: Validate them
97
119
  * @returns true if setup is required
98
120
  */
99
121
  export async function requiresSetup() {
@@ -19,7 +19,7 @@
19
19
  * - Merge strategy configuration for non-LLM fields
20
20
  */
21
21
  import type { AgentConfig } from '@dexto/agent-config';
22
- import type { LLMConfig, LLMProvider } from '@dexto/core';
22
+ import { type LLMConfig, type LLMProvider } from '@dexto/core';
23
23
  import type { GlobalPreferences } from '@dexto/agent-management';
24
24
  /**
25
25
  * CLI config override type for fields that can be overridden via CLI
@@ -30,6 +30,12 @@ export interface CLIConfigOverrides extends Partial<Pick<LLMConfig, 'provider' |
30
30
  /** When false (via --no-elicitation), disables elicitation */
31
31
  elicitation?: boolean;
32
32
  }
33
+ export interface StartupLLMFallbackOptions {
34
+ hasCompletedSetup: boolean;
35
+ hasExplicitProviderOverride: boolean;
36
+ hasExplicitModelOverride: boolean;
37
+ hasExplicitApiKeyOverride: boolean;
38
+ }
33
39
  /**
34
40
  * Applies CLI overrides to an agent configuration
35
41
  * This merges CLI arguments into the base config without validation.
@@ -52,6 +58,8 @@ export declare function applyCLIOverrides(baseConfig: AgentConfig, cliOverrides?
52
58
  * @returns Merged configuration with user preferences applied
53
59
  */
54
60
  export declare function applyUserPreferences(baseConfig: AgentConfig, preferences: Partial<GlobalPreferences>): AgentConfig;
61
+ export declare function applyStartupLLMFallback(baseConfig: AgentConfig, options: StartupLLMFallbackOptions): AgentConfig;
62
+ export declare function hasUsableCredentials(provider: LLMProvider, llmConfig?: Pick<AgentConfig['llm'], 'apiKey' | 'baseURL'>): boolean;
55
63
  /**
56
64
  * Result of agent compatibility check
57
65
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cli-overrides.d.ts","sourceRoot":"","sources":["../../src/config/cli-overrides.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,kBACb,SAAQ,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,WAAW,EACvB,YAAY,CAAC,EAAE,kBAAkB,GAClC,WAAW,CAuCb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,WAAW,EACvB,WAAW,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACxC,WAAW,CAwBb;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,WAAW,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACnC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,iBAAiB,GAAG,IAAI,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,GACnC,wBAAwB,CAwC1B"}
1
+ {"version":3,"file":"cli-overrides.d.ts","sourceRoot":"","sources":["../../src/config/cli-overrides.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAOH,KAAK,SAAS,EACd,KAAK,WAAW,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,kBACb,SAAQ,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACtC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,2BAA2B,EAAE,OAAO,CAAC;IACrC,wBAAwB,EAAE,OAAO,CAAC;IAClC,yBAAyB,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,WAAW,EACvB,YAAY,CAAC,EAAE,kBAAkB,GAClC,WAAW,CAuCb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,WAAW,EACvB,WAAW,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACxC,WAAW,CAwBb;AAED,wBAAgB,uBAAuB,CACnC,UAAU,EAAE,WAAW,EACvB,OAAO,EAAE,yBAAyB,GACnC,WAAW,CAyCb;AAcD,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,WAAW,EACrB,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC,GAC3D,OAAO,CAgBT;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,WAAW,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACnC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,iBAAiB,GAAG,IAAI,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,GACnC,wBAAwB,CAwC1B"}
@@ -18,6 +18,7 @@
18
18
  * - Agent capability requirements (requires: { vision: true, toolUse: true })
19
19
  * - Merge strategy configuration for non-LLM fields
20
20
  */
21
+ import { EnvExpandedString, LLM_PROVIDERS, getDefaultModelForProvider, requiresApiKey, requiresBaseURL, resolveApiKeyForProvider, } from '@dexto/core';
21
22
  /**
22
23
  * Applies CLI overrides to an agent configuration
23
24
  * This merges CLI arguments into the base config without validation.
@@ -96,6 +97,62 @@ export function applyUserPreferences(baseConfig, preferences) {
96
97
  }
97
98
  return mergedConfig;
98
99
  }
100
+ export function applyStartupLLMFallback(baseConfig, options) {
101
+ const mergedConfig = JSON.parse(JSON.stringify(baseConfig));
102
+ if (options.hasCompletedSetup ||
103
+ options.hasExplicitProviderOverride ||
104
+ options.hasExplicitModelOverride ||
105
+ options.hasExplicitApiKeyOverride) {
106
+ return mergedConfig;
107
+ }
108
+ if (hasUsableCredentials(mergedConfig.llm.provider, mergedConfig.llm)) {
109
+ return mergedConfig;
110
+ }
111
+ for (const provider of LLM_PROVIDERS) {
112
+ if (!hasExplicitStartupFallbackConfiguration(provider)) {
113
+ continue;
114
+ }
115
+ const model = getDefaultModelForProvider(provider);
116
+ if (!model) {
117
+ continue;
118
+ }
119
+ mergedConfig.llm.provider = provider;
120
+ mergedConfig.llm.model = model;
121
+ delete mergedConfig.llm.baseURL;
122
+ const apiKey = resolveApiKeyForProvider(provider);
123
+ if (apiKey) {
124
+ mergedConfig.llm.apiKey = apiKey;
125
+ }
126
+ else {
127
+ delete mergedConfig.llm.apiKey;
128
+ }
129
+ return mergedConfig;
130
+ }
131
+ return mergedConfig;
132
+ }
133
+ function hasExplicitStartupFallbackConfiguration(provider) {
134
+ if (resolveApiKeyForProvider(provider)?.trim()) {
135
+ return true;
136
+ }
137
+ if (requiresBaseURL(provider) && process.env.OPENAI_BASE_URL?.trim()) {
138
+ return true;
139
+ }
140
+ return false;
141
+ }
142
+ export function hasUsableCredentials(provider, llmConfig) {
143
+ const baseURL = llmConfig?.baseURL ? EnvExpandedString().parse(llmConfig.baseURL) : undefined;
144
+ if (requiresBaseURL(provider) && !baseURL?.trim()) {
145
+ return false;
146
+ }
147
+ if (!requiresApiKey(provider)) {
148
+ return true;
149
+ }
150
+ const inlineApiKey = llmConfig?.apiKey ? EnvExpandedString().parse(llmConfig.apiKey) : '';
151
+ if (inlineApiKey.trim()) {
152
+ return true;
153
+ }
154
+ return Boolean(resolveApiKeyForProvider(provider)?.trim());
155
+ }
99
156
  /**
100
157
  * Check if user's current setup is compatible with an agent's requirements.
101
158
  * Used when switching to non-default agents to warn users about potential issues.