dexto 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +62 -7
  2. package/dist/agents/agent-template.yml +2 -2
  3. package/dist/agents/coding-agent/coding-agent.yml +22 -16
  4. package/dist/agents/database-agent/database-agent.yml +2 -2
  5. package/dist/agents/default-agent.yml +7 -5
  6. package/dist/agents/github-agent/github-agent.yml +2 -2
  7. package/dist/agents/product-name-researcher/product-name-researcher.yml +2 -2
  8. package/dist/agents/talk2pdf-agent/talk2pdf-agent.yml +2 -2
  9. package/dist/analytics/events.d.ts +13 -6
  10. package/dist/analytics/events.d.ts.map +1 -1
  11. package/dist/analytics/index.d.ts +1 -1
  12. package/dist/analytics/index.d.ts.map +1 -1
  13. package/dist/analytics/index.js +6 -2
  14. package/dist/api/server-hono.d.ts.map +1 -1
  15. package/dist/api/server-hono.js +27 -5
  16. package/dist/cli/cli-subscriber.d.ts +4 -0
  17. package/dist/cli/cli-subscriber.d.ts.map +1 -1
  18. package/dist/cli/cli-subscriber.js +40 -2
  19. package/dist/cli/commands/create-app.d.ts +16 -14
  20. package/dist/cli/commands/create-app.d.ts.map +1 -1
  21. package/dist/cli/commands/create-app.js +626 -102
  22. package/dist/cli/commands/create-image.d.ts +7 -0
  23. package/dist/cli/commands/create-image.d.ts.map +1 -0
  24. package/dist/cli/commands/create-image.js +201 -0
  25. package/dist/cli/commands/helpers/formatters.js +7 -7
  26. package/dist/cli/commands/index.d.ts +2 -1
  27. package/dist/cli/commands/index.d.ts.map +1 -1
  28. package/dist/cli/commands/index.js +2 -1
  29. package/dist/cli/commands/init-app.js +7 -7
  30. package/dist/cli/commands/install.d.ts +0 -3
  31. package/dist/cli/commands/install.d.ts.map +1 -1
  32. package/dist/cli/commands/install.js +10 -35
  33. package/dist/cli/commands/interactive-commands/command-parser.js +7 -7
  34. package/dist/cli/commands/interactive-commands/general-commands.js +1 -1
  35. package/dist/cli/commands/interactive-commands/prompt-commands.js +11 -11
  36. package/dist/cli/commands/interactive-commands/system/system-commands.js +3 -3
  37. package/dist/cli/commands/list-agents.js +2 -2
  38. package/dist/cli/commands/session-commands.js +16 -16
  39. package/dist/cli/commands/setup.d.ts +13 -5
  40. package/dist/cli/commands/setup.d.ts.map +1 -1
  41. package/dist/cli/commands/setup.js +860 -65
  42. package/dist/cli/commands/which.js +1 -1
  43. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts +2 -0
  44. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
  45. package/dist/cli/ink-cli/components/ApprovalPrompt.js +29 -7
  46. package/dist/cli/ink-cli/components/CustomInput.js +1 -1
  47. package/dist/cli/ink-cli/components/EditableMultiLineInput.js +4 -4
  48. package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
  49. package/dist/cli/ink-cli/components/ElicitationForm.js +6 -6
  50. package/dist/cli/ink-cli/components/ErrorBoundary.d.ts.map +1 -1
  51. package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
  52. package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/components/Footer.js +1 -1
  54. package/dist/cli/ink-cli/components/HistorySearchBar.d.ts.map +1 -1
  55. package/dist/cli/ink-cli/components/HistorySearchBar.js +1 -1
  56. package/dist/cli/ink-cli/components/MultiLineInput.d.ts.map +1 -1
  57. package/dist/cli/ink-cli/components/MultiLineInput.js +3 -3
  58. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  59. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +4 -4
  60. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +3 -3
  61. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  62. package/dist/cli/ink-cli/components/StatusBar.js +7 -5
  63. package/dist/cli/ink-cli/components/TextBufferInput.d.ts.map +1 -1
  64. package/dist/cli/ink-cli/components/TextBufferInput.js +6 -6
  65. package/dist/cli/ink-cli/components/base/BaseAutocomplete.js +4 -4
  66. package/dist/cli/ink-cli/components/base/BaseSelector.js +2 -2
  67. package/dist/cli/ink-cli/components/chat/Footer.js +1 -1
  68. package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
  69. package/dist/cli/ink-cli/components/chat/Header.js +2 -4
  70. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/components/chat/MessageItem.js +5 -5
  72. package/dist/cli/ink-cli/components/chat/MessageList.js +1 -1
  73. package/dist/cli/ink-cli/components/chat/QueuedMessagesDisplay.js +1 -1
  74. package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts +1 -1
  75. package/dist/cli/ink-cli/components/chat/ToolIcon.js +4 -4
  76. package/dist/cli/ink-cli/components/chat/styled-boxes/ConfigBox.js +1 -1
  77. package/dist/cli/ink-cli/components/chat/styled-boxes/HelpBox.js +1 -1
  78. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +2 -2
  79. package/dist/cli/ink-cli/components/chat/styled-boxes/SessionHistoryBox.js +5 -5
  80. package/dist/cli/ink-cli/components/chat/styled-boxes/SessionListBox.js +2 -2
  81. package/dist/cli/ink-cli/components/chat/styled-boxes/ShortcutsBox.js +1 -1
  82. package/dist/cli/ink-cli/components/chat/styled-boxes/StatsBox.js +1 -1
  83. package/dist/cli/ink-cli/components/chat/styled-boxes/StyledBox.js +2 -2
  84. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  85. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +1 -1
  86. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  87. package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
  88. package/dist/cli/ink-cli/components/overlays/ApiKeyInput.js +1 -1
  89. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts +10 -2
  90. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
  91. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +198 -89
  92. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts.map +1 -1
  93. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +2 -2
  94. package/dist/cli/ink-cli/components/overlays/McpAddChoice.d.ts.map +1 -1
  95. package/dist/cli/ink-cli/components/overlays/McpAddChoice.js +1 -1
  96. package/dist/cli/ink-cli/components/overlays/McpAddSelector.js +1 -1
  97. package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.d.ts.map +1 -1
  98. package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.js +2 -2
  99. package/dist/cli/ink-cli/components/overlays/McpCustomWizard.js +1 -1
  100. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.d.ts.map +1 -1
  101. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
  102. package/dist/cli/ink-cli/components/overlays/McpSelector.d.ts.map +1 -1
  103. package/dist/cli/ink-cli/components/overlays/McpSelector.js +1 -1
  104. package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts.map +1 -1
  105. package/dist/cli/ink-cli/components/overlays/McpServerActions.js +2 -2
  106. package/dist/cli/ink-cli/components/overlays/McpServerList.d.ts.map +1 -1
  107. package/dist/cli/ink-cli/components/overlays/McpServerList.js +1 -1
  108. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +5 -5
  109. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  110. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +222 -68
  111. package/dist/cli/ink-cli/components/overlays/PromptAddChoice.d.ts.map +1 -1
  112. package/dist/cli/ink-cli/components/overlays/PromptAddChoice.js +2 -2
  113. package/dist/cli/ink-cli/components/overlays/PromptAddWizard.js +1 -1
  114. package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.d.ts.map +1 -1
  115. package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.js +2 -2
  116. package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
  117. package/dist/cli/ink-cli/components/overlays/PromptList.js +2 -2
  118. package/dist/cli/ink-cli/components/overlays/SearchOverlay.d.ts.map +1 -1
  119. package/dist/cli/ink-cli/components/overlays/SearchOverlay.js +4 -4
  120. package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.d.ts.map +1 -1
  121. package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.js +1 -1
  122. package/dist/cli/ink-cli/components/overlays/StreamSelector.d.ts.map +1 -1
  123. package/dist/cli/ink-cli/components/overlays/StreamSelector.js +1 -1
  124. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +12 -12
  125. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts +25 -0
  126. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -0
  127. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +609 -0
  128. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts +15 -0
  129. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts.map +1 -0
  130. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.js +14 -0
  131. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +33 -0
  132. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -0
  133. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +419 -0
  134. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts +25 -0
  135. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts.map +1 -0
  136. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.js +29 -0
  137. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts +17 -0
  138. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts.map +1 -0
  139. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.js +11 -0
  140. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts +20 -0
  141. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts.map +1 -0
  142. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.js +10 -0
  143. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts +30 -0
  144. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts.map +1 -0
  145. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.js +13 -0
  146. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts +8 -0
  147. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts.map +1 -0
  148. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.js +7 -0
  149. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts +79 -0
  150. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts.map +1 -0
  151. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.js +38 -0
  152. package/dist/cli/ink-cli/components/renderers/DiffRenderer.js +2 -2
  153. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +1 -1
  154. package/dist/cli/ink-cli/components/renderers/FileRenderer.js +4 -4
  155. package/dist/cli/ink-cli/components/renderers/GenericRenderer.js +2 -2
  156. package/dist/cli/ink-cli/components/renderers/SearchRenderer.js +1 -1
  157. package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +3 -3
  158. package/dist/cli/ink-cli/components/renderers/diff-shared.js +1 -1
  159. package/dist/cli/ink-cli/components/shared/MarkdownText.d.ts.map +1 -1
  160. package/dist/cli/ink-cli/components/shared/MarkdownText.js +8 -6
  161. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  162. package/dist/cli/ink-cli/containers/InputContainer.js +23 -1
  163. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  164. package/dist/cli/ink-cli/containers/OverlayContainer.js +80 -24
  165. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts +1 -1
  166. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
  167. package/dist/cli/ink-cli/hooks/useAgentEvents.js +5 -1
  168. package/dist/cli/ink-cli/hooks/useCLIState.d.ts +1 -1
  169. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  170. package/dist/cli/ink-cli/hooks/useCLIState.js +4 -2
  171. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  172. package/dist/cli/ink-cli/services/processStream.js +77 -9
  173. package/dist/cli/ink-cli/state/types.d.ts +3 -2
  174. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  175. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +5 -0
  176. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  177. package/dist/cli/ink-cli/utils/messageFormatting.js +59 -1
  178. package/dist/cli/ink-cli/utils/toolUtils.d.ts.map +1 -1
  179. package/dist/cli/ink-cli/utils/toolUtils.js +2 -0
  180. package/dist/cli/utils/api-key-setup.d.ts +54 -4
  181. package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
  182. package/dist/cli/utils/api-key-setup.js +433 -107
  183. package/dist/cli/utils/api-key-verification.d.ts +17 -0
  184. package/dist/cli/utils/api-key-verification.d.ts.map +1 -0
  185. package/dist/cli/utils/api-key-verification.js +211 -0
  186. package/dist/cli/utils/config-validation.d.ts +22 -2
  187. package/dist/cli/utils/config-validation.d.ts.map +1 -1
  188. package/dist/cli/utils/config-validation.js +354 -25
  189. package/dist/cli/utils/local-model-setup.d.ts +46 -0
  190. package/dist/cli/utils/local-model-setup.d.ts.map +1 -0
  191. package/dist/cli/utils/local-model-setup.js +662 -0
  192. package/dist/cli/utils/options.js +1 -1
  193. package/dist/cli/utils/prompt-helpers.d.ts +47 -0
  194. package/dist/cli/utils/prompt-helpers.d.ts.map +1 -0
  195. package/dist/cli/utils/prompt-helpers.js +66 -0
  196. package/dist/cli/utils/provider-setup.d.ts +66 -8
  197. package/dist/cli/utils/provider-setup.d.ts.map +1 -1
  198. package/dist/cli/utils/provider-setup.js +324 -84
  199. package/dist/cli/utils/scaffolding-utils.d.ts +76 -0
  200. package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -0
  201. package/dist/cli/utils/scaffolding-utils.js +246 -0
  202. package/dist/cli/utils/setup-utils.d.ts +16 -0
  203. package/dist/cli/utils/setup-utils.d.ts.map +1 -1
  204. package/dist/cli/utils/setup-utils.js +72 -21
  205. package/dist/cli/utils/template-engine.d.ts +65 -0
  206. package/dist/cli/utils/template-engine.d.ts.map +1 -0
  207. package/dist/cli/utils/template-engine.js +1089 -0
  208. package/dist/config/cli-overrides.d.ts +44 -1
  209. package/dist/config/cli-overrides.d.ts.map +1 -1
  210. package/dist/config/cli-overrides.js +102 -0
  211. package/dist/index.js +315 -53
  212. package/dist/webui/assets/index-8j-KMkX1.js +2054 -0
  213. package/dist/webui/assets/index-c_AX24V4.css +1 -0
  214. package/dist/webui/index.html +3 -9
  215. package/dist/webui/logos/aws-color.svg +1 -0
  216. package/dist/webui/logos/dexto/dexto_logo.svg +1 -1
  217. package/dist/webui/logos/dexto/dexto_logo_light.svg +6 -6
  218. package/dist/webui/logos/glama.svg +7 -0
  219. package/dist/webui/logos/litellm.svg +7 -0
  220. package/dist/webui/logos/openrouter.svg +1 -0
  221. package/package.json +8 -7
  222. package/dist/webui/assets/index-BkwPkZpd.css +0 -1
  223. package/dist/webui/assets/index-D9u1XfyH.js +0 -2025
package/dist/index.js CHANGED
@@ -19,10 +19,10 @@ import { resolveAgentPath, loadAgentConfig, globalPreferencesExist, loadGlobalPr
19
19
  import { startHonoApiServer } from './api/server-hono.js';
20
20
  import { validateCliOptions, handleCliOptionsError } from './cli/utils/options.js';
21
21
  import { validateAgentConfig } from './cli/utils/config-validation.js';
22
- import { applyCLIOverrides } from './config/cli-overrides.js';
22
+ import { applyCLIOverrides, applyUserPreferences, checkAgentCompatibility, } from './config/cli-overrides.js';
23
23
  import { enrichAgentConfig } from '@dexto/agent-management';
24
24
  import { getPort } from './utils/port-utils.js';
25
- import { createDextoProject, createTsconfigJson, addDextoScriptsToPackageJson, postCreateDexto, initDexto, postInitDexto, getUserInputToInitDextoApp, } from './cli/commands/index.js';
25
+ import { createDextoProject, createImage, getUserInputToInitDextoApp, initDexto, postInitDexto, } from './cli/commands/index.js';
26
26
  import { handleSetupCommand, handleInstallCommand, handleUninstallCommand, handleListAgentsCommand, handleWhichCommand, } from './cli/commands/index.js';
27
27
  import { handleSessionListCommand, handleSessionHistoryCommand, handleSessionDeleteCommand, handleSessionSearchCommand, } from './cli/commands/session-commands.js';
28
28
  import { requiresSetup } from './cli/utils/setup-utils.js';
@@ -34,6 +34,41 @@ import { initializeMcpToolAggregationServer } from './api/mcp/tool-aggregation-h
34
34
  const program = new Command();
35
35
  // Initialize analytics early (no-op if disabled)
36
36
  await initAnalytics({ appVersion: pkg.version });
37
+ /**
38
+ * Recursively removes null values from an object.
39
+ * This handles YAML files that have explicit `apiKey: null` entries
40
+ * which would otherwise cause "Expected string, received null" validation errors.
41
+ */
42
+ function cleanNullValues(obj) {
43
+ if (obj === null || obj === undefined)
44
+ return obj;
45
+ if (typeof obj !== 'object')
46
+ return obj;
47
+ if (Array.isArray(obj)) {
48
+ return obj.map((item) => typeof item === 'object' && item !== null
49
+ ? cleanNullValues(item)
50
+ : item);
51
+ }
52
+ const cleaned = {};
53
+ for (const [key, value] of Object.entries(obj)) {
54
+ if (value === null) {
55
+ // Skip null values - they become undefined (missing)
56
+ continue;
57
+ }
58
+ if (typeof value === 'object' && !Array.isArray(value)) {
59
+ cleaned[key] = cleanNullValues(value);
60
+ }
61
+ else if (Array.isArray(value)) {
62
+ cleaned[key] = value.map((item) => typeof item === 'object' && item !== null
63
+ ? cleanNullValues(item)
64
+ : item);
65
+ }
66
+ else {
67
+ cleaned[key] = value;
68
+ }
69
+ }
70
+ return cleaned;
71
+ }
37
72
  // 1) GLOBAL OPTIONS
38
73
  program
39
74
  .name('dexto')
@@ -52,36 +87,22 @@ program
52
87
  .option('--mode <mode>', 'The application in which dexto should talk to you - web | cli | server | mcp', 'web')
53
88
  .option('--port <port>', 'port for the server (default: 3000 for web, 3001 for server mode)')
54
89
  .option('--no-auto-install', 'Disable automatic installation of missing agents from registry')
90
+ .option('--image <package>', 'Image package to load (e.g., @dexto/image-local). Overrides config image field.')
55
91
  .enablePositionalOptions();
56
92
  // 2) `create-app` SUB-COMMAND
57
93
  program
58
- .command('create-app')
59
- .description('Scaffold a new Dexto Typescript app')
60
- .action(withAnalytics('create-app', async () => {
94
+ .command('create-app [name]')
95
+ .description('Create a Dexto application (CLI, web, bot, etc.)')
96
+ .option('--from-image <package>', 'Use existing image (e.g., @dexto/image-local)')
97
+ .option('--extend-image <package>', 'Extend image with custom providers')
98
+ .option('--from-core', 'Build from @dexto/core (advanced)')
99
+ .option('--type <type>', 'App type: script, webapp (default: script)')
100
+ .action(withAnalytics('create-app', async (name, options) => {
61
101
  try {
62
- p.intro(chalk.inverse('Dexto Create App'));
63
- // first setup the initial files in the project and get the project path
64
- const appPath = await createDextoProject();
65
- // then get user inputs for directory, llm etc.
66
- const userInput = await getUserInputToInitDextoApp();
67
- try {
68
- capture('dexto_create', {
69
- provider: userInput.llmProvider,
70
- providedKey: Boolean(userInput.llmApiKey),
71
- });
72
- }
73
- catch {
74
- // Analytics failures should not block CLI execution.
75
- }
76
- // move to project directory, then add the dexto scripts to the package.json and create the tsconfig.json
77
- process.chdir(appPath);
78
- await addDextoScriptsToPackageJson(userInput.directory, appPath);
79
- await createTsconfigJson(appPath, userInput.directory);
80
- // then initialize the other parts of the project
81
- await initDexto(userInput.directory, userInput.createExampleFile, userInput.llmProvider, userInput.llmApiKey);
82
- p.outro(chalk.greenBright('Dexto app created and initialized successfully!'));
83
- // add notes for users to get started with their newly created Dexto project
84
- await postCreateDexto(appPath, userInput.directory);
102
+ p.intro(chalk.inverse('Create Dexto App'));
103
+ // Create the app project structure (fully self-contained)
104
+ await createDextoProject(name, options);
105
+ p.outro(chalk.greenBright('Dexto app created successfully!'));
85
106
  safeExit('create-app', 0);
86
107
  }
87
108
  catch (err) {
@@ -91,7 +112,26 @@ program
91
112
  safeExit('create-app', 1, 'error');
92
113
  }
93
114
  }));
94
- // 3) `init-app` SUB-COMMAND
115
+ // 3) `create-image` SUB-COMMAND
116
+ program
117
+ .command('create-image [name]')
118
+ .description('Create a Dexto image - a distributable agent harness package')
119
+ .action(withAnalytics('create-image', async (name) => {
120
+ try {
121
+ p.intro(chalk.inverse('Create Dexto Image'));
122
+ // Create the image project structure
123
+ const projectPath = await createImage(name);
124
+ p.outro(chalk.greenBright(`Dexto image created successfully at ${projectPath}!`));
125
+ safeExit('create-image', 0);
126
+ }
127
+ catch (err) {
128
+ if (err instanceof ExitSignal)
129
+ throw err;
130
+ console.error(`❌ dexto create-image command failed: ${err}`);
131
+ safeExit('create-image', 1, 'error');
132
+ }
133
+ }));
134
+ // 4) `init-app` SUB-COMMAND
95
135
  program
96
136
  .command('init-app')
97
137
  .description('Initialize an existing Typescript app with Dexto')
@@ -130,13 +170,13 @@ program
130
170
  safeExit('init-app', 1, 'error');
131
171
  }
132
172
  }));
133
- // 4) `setup` SUB-COMMAND
173
+ // 5) `setup` SUB-COMMAND
134
174
  program
135
175
  .command('setup')
136
176
  .description('Configure global Dexto preferences')
137
177
  .option('--provider <provider>', 'LLM provider (openai, anthropic, google, groq)')
138
178
  .option('--model <model>', 'Model name (uses provider default if not specified)')
139
- .option('--default-agent <agent>', 'Default agent name (default: default-agent)')
179
+ .option('--default-agent <agent>', 'Default agent name (default: coding-agent)')
140
180
  .option('--no-interactive', 'Skip interactive prompts and API key setup')
141
181
  .option('--force', 'Overwrite existing setup without confirmation')
142
182
  .action(withAnalytics('setup', async (options) => {
@@ -151,7 +191,7 @@ program
151
191
  safeExit('setup', 1, 'error');
152
192
  }
153
193
  }));
154
- // 5) `install` SUB-COMMAND
194
+ // 6) `install` SUB-COMMAND
155
195
  program
156
196
  .command('install [agents...]')
157
197
  .description('Install agents from registry or custom YAML files/directories')
@@ -160,7 +200,7 @@ program
160
200
  .option('--force', 'Force reinstall even if agent is already installed')
161
201
  .addHelpText('after', `
162
202
  Examples:
163
- $ dexto install default-agent Install agent from registry
203
+ $ dexto install coding-agent Install agent from registry
164
204
  $ dexto install agent1 agent2 Install multiple registry agents
165
205
  $ dexto install --all Install all available registry agents
166
206
  $ dexto install ./my-agent.yml Install custom agent from YAML file
@@ -177,12 +217,12 @@ Examples:
177
217
  safeExit('install', 1, 'error');
178
218
  }
179
219
  }));
180
- // 6) `uninstall` SUB-COMMAND
220
+ // 7) `uninstall` SUB-COMMAND
181
221
  program
182
222
  .command('uninstall [agents...]')
183
223
  .description('Uninstall agents from the local installation')
184
224
  .option('--all', 'Uninstall all installed agents')
185
- .option('--force', 'Force uninstall even if agent is protected (e.g., default-agent)')
225
+ .option('--force', 'Force uninstall even if agent is protected (e.g., coding-agent)')
186
226
  .action(withAnalytics('uninstall', async (agents, options) => {
187
227
  try {
188
228
  await handleUninstallCommand(agents, options);
@@ -195,7 +235,7 @@ program
195
235
  safeExit('uninstall', 1, 'error');
196
236
  }
197
237
  }));
198
- // 7) `list-agents` SUB-COMMAND
238
+ // 8) `list-agents` SUB-COMMAND
199
239
  program
200
240
  .command('list-agents')
201
241
  .description('List available and installed agents')
@@ -214,7 +254,7 @@ program
214
254
  safeExit('list-agents', 1, 'error');
215
255
  }
216
256
  }));
217
- // 8) `which` SUB-COMMAND
257
+ // 9) `which` SUB-COMMAND
218
258
  program
219
259
  .command('which <agent>')
220
260
  .description('Show the path to an agent')
@@ -233,12 +273,27 @@ program
233
273
  // Helper to bootstrap a minimal agent for non-interactive session/search ops
234
274
  async function bootstrapAgentFromGlobalOpts() {
235
275
  const globalOpts = program.opts();
236
- const resolvedPath = await resolveAgentPath(globalOpts.agent, globalOpts.autoInstall !== false, true);
276
+ const resolvedPath = await resolveAgentPath(globalOpts.agent, globalOpts.autoInstall !== false);
237
277
  const rawConfig = await loadAgentConfig(resolvedPath);
238
278
  const mergedConfig = applyCLIOverrides(rawConfig, globalOpts);
239
279
  const enrichedConfig = enrichAgentConfig(mergedConfig, resolvedPath, {
240
280
  logLevel: 'info', // CLI uses info-level logging for visibility
241
281
  });
282
+ // Load image dynamically if specified (same priority as main command)
283
+ // Priority: CLI flag > Agent config > Environment variable > Default
284
+ // Images are optional, but default to image-local for convenience
285
+ const imageName = globalOpts.image || // --image flag
286
+ enrichedConfig.image || // image field in agent config
287
+ process.env.DEXTO_IMAGE || // DEXTO_IMAGE env var
288
+ '@dexto/image-local'; // Default for convenience
289
+ try {
290
+ await import(imageName);
291
+ }
292
+ catch (_err) {
293
+ console.error(`❌ Failed to load image '${imageName}'`);
294
+ console.error(`💡 Install it with: ${existsSync('package.json') ? 'npm install' : 'npm install -g'} ${imageName}`);
295
+ safeExit('bootstrap', 1, 'image-load-failed');
296
+ }
242
297
  // Override approval config for read-only commands (never run conversations)
243
298
  // This avoids needing to set up unused approval handlers
244
299
  enrichedConfig.toolConfirmation = {
@@ -253,7 +308,8 @@ async function bootstrapAgentFromGlobalOpts() {
253
308
  timeout: enrichedConfig.elicitation.timeout,
254
309
  }),
255
310
  };
256
- const agent = new DextoAgent(enrichedConfig, resolvedPath);
311
+ // Use relaxed validation for session commands - they don't need LLM calls
312
+ const agent = new DextoAgent(enrichedConfig, resolvedPath, { strict: false });
257
313
  await agent.start();
258
314
  // Register graceful shutdown
259
315
  const shutdown = async () => {
@@ -292,7 +348,7 @@ async function getMostRecentSessionId(agent, includeHeadless = false) {
292
348
  }
293
349
  return mostRecentId;
294
350
  }
295
- // 9) `session` SUB-COMMAND
351
+ // 10) `session` SUB-COMMAND
296
352
  const sessionCommand = program.command('session').description('Manage chat sessions');
297
353
  sessionCommand
298
354
  .command('list')
@@ -347,7 +403,7 @@ sessionCommand
347
403
  safeExit('session delete', 1, 'error');
348
404
  }
349
405
  }));
350
- // 10) `search` SUB-COMMAND
406
+ // 11) `search` SUB-COMMAND
351
407
  program
352
408
  .command('search')
353
409
  .description('Search session history')
@@ -389,7 +445,7 @@ program
389
445
  safeExit('search', 1, 'error');
390
446
  }
391
447
  }));
392
- // 11) `mcp` SUB-COMMAND
448
+ // 12) `mcp` SUB-COMMAND
393
449
  // For now, this mode simply aggregates and re-expose tools from configured MCP servers (no agent)
394
450
  // dexto --mode mcp will be moved to this sub-command in the future
395
451
  program
@@ -412,7 +468,7 @@ program
412
468
  // Get the global agent option from the main program
413
469
  const globalOpts = program.opts();
414
470
  const nameOrPath = globalOpts.agent;
415
- const configPath = await resolveAgentPath(nameOrPath, globalOpts.autoInstall !== false, true);
471
+ const configPath = await resolveAgentPath(nameOrPath, globalOpts.autoInstall !== false);
416
472
  console.log(`📄 Loading Dexto config from: ${configPath}`);
417
473
  const config = await loadAgentConfig(configPath);
418
474
  logger.info(`Validating MCP servers...`);
@@ -442,7 +498,7 @@ program
442
498
  safeExit('mcp', 1, 'mcp-agg-failed');
443
499
  }
444
500
  }, { timeoutMs: 0 }));
445
- // 10) Main dexto CLI - Interactive/One shot (CLI/HEADLESS) or run in other modes (--mode web/server/mcp)
501
+ // 13) Main dexto CLI - Interactive/One shot (CLI/HEADLESS) or run in other modes (--mode web/server/mcp)
446
502
  program
447
503
  .argument('[prompt...]', 'Natural-language prompt to run once. If not passed, dexto will start as an interactive CLI')
448
504
  // Main customer facing description
@@ -565,10 +621,14 @@ program
565
621
  // ——— ENHANCED PREFERENCE-AWARE CONFIG LOADING ———
566
622
  let validatedConfig;
567
623
  let resolvedPath;
624
+ // Determine validation mode early - used throughout config loading and agent creation
625
+ // Use relaxed validation for interactive modes (web/cli) where users can configure later
626
+ // Use strict validation for headless modes (server/mcp) that need full config upfront
627
+ const isInteractiveMode = opts.mode === 'web' || opts.mode === 'cli';
568
628
  try {
569
629
  // Case 1: File path - skip all validation and setup
570
630
  if (opts.agent && isPath(opts.agent)) {
571
- resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false, true);
631
+ resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false);
572
632
  }
573
633
  // Cases 2 & 3: Default agent or registry agent
574
634
  else {
@@ -608,23 +668,190 @@ program
608
668
  safeExit('main', 1, 'setup-required-non-interactive');
609
669
  }
610
670
  await handleSetupCommand({ interactive: true });
671
+ // Reload preferences after setup to get the newly selected default mode
672
+ // (setup may have just saved a different mode than the default 'web')
673
+ try {
674
+ const newPreferences = await loadGlobalPreferences();
675
+ if (newPreferences.defaults?.defaultMode) {
676
+ opts.mode = newPreferences.defaults.defaultMode;
677
+ logger.debug(`Updated mode from setup preferences: ${opts.mode}`);
678
+ }
679
+ }
680
+ catch {
681
+ // Ignore errors - will use default mode
682
+ }
611
683
  }
612
- // Now resolve agent (will auto-install with preferences since setup is complete)
613
- resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false, true);
684
+ // Now resolve agent (will auto-install since setup is complete)
685
+ resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false);
614
686
  }
615
687
  // Load raw config and apply CLI overrides
616
688
  const rawConfig = await loadAgentConfig(resolvedPath);
617
- const mergedConfig = applyCLIOverrides(rawConfig, opts);
689
+ let mergedConfig = applyCLIOverrides(rawConfig, opts);
690
+ // ——— PREFERENCE-AWARE CONFIG HANDLING ———
691
+ // For coding-agent (no explicit agent specified): Apply user preferences
692
+ // For specific agents: Check compatibility and warn if needed
693
+ const isDefaultAgent = !opts.agent;
694
+ let preferences = null;
695
+ if (globalPreferencesExist()) {
696
+ try {
697
+ preferences = await loadGlobalPreferences();
698
+ }
699
+ catch {
700
+ // Preferences exist but couldn't load - continue without them
701
+ logger.debug('Could not load preferences, continuing without them');
702
+ }
703
+ }
704
+ // Check for pending API key setup (user skipped during initial setup)
705
+ if (isDefaultAgent &&
706
+ preferences?.setup?.apiKeyPending &&
707
+ opts.interactive !== false) {
708
+ // Check if API key is still missing (user may have set it manually)
709
+ const configuredApiKey = resolveApiKeyForProvider(preferences.llm.provider);
710
+ if (!configuredApiKey) {
711
+ const { promptForPendingApiKey } = await import('./cli/utils/api-key-setup.js');
712
+ const { updateGlobalPreferences } = await import('@dexto/agent-management');
713
+ const result = await promptForPendingApiKey(preferences.llm.provider, preferences.llm.model);
714
+ if (result.action === 'cancel') {
715
+ safeExit('main', 0, 'pending-api-key-cancelled');
716
+ }
717
+ if (result.action === 'setup' && result.apiKey) {
718
+ // API key was configured - update preferences to clear pending flag
719
+ await updateGlobalPreferences({
720
+ setup: { apiKeyPending: false },
721
+ });
722
+ // Update the merged config with the new API key
723
+ mergedConfig.llm.apiKey = result.apiKey;
724
+ logger.debug('API key configured, pending flag cleared');
725
+ }
726
+ // If 'skip', continue without API key (user chose to proceed)
727
+ }
728
+ else {
729
+ // API key exists (user set it manually) - clear the pending flag
730
+ const { updateGlobalPreferences } = await import('@dexto/agent-management');
731
+ await updateGlobalPreferences({
732
+ setup: { apiKeyPending: false },
733
+ });
734
+ logger.debug('API key found in environment, cleared pending flag');
735
+ }
736
+ }
737
+ if (isDefaultAgent && preferences) {
738
+ // Default-agent: Apply user's LLM preferences at runtime
739
+ // This ensures the base agent always uses user's preferred model/provider
740
+ mergedConfig = applyUserPreferences(mergedConfig, preferences);
741
+ logger.debug('Applied user preferences to coding-agent', {
742
+ provider: preferences.llm.provider,
743
+ model: preferences.llm.model,
744
+ });
745
+ }
746
+ else if (!isDefaultAgent && mergedConfig.llm) {
747
+ // Specific agent: Check if user has the required provider configured
748
+ const agentProvider = mergedConfig.llm.provider;
749
+ const resolvedApiKey = resolveApiKeyForProvider(agentProvider);
750
+ const compatibility = checkAgentCompatibility(mergedConfig, preferences, resolvedApiKey);
751
+ if (!compatibility.compatible && opts.interactive !== false) {
752
+ // User is missing API key for the agent's provider
753
+ if (!compatibility.userHasApiKey &&
754
+ preferences?.llm?.provider &&
755
+ preferences?.llm?.model) {
756
+ // User has a default LLM configured - offer choice
757
+ const { promptForMissingAgentApiKey } = await import('./cli/utils/api-key-setup.js');
758
+ const result = await promptForMissingAgentApiKey(compatibility.agentProvider, compatibility.agentModel, preferences.llm.provider, preferences.llm.model);
759
+ if (result.action === 'cancel') {
760
+ safeExit('main', 0, 'agent-api-key-cancelled');
761
+ }
762
+ if (result.action === 'use-default') {
763
+ // Apply user's default LLM to the agent config
764
+ mergedConfig = applyUserPreferences(mergedConfig, preferences);
765
+ // Also resolve the actual API key from environment
766
+ // (preferences store env var reference like $GOOGLE_API_KEY, not the actual key)
767
+ const userApiKey = resolveApiKeyForProvider(preferences.llm.provider);
768
+ if (userApiKey) {
769
+ mergedConfig.llm.apiKey = userApiKey;
770
+ }
771
+ logger.debug('Applied user preferences to agent (user chose default)', {
772
+ provider: preferences.llm.provider,
773
+ model: preferences.llm.model,
774
+ });
775
+ }
776
+ if (result.action === 'add-key' && result.apiKey) {
777
+ // User added the API key - update the config
778
+ mergedConfig.llm.apiKey = result.apiKey;
779
+ logger.debug('Applied new API key to agent config');
780
+ }
781
+ }
782
+ else {
783
+ // No default LLM to fall back to - show warnings only
784
+ console.log(chalk.yellow('\n⚠️ Agent Compatibility Notice:'));
785
+ for (const warning of compatibility.warnings) {
786
+ console.log(chalk.yellow(` ${warning}`));
787
+ }
788
+ if (compatibility.instructions.length > 0) {
789
+ console.log(chalk.dim('\n To fix:'));
790
+ for (const instruction of compatibility.instructions) {
791
+ console.log(chalk.dim(` ${instruction}`));
792
+ }
793
+ }
794
+ console.log(''); // Empty line for spacing
795
+ }
796
+ }
797
+ }
798
+ // Clean up null values from config (can happen from YAML files with explicit nulls)
799
+ // This prevents "Expected string, received null" errors for optional fields
800
+ const cleanedConfig = cleanNullValues(mergedConfig);
618
801
  // Enrich config with per-agent paths BEFORE validation
619
802
  // Enrichment adds filesystem paths to storage (schema has in-memory defaults)
620
803
  // Interactive CLI mode: only log to file (console would interfere with chat UI)
621
804
  const isInteractiveCli = opts.mode === 'cli' && !headlessInput;
622
- const enrichedConfig = enrichAgentConfig(mergedConfig, resolvedPath, {
805
+ const enrichedConfig = enrichAgentConfig(cleanedConfig, resolvedPath, {
623
806
  isInteractiveCli,
624
807
  logLevel: 'info', // CLI uses info-level logging for visibility
625
808
  });
626
809
  // Validate enriched config with interactive setup if needed (for API key issues)
627
- validatedConfig = await validateAgentConfig(enrichedConfig, opts.interactive !== false);
810
+ // isInteractiveMode is defined above the try block
811
+ const validationResult = await validateAgentConfig(enrichedConfig, opts.interactive !== false, { strict: !isInteractiveMode });
812
+ if (validationResult.success && validationResult.config) {
813
+ validatedConfig = validationResult.config;
814
+ }
815
+ else if (validationResult.skipped) {
816
+ // User chose to continue despite validation errors
817
+ // SAFETY: This cast is intentionally unsafe - it's an escape hatch for users
818
+ // when validation is overly strict or incorrect. Runtime errors will surface
819
+ // if the config truly doesn't work. Future: explicit `allowUnvalidated` mode.
820
+ logger.warn('Starting with validation warnings - some features may not work');
821
+ validatedConfig = enrichedConfig;
822
+ }
823
+ else {
824
+ // Validation failed and user didn't skip - show next steps and exit
825
+ safeExit('main', 1, 'config-validation-failed');
826
+ }
827
+ // ——— LOAD IMAGE DYNAMICALLY (if specified) ———
828
+ // Priority: CLI flag > Agent config > Environment variable > Default
829
+ // Images are optional, but default to image-local for convenience
830
+ const imageName = opts.image || // --image flag
831
+ validatedConfig.image || // image field in agent config
832
+ process.env.DEXTO_IMAGE || // DEXTO_IMAGE env var
833
+ '@dexto/image-local'; // Default for convenience
834
+ try {
835
+ await import(imageName);
836
+ logger.debug(`Loaded image: ${imageName}`);
837
+ }
838
+ catch (err) {
839
+ console.error(`❌ Failed to load image '${imageName}'`);
840
+ console.error(`💡 Install it with: ${existsSync('package.json') ? 'npm install' : 'npm install -g'} ${imageName}`);
841
+ if (err instanceof Error) {
842
+ logger.debug(`Image load error: ${err.message}`);
843
+ }
844
+ safeExit('main', 1, 'image-load-failed');
845
+ }
846
+ // Validate that if config specifies an image, it matches what was loaded
847
+ // Skip this check if user explicitly provided --image flag (intentional override)
848
+ if (!opts.image &&
849
+ validatedConfig.image &&
850
+ validatedConfig.image !== imageName) {
851
+ console.error(`❌ Config specifies image '${validatedConfig.image}' but '${imageName}' was loaded instead`);
852
+ console.error(`💡 Either remove 'image' from config or ensure it matches the loaded image`);
853
+ safeExit('main', 1, 'image-mismatch');
854
+ }
628
855
  }
629
856
  catch (err) {
630
857
  if (err instanceof ExitSignal)
@@ -677,7 +904,10 @@ program
677
904
  }
678
905
  // Config is already enriched and validated - ready for agent creation
679
906
  // DextoAgent will parse/validate again (parse-twice pattern)
680
- agent = new DextoAgent(validatedConfig, resolvedPath);
907
+ // isInteractiveMode is already defined above for validateAgentConfig
908
+ agent = new DextoAgent(validatedConfig, resolvedPath, {
909
+ strict: !isInteractiveMode,
910
+ });
681
911
  // Start the agent (initialize async services)
682
912
  // - web/server modes: initializeHonoApi will set approval handler and start the agent
683
913
  // - cli mode: handles its own approval setup in the case block
@@ -822,6 +1052,38 @@ program
822
1052
  else {
823
1053
  // Interactive mode - session management handled via /resume command
824
1054
  // Note: -c and -r flags are validated to require a prompt (headless mode only)
1055
+ // Check if API key is configured before trying to create session
1056
+ // Session creation triggers LLM service init which requires API key
1057
+ const llmConfig = agent.getCurrentLLMConfig();
1058
+ const { requiresApiKey } = await import('@dexto/core');
1059
+ if (requiresApiKey(llmConfig.provider) && !llmConfig.apiKey?.trim()) {
1060
+ // Offer interactive API key setup instead of just exiting
1061
+ const { interactiveApiKeySetup } = await import('./cli/utils/api-key-setup.js');
1062
+ console.log(chalk.yellow(`\n⚠️ API key required for provider '${llmConfig.provider}'\n`));
1063
+ const setupResult = await interactiveApiKeySetup(llmConfig.provider, {
1064
+ exitOnCancel: false,
1065
+ model: llmConfig.model,
1066
+ });
1067
+ if (setupResult.cancelled) {
1068
+ await agent.stop().catch(() => { });
1069
+ safeExit('main', 0, 'api-key-setup-cancelled');
1070
+ }
1071
+ if (setupResult.skipped) {
1072
+ // User chose to skip - exit with instructions
1073
+ await agent.stop().catch(() => { });
1074
+ safeExit('main', 0, 'api-key-pending');
1075
+ }
1076
+ if (setupResult.success && setupResult.apiKey) {
1077
+ // API key was entered and saved - reload config and continue
1078
+ // Update the agent's LLM config with the new API key
1079
+ await agent.switchLLM({
1080
+ provider: llmConfig.provider,
1081
+ model: llmConfig.model,
1082
+ apiKey: setupResult.apiKey,
1083
+ });
1084
+ logger.info('API key configured successfully, continuing...');
1085
+ }
1086
+ }
825
1087
  // Create session eagerly so slash commands work immediately
826
1088
  const session = await agent.createSession();
827
1089
  const cliSessionId = session.id;
@@ -873,8 +1135,8 @@ program
873
1135
  }
874
1136
  safeExit('main', 0);
875
1137
  }
876
- break;
877
1138
  }
1139
+ // falls through - safeExit returns never, but eslint doesn't know that
878
1140
  case 'web': {
879
1141
  // Default to 3000 for web mode
880
1142
  const defaultPort = opts.port ? parseInt(opts.port, 10) : 3000;
@@ -975,5 +1237,5 @@ program
975
1237
  safeExit('main', 1, 'unknown-mode');
976
1238
  }
977
1239
  }, { timeoutMs: 0 }));
978
- // 11) PARSE & EXECUTE
1240
+ // 14) PARSE & EXECUTE
979
1241
  program.parseAsync(process.argv);