dexto 1.0.1 → 1.1.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 (166) hide show
  1. package/README.md +14 -12
  2. package/agents/agent-registry.json +53 -0
  3. package/agents/database-agent/README.md +35 -0
  4. package/agents/database-agent/data/example.db +0 -0
  5. package/agents/database-agent/database-agent-example.sql +98 -0
  6. package/agents/database-agent/database-agent.yml +116 -0
  7. package/agents/database-agent/setup-database.sh +64 -0
  8. package/agents/{agent.yml → default-agent.yml} +1 -1
  9. package/agents/image-editor-agent/Lenna.webp +0 -0
  10. package/agents/image-editor-agent/README.md +435 -0
  11. package/agents/image-editor-agent/image-editor-agent.yml +45 -0
  12. package/agents/music-agent/README.md +294 -0
  13. package/agents/music-agent/music-agent.yml +43 -0
  14. package/agents/product-name-researcher/README.md +98 -0
  15. package/agents/product-name-researcher/product-name-researcher.yml +161 -0
  16. package/agents/talk2pdf-agent/README.md +166 -0
  17. package/agents/talk2pdf-agent/talk2pdf-agent.yml +42 -0
  18. package/agents/triage-demo/README.md +337 -0
  19. package/agents/triage-demo/billing-agent.yml +76 -0
  20. package/agents/triage-demo/docs/billing-policies.md +246 -0
  21. package/agents/triage-demo/docs/company-overview.md +94 -0
  22. package/agents/triage-demo/docs/escalation-policies.md +301 -0
  23. package/agents/triage-demo/docs/product-features.md +253 -0
  24. package/agents/triage-demo/docs/technical-documentation.md +226 -0
  25. package/agents/triage-demo/escalation-agent.yml +82 -0
  26. package/agents/triage-demo/product-info-agent.yml +86 -0
  27. package/agents/triage-demo/technical-support-agent.yml +71 -0
  28. package/agents/triage-demo/test-scenarios.md +209 -0
  29. package/agents/triage-demo/triage-agent.yml +172 -0
  30. package/dist/src/app/{chunk-M4OZIDPC.js → chunk-CLDYRNV6.js} +7241 -5753
  31. package/dist/src/app/chunk-DYNVXGAH.js +137 -0
  32. package/dist/src/app/chunk-PW2PHCHR.js +83 -0
  33. package/dist/src/app/chunk-R4Q522DR.js +205 -0
  34. package/dist/src/app/chunk-UXCBS3TR.js +281 -0
  35. package/dist/src/app/chunk-X6LEX724.js +108 -0
  36. package/dist/src/app/chunk-Y33BS5SA.js +39 -0
  37. package/dist/src/app/{cli-confirmation-handler-2APQRKHG.js → cli-confirmation-handler-GJHPLGOL.js} +4 -1
  38. package/dist/src/app/errors-5MNETGOV.js +8 -0
  39. package/dist/src/app/index.js +2184 -612
  40. package/dist/src/app/loader-EFMKWNNQ.js +20 -0
  41. package/dist/src/app/path-7FT4SZMO.js +23 -0
  42. package/dist/src/app/{postgres-backend-7HVVW3RL.js → postgres-backend-U5MIIMUY.js} +12 -1
  43. package/dist/src/app/{redis-backend-2YBZSSSV.js → redis-backend-NGI67ILT.js} +38 -9
  44. package/dist/src/app/registry-RALMVM3P.js +14 -0
  45. package/dist/src/app/sqlite-backend-752UUBD4.js +245 -0
  46. package/dist/src/app/webui/.next/standalone/.next/BUILD_ID +1 -1
  47. package/dist/src/app/webui/.next/standalone/.next/app-build-manifest.json +20 -21
  48. package/dist/src/app/webui/.next/standalone/.next/app-path-routes-manifest.json +0 -1
  49. package/dist/src/app/webui/.next/standalone/.next/build-manifest.json +4 -4
  50. package/dist/src/app/webui/.next/standalone/.next/prerender-manifest.json +4 -82
  51. package/dist/src/app/webui/.next/standalone/.next/required-server-files.json +2 -3
  52. package/dist/src/app/webui/.next/standalone/.next/routes-manifest.json +0 -6
  53. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  54. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  55. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  56. package/dist/src/app/webui/.next/standalone/.next/server/app/page.js +7 -3
  57. package/dist/src/app/webui/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/dist/src/app/webui/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/dist/src/app/webui/.next/standalone/.next/server/app/playground/page.js +8 -8
  60. package/dist/src/app/webui/.next/standalone/.next/server/app/playground/page.js.nft.json +1 -1
  61. package/dist/src/app/webui/.next/standalone/.next/server/app/playground/page_client-reference-manifest.js +1 -1
  62. package/dist/src/app/webui/.next/standalone/.next/server/app-paths-manifest.json +0 -1
  63. package/dist/src/app/webui/.next/standalone/.next/server/chunks/176.js +1 -0
  64. package/dist/src/app/webui/.next/standalone/.next/server/chunks/195.js +24 -0
  65. package/dist/src/app/webui/.next/standalone/.next/server/chunks/620.js +1 -0
  66. package/dist/src/app/webui/.next/standalone/.next/server/chunks/80.js +5 -0
  67. package/dist/src/app/webui/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  68. package/dist/src/app/webui/.next/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/src/app/webui/.next/standalone/.next/server/pages-manifest.json +1 -2
  70. package/dist/src/app/webui/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  71. package/dist/src/app/webui/.next/standalone/.next/server/webpack-runtime.js +1 -1
  72. package/dist/src/app/webui/.next/standalone/.next/static/chunks/190-b897ef36fde616bf.js +1 -0
  73. package/dist/src/app/webui/.next/standalone/.next/static/chunks/487-a77054bd2c64c79c.js +1 -0
  74. package/dist/src/app/webui/.next/standalone/.next/static/chunks/588-dbe47a44489742dd.js +1 -0
  75. package/dist/src/app/webui/.next/standalone/.next/static/chunks/62-35030b5cb176bd7b.js +1 -0
  76. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/layout-91c0cb9eb1ee327a.js +1 -0
  77. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/page-3279aaf14db87f45.js +1 -0
  78. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/playground/page-200aad53af9ca53f.js +1 -0
  79. package/dist/src/app/webui/.next/standalone/.next/static/css/af71306827be150e.css +3 -0
  80. package/dist/src/app/webui/.next/standalone/.next/static/tpCURR82LyyGfdALJ4Qvl/_buildManifest.js +1 -0
  81. package/dist/src/app/webui/.next/standalone/package.json +4 -3
  82. package/dist/src/app/webui/.next/standalone/public/favicon2.ico +0 -0
  83. package/dist/src/app/webui/.next/standalone/public/logo.svg +1 -0
  84. package/dist/src/app/webui/.next/standalone/public/logo_no_text.png +0 -0
  85. package/dist/src/app/webui/.next/standalone/server.js +1 -1
  86. package/dist/src/app/webui/.next/static/chunks/190-b897ef36fde616bf.js +1 -0
  87. package/dist/src/app/webui/.next/static/chunks/487-a77054bd2c64c79c.js +1 -0
  88. package/dist/src/app/webui/.next/static/chunks/588-dbe47a44489742dd.js +1 -0
  89. package/dist/src/app/webui/.next/static/chunks/62-35030b5cb176bd7b.js +1 -0
  90. package/dist/src/app/webui/.next/static/chunks/app/layout-91c0cb9eb1ee327a.js +1 -0
  91. package/dist/src/app/webui/.next/static/chunks/app/page-3279aaf14db87f45.js +1 -0
  92. package/dist/src/app/webui/.next/static/chunks/app/playground/page-200aad53af9ca53f.js +1 -0
  93. package/dist/src/app/webui/.next/static/css/af71306827be150e.css +3 -0
  94. package/dist/src/app/webui/.next/static/tpCURR82LyyGfdALJ4Qvl/_buildManifest.js +1 -0
  95. package/dist/src/app/webui/package.json +4 -3
  96. package/dist/src/app/webui/public/favicon2.ico +0 -0
  97. package/dist/src/app/webui/public/logo.svg +1 -0
  98. package/dist/src/app/webui/public/logo_no_text.png +0 -0
  99. package/dist/src/core/chunk-7A6NQKQ3.js +193 -0
  100. package/dist/src/core/chunk-CZIXQNMZ.js +12191 -0
  101. package/dist/src/core/index.cjs +10601 -8012
  102. package/dist/src/core/index.d.cts +4356 -3907
  103. package/dist/src/core/index.d.ts +4356 -3907
  104. package/dist/src/core/index.js +117 -8396
  105. package/dist/src/core/{postgres-backend-ERZ6DP76.js → postgres-backend-LOLKTD2T.js} +9 -2
  106. package/dist/src/core/{redis-backend-46O7Y44C.js → redis-backend-APZ576PJ.js} +35 -10
  107. package/dist/src/core/sqlite-backend-KQ75DPR7.js +245 -0
  108. package/package.json +20 -12
  109. package/dist/src/app/chunk-O5YHNFMH.js +0 -435
  110. package/dist/src/app/interactive-api-key-setup-V3GAACLN.js +0 -269
  111. package/dist/src/app/sqlite-backend-KIJZ5IP3.js +0 -180
  112. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found.html +0 -1
  113. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found.meta +0 -8
  114. package/dist/src/app/webui/.next/standalone/.next/server/app/_not-found.rsc +0 -21
  115. package/dist/src/app/webui/.next/standalone/.next/server/app/favicon.ico/route.js +0 -1
  116. package/dist/src/app/webui/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  117. package/dist/src/app/webui/.next/standalone/.next/server/app/favicon.ico.body +0 -0
  118. package/dist/src/app/webui/.next/standalone/.next/server/app/favicon.ico.meta +0 -1
  119. package/dist/src/app/webui/.next/standalone/.next/server/app/index.html +0 -1
  120. package/dist/src/app/webui/.next/standalone/.next/server/app/index.meta +0 -7
  121. package/dist/src/app/webui/.next/standalone/.next/server/app/index.rsc +0 -22
  122. package/dist/src/app/webui/.next/standalone/.next/server/app/playground.html +0 -1
  123. package/dist/src/app/webui/.next/standalone/.next/server/app/playground.meta +0 -7
  124. package/dist/src/app/webui/.next/standalone/.next/server/app/playground.rsc +0 -25
  125. package/dist/src/app/webui/.next/standalone/.next/server/chunks/447.js +0 -20
  126. package/dist/src/app/webui/.next/standalone/.next/server/chunks/504.js +0 -1
  127. package/dist/src/app/webui/.next/standalone/.next/server/chunks/54.js +0 -5
  128. package/dist/src/app/webui/.next/standalone/.next/server/chunks/624.js +0 -1
  129. package/dist/src/app/webui/.next/standalone/.next/server/chunks/985.js +0 -5
  130. package/dist/src/app/webui/.next/standalone/.next/server/pages/404.html +0 -1
  131. package/dist/src/app/webui/.next/standalone/.next/static/H-71qcBOOk528tDEa_ldn/_buildManifest.js +0 -1
  132. package/dist/src/app/webui/.next/standalone/.next/static/chunks/125-9b34ec01f112cdb2.js +0 -1
  133. package/dist/src/app/webui/.next/standalone/.next/static/chunks/487-c6ea8b63ca68db1c.js +0 -1
  134. package/dist/src/app/webui/.next/standalone/.next/static/chunks/588-20dc7f3a8664c387.js +0 -1
  135. package/dist/src/app/webui/.next/standalone/.next/static/chunks/687-3e614f30982093f6.js +0 -1
  136. package/dist/src/app/webui/.next/standalone/.next/static/chunks/879-cf875984faa0b72f.js +0 -1
  137. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/layout-ed56660b7ecaf47b.js +0 -1
  138. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/page-b05580de36ce0e36.js +0 -1
  139. package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/playground/page-ac5443cddbe824aa.js +0 -1
  140. package/dist/src/app/webui/.next/standalone/.next/static/css/d44f09bc2605dc76.css +0 -3
  141. package/dist/src/app/webui/.next/standalone/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js +0 -19
  142. package/dist/src/app/webui/.next/standalone/public/favicon.svg +0 -26
  143. package/dist/src/app/webui/.next/standalone/public/logo.png +0 -0
  144. package/dist/src/app/webui/.next/static/H-71qcBOOk528tDEa_ldn/_buildManifest.js +0 -1
  145. package/dist/src/app/webui/.next/static/chunks/125-9b34ec01f112cdb2.js +0 -1
  146. package/dist/src/app/webui/.next/static/chunks/487-c6ea8b63ca68db1c.js +0 -1
  147. package/dist/src/app/webui/.next/static/chunks/588-20dc7f3a8664c387.js +0 -1
  148. package/dist/src/app/webui/.next/static/chunks/687-3e614f30982093f6.js +0 -1
  149. package/dist/src/app/webui/.next/static/chunks/879-cf875984faa0b72f.js +0 -1
  150. package/dist/src/app/webui/.next/static/chunks/app/layout-ed56660b7ecaf47b.js +0 -1
  151. package/dist/src/app/webui/.next/static/chunks/app/page-b05580de36ce0e36.js +0 -1
  152. package/dist/src/app/webui/.next/static/chunks/app/playground/page-ac5443cddbe824aa.js +0 -1
  153. package/dist/src/app/webui/.next/static/css/d44f09bc2605dc76.css +0 -3
  154. package/dist/src/app/webui/public/favicon.svg +0 -26
  155. package/dist/src/app/webui/public/logo.png +0 -0
  156. package/dist/src/core/chunk-BGO5B3L4.js +0 -2124
  157. package/dist/src/core/chunk-BYHW25EA.js +0 -41
  158. package/dist/src/core/sqlite-backend-RKK4WBHE.js +0 -184
  159. /package/dist/src/app/webui/.next/standalone/.next/static/chunks/{4bd1b696-2df85d4b3b58aed5.js → 4bd1b696-c95fa02060335229.js} +0 -0
  160. /package/dist/src/app/webui/.next/standalone/.next/static/chunks/{684-058c08971e023037.js → 684-2e7175657246b549.js} +0 -0
  161. /package/dist/src/app/webui/.next/standalone/.next/static/chunks/app/_not-found/{page-7b137d85f9de4771.js → page-b63df5a8d3225455.js} +0 -0
  162. /package/dist/src/app/webui/.next/standalone/.next/static/{H-71qcBOOk528tDEa_ldn → tpCURR82LyyGfdALJ4Qvl}/_ssgManifest.js +0 -0
  163. /package/dist/src/app/webui/.next/static/chunks/{4bd1b696-2df85d4b3b58aed5.js → 4bd1b696-c95fa02060335229.js} +0 -0
  164. /package/dist/src/app/webui/.next/static/chunks/{684-058c08971e023037.js → 684-2e7175657246b549.js} +0 -0
  165. /package/dist/src/app/webui/.next/static/chunks/app/_not-found/{page-7b137d85f9de4771.js → page-b63df5a8d3225455.js} +0 -0
  166. /package/dist/src/app/webui/.next/static/{H-71qcBOOk528tDEa_ldn → tpCURR82LyyGfdALJ4Qvl}/_ssgManifest.js +0 -0
@@ -1,57 +1,181 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getAgentRegistry
4
+ } from "./chunk-UXCBS3TR.js";
5
+ import {
6
+ createInitialPreferences,
7
+ globalPreferencesExist,
8
+ loadGlobalPreferences,
9
+ saveGlobalPreferences
10
+ } from "./chunk-R4Q522DR.js";
2
11
  import {
3
12
  AgentConfigSchema,
13
+ ConfigError,
4
14
  DextoAgent,
5
- DextoLLMError,
6
- DextoValidationError,
7
- FileNotFoundError,
8
15
  LLMUpdatesSchema,
9
16
  LLM_PROVIDERS,
10
17
  LLM_REGISTRY,
11
18
  MCPManager,
19
+ McpServerConfigSchema,
12
20
  NoOpConfirmationProvider,
13
- UnknownModelError,
14
- UnknownProviderError,
15
- addScriptsToPackageJson,
16
- checkForFileInCurrentDirectory,
17
21
  createAgentCard,
18
- executeWithTimeout,
22
+ ensureDextoGlobalDirectory,
23
+ findDextoProjectRoot,
24
+ findDextoSourceRoot,
25
+ findPackageRoot,
19
26
  getAllSupportedModels,
20
- getPackageManager,
21
- getPackageManagerInstallCommand,
27
+ getDefaultModelForProvider,
28
+ getDextoEnvPath,
29
+ getDextoGlobalPath,
30
+ getDextoPath,
31
+ getExecutionContext,
32
+ getPrimaryApiKeyEnvVar,
22
33
  getProviderFromModel,
34
+ getSupportedModels,
23
35
  getSupportedProviders,
24
36
  getSupportedRoutersForProvider,
25
- getUserInputToInitDextoApp,
26
- initDexto,
37
+ isPath,
38
+ isValidProviderModel,
27
39
  jsonSchemaToZodShape,
28
40
  loadAgentConfig,
29
- postInitDexto,
41
+ logger,
42
+ redactSensitiveData,
30
43
  resolveAndValidateMcpServerConfig,
31
44
  resolveApiKeyForProvider,
45
+ resolveBundledScript,
32
46
  supportsBaseURL,
33
- validateInputForLLM
34
- } from "./chunk-M4OZIDPC.js";
47
+ toError,
48
+ validateInputForLLM,
49
+ zodToIssues
50
+ } from "./chunk-CLDYRNV6.js";
51
+ import "./chunk-PW2PHCHR.js";
52
+ import "./chunk-DYNVXGAH.js";
35
53
  import {
36
- ConfigurationError,
37
- DEFAULT_CONFIG_PATH,
38
- getDextoPath,
39
- logger,
40
- resolveBundledScript,
41
- resolveConfigPath
42
- } from "./chunk-O5YHNFMH.js";
54
+ DextoValidationError
55
+ } from "./chunk-X6LEX724.js";
56
+ import {
57
+ DextoRuntimeError
58
+ } from "./chunk-Y33BS5SA.js";
59
+
60
+ // src/core/utils/env.ts
61
+ import * as path from "path";
62
+ import { homedir } from "os";
63
+ import { promises as fs } from "fs";
64
+ import dotenv from "dotenv";
65
+ async function loadEnvironmentVariables(startPath = process.cwd()) {
66
+ const context = getExecutionContext(startPath);
67
+ const env = {};
68
+ const globalEnvPath = path.join(homedir(), ".dexto", ".env");
69
+ try {
70
+ const globalResult = dotenv.config({ path: globalEnvPath, processEnv: {} });
71
+ if (globalResult.parsed) {
72
+ Object.assign(env, globalResult.parsed);
73
+ }
74
+ } catch {
75
+ }
76
+ if (context === "dexto-source" || context === "dexto-project") {
77
+ const projectEnvPath = getDextoEnvPath(startPath);
78
+ try {
79
+ const projectResult = dotenv.config({ path: projectEnvPath, processEnv: {} });
80
+ if (projectResult.parsed) {
81
+ Object.assign(env, projectResult.parsed);
82
+ }
83
+ } catch {
84
+ }
85
+ }
86
+ for (const [key, value] of Object.entries(process.env)) {
87
+ if (value !== void 0 && value !== "") {
88
+ env[key] = value;
89
+ }
90
+ }
91
+ return env;
92
+ }
93
+ async function applyLayeredEnvironmentLoading(startPath = process.cwd()) {
94
+ await ensureDextoGlobalDirectory();
95
+ const layeredEnv = await loadEnvironmentVariables(startPath);
96
+ Object.assign(process.env, layeredEnv);
97
+ }
98
+ async function updateEnvFile(envFilePath, updates) {
99
+ const dextoEnvKeys = [
100
+ "OPENAI_API_KEY",
101
+ "ANTHROPIC_API_KEY",
102
+ "GOOGLE_GENERATIVE_AI_API_KEY",
103
+ "GROQ_API_KEY",
104
+ "DEXTO_LOG_LEVEL"
105
+ ];
106
+ await fs.mkdir(path.dirname(envFilePath), { recursive: true });
107
+ let envLines = [];
108
+ try {
109
+ const existingEnv = await fs.readFile(envFilePath, "utf8");
110
+ envLines = existingEnv.split("\n");
111
+ } catch {
112
+ }
113
+ const currentValues = {};
114
+ envLines.forEach((line) => {
115
+ const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
116
+ if (match && match[1] && match[2] !== void 0 && dextoEnvKeys.includes(match[1])) {
117
+ currentValues[match[1]] = match[2];
118
+ }
119
+ });
120
+ const updatedValues = {
121
+ OPENAI_API_KEY: updates.OPENAI_API_KEY ?? currentValues.OPENAI_API_KEY ?? "",
122
+ ANTHROPIC_API_KEY: updates.ANTHROPIC_API_KEY ?? currentValues.ANTHROPIC_API_KEY ?? "",
123
+ GOOGLE_GENERATIVE_AI_API_KEY: updates.GOOGLE_GENERATIVE_AI_API_KEY ?? currentValues.GOOGLE_GENERATIVE_AI_API_KEY ?? "",
124
+ GROQ_API_KEY: updates.GROQ_API_KEY ?? currentValues.GROQ_API_KEY ?? "",
125
+ DEXTO_LOG_LEVEL: updates.DEXTO_LOG_LEVEL ?? currentValues.DEXTO_LOG_LEVEL ?? "info"
126
+ };
127
+ const sectionHeader = "## Dexto env variables";
128
+ const headerIndex = envLines.findIndex((line) => line.trim() === sectionHeader);
129
+ let contentLines;
130
+ if (headerIndex !== -1) {
131
+ const beforeSection = envLines.slice(0, headerIndex);
132
+ let sectionEnd = headerIndex + 1;
133
+ while (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() !== "") {
134
+ sectionEnd++;
135
+ }
136
+ if (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() === "") {
137
+ sectionEnd++;
138
+ }
139
+ const afterSection = envLines.slice(sectionEnd);
140
+ contentLines = [...beforeSection, ...afterSection];
141
+ } else {
142
+ contentLines = envLines;
143
+ }
144
+ const existingEnvVars = {};
145
+ contentLines.forEach((line) => {
146
+ const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
147
+ if (match && match[1] && match[2] !== void 0 && dextoEnvKeys.includes(match[1])) {
148
+ existingEnvVars[match[1]] = match[2];
149
+ }
150
+ });
151
+ if (contentLines.length > 0) {
152
+ if (contentLines[contentLines.length - 1]?.trim() !== "") {
153
+ contentLines.push("");
154
+ }
155
+ } else {
156
+ contentLines.push("");
157
+ }
158
+ contentLines.push(sectionHeader);
159
+ for (const key of dextoEnvKeys) {
160
+ if (key in existingEnvVars && !(key in updates)) {
161
+ continue;
162
+ }
163
+ contentLines.push(`${key}=${updatedValues[key]}`);
164
+ }
165
+ contentLines.push("");
166
+ await fs.writeFile(envFilePath, contentLines.join("\n"), "utf8");
167
+ }
43
168
 
44
169
  // src/app/index.ts
45
- import dotenv3 from "dotenv";
46
- import { existsSync as existsSync2 } from "fs";
170
+ import { existsSync as existsSync4 } from "fs";
47
171
  import { Command } from "commander";
48
- import * as p2 from "@clack/prompts";
49
- import chalk15 from "chalk";
172
+ import * as p6 from "@clack/prompts";
173
+ import chalk23 from "chalk";
50
174
 
51
175
  // package.json
52
176
  var package_default = {
53
177
  name: "dexto",
54
- version: "1.0.1",
178
+ version: "1.1.0",
55
179
  engines: {
56
180
  node: ">=20.0.0",
57
181
  npm: ">=8.3.0"
@@ -107,27 +231,35 @@ var package_default = {
107
231
  "cli",
108
232
  "natural-language",
109
233
  "openai",
110
- "truffle-ai"
234
+ "truffle-ai",
235
+ "dexto"
111
236
  ],
112
237
  author: "",
113
238
  license: "Elastic-2.0",
114
239
  files: [
115
240
  "dist/",
116
- "agents/agent.yml",
241
+ "agents/agent-registry.json",
117
242
  "agents/agent-template.yml",
243
+ "agents/default-agent.yml",
244
+ "agents/database-agent/",
245
+ "agents/talk2pdf-agent/",
246
+ "agents/image-editor-agent/",
247
+ "agents/music-agent/",
248
+ "agents/product-name-researcher/",
249
+ "agents/triage-demo/",
118
250
  "public/"
119
251
  ],
120
252
  dependencies: {
121
- "@ai-sdk/anthropic": "^1.2.2",
122
- "@ai-sdk/cohere": "^1.2.10",
123
- "@ai-sdk/google": "^1.2.8",
124
- "@ai-sdk/groq": "^1.2.9",
125
- "@ai-sdk/openai": "^1.3.3",
126
- "@ai-sdk/xai": "^1.2.17",
253
+ "@ai-sdk/anthropic": "^2.0.0",
254
+ "@ai-sdk/cohere": "^2.0.0",
255
+ "@ai-sdk/google": "^2.0.0",
256
+ "@ai-sdk/groq": "^2.0.0",
257
+ "@ai-sdk/openai": "^2.0.0",
258
+ "@ai-sdk/xai": "^2.0.0",
127
259
  "@anthropic-ai/sdk": "^0.39.0",
128
260
  "@clack/prompts": "^0.10.1",
129
- "@modelcontextprotocol/sdk": "^1.11.0",
130
- ai: "^4.2.6",
261
+ "@modelcontextprotocol/sdk": "^1.17.2",
262
+ ai: "^5.0.0",
131
263
  "better-sqlite3": "^11.10.0",
132
264
  boxen: "^7.1.1",
133
265
  chalk: "^5.4.1",
@@ -150,7 +282,7 @@ var package_default = {
150
282
  winston: "^3.17.0",
151
283
  ws: "^8.18.1",
152
284
  yaml: "^2.7.1",
153
- zod: "^3.24.3",
285
+ zod: "^3.25.0",
154
286
  "zod-to-json-schema": "^3.24.6"
155
287
  },
156
288
  devDependencies: {
@@ -186,25 +318,100 @@ var package_default = {
186
318
  }
187
319
  };
188
320
 
189
- // src/app/config/cli-overrides.ts
190
- function applyCLIOverrides(baseConfig, cliOverrides) {
191
- if (!cliOverrides) {
192
- return AgentConfigSchema.parse(baseConfig);
321
+ // src/core/config/agent-resolver.ts
322
+ import { promises as fs2 } from "fs";
323
+ import path2 from "path";
324
+ async function resolveAgentPath(nameOrPath, autoInstall = true, injectPreferences = true) {
325
+ if (nameOrPath && isPath(nameOrPath)) {
326
+ const resolved = path2.resolve(nameOrPath);
327
+ try {
328
+ const stat = await fs2.stat(resolved);
329
+ if (!stat.isFile()) {
330
+ throw ConfigError.fileNotFound(resolved);
331
+ }
332
+ return resolved;
333
+ } catch {
334
+ throw ConfigError.fileNotFound(resolved);
335
+ }
193
336
  }
194
- const mergedConfig = JSON.parse(JSON.stringify(baseConfig));
195
- if (cliOverrides.provider) {
196
- mergedConfig.llm.provider = cliOverrides.provider;
337
+ if (nameOrPath) {
338
+ const { getAgentRegistry: getAgentRegistry2 } = await import("./registry-RALMVM3P.js");
339
+ const registry = getAgentRegistry2();
340
+ return await registry.resolveAgent(nameOrPath, autoInstall, injectPreferences);
197
341
  }
198
- if (cliOverrides.model) {
199
- mergedConfig.llm.model = cliOverrides.model;
342
+ return await resolveDefaultAgentByContext(autoInstall, injectPreferences);
343
+ }
344
+ async function resolveDefaultAgentByContext(autoInstall = true, injectPreferences = true) {
345
+ const executionContext = getExecutionContext();
346
+ switch (executionContext) {
347
+ case "dexto-source":
348
+ return await resolveDefaultAgentForDextoSource();
349
+ case "dexto-project":
350
+ return await resolveDefaultAgentForDextoProject(autoInstall, injectPreferences);
351
+ case "global-cli":
352
+ return await resolveDefaultAgentForGlobalCLI(autoInstall, injectPreferences);
353
+ default:
354
+ throw ConfigError.unknownContext(executionContext);
200
355
  }
201
- if (cliOverrides.router) {
202
- mergedConfig.llm.router = cliOverrides.router;
356
+ }
357
+ async function resolveDefaultAgentForDextoSource() {
358
+ logger.debug("Resolving default agent for dexto source context");
359
+ const sourceRoot = findDextoSourceRoot();
360
+ if (!sourceRoot) {
361
+ throw ConfigError.bundledNotFound("dexto source directory not found");
203
362
  }
204
- if (cliOverrides.apiKey) {
205
- mergedConfig.llm.apiKey = cliOverrides.apiKey;
363
+ const bundledPath = path2.join(sourceRoot, "agents", "default-agent.yml");
364
+ try {
365
+ await fs2.access(bundledPath);
366
+ return bundledPath;
367
+ } catch {
368
+ throw ConfigError.bundledNotFound(bundledPath);
369
+ }
370
+ }
371
+ async function resolveDefaultAgentForDextoProject(autoInstall = true, injectPreferences = true) {
372
+ logger.debug("Resolving default agent for dexto project context");
373
+ const projectRoot = findDextoProjectRoot();
374
+ if (!projectRoot) {
375
+ throw ConfigError.unknownContext("dexto-project: project root not found");
376
+ }
377
+ const candidatePaths = [
378
+ path2.join(projectRoot, "default-agent.yml"),
379
+ path2.join(projectRoot, "agents", "default-agent.yml"),
380
+ path2.join(projectRoot, "src", "dexto", "agents", "default-agent.yml")
381
+ ];
382
+ for (const p7 of candidatePaths) {
383
+ try {
384
+ await fs2.access(p7);
385
+ return p7;
386
+ } catch {
387
+ }
388
+ }
389
+ logger.debug(`No project-local default-agent.yml found in ${projectRoot}`);
390
+ if (!globalPreferencesExist()) {
391
+ throw ConfigError.noProjectDefault(projectRoot);
392
+ }
393
+ const preferences = await loadGlobalPreferences();
394
+ if (!preferences.setup.completed) {
395
+ throw ConfigError.setupIncomplete();
206
396
  }
207
- return AgentConfigSchema.parse(mergedConfig);
397
+ const preferredAgentName = preferences.defaults.defaultAgent;
398
+ const { getAgentRegistry: getAgentRegistry2 } = await import("./registry-RALMVM3P.js");
399
+ const registry = getAgentRegistry2();
400
+ return await registry.resolveAgent(preferredAgentName, autoInstall, injectPreferences);
401
+ }
402
+ async function resolveDefaultAgentForGlobalCLI(autoInstall = true, injectPreferences = true) {
403
+ logger.debug("Resolving default agent for global CLI context");
404
+ if (!globalPreferencesExist()) {
405
+ throw ConfigError.noGlobalPreferences();
406
+ }
407
+ const preferences = await loadGlobalPreferences();
408
+ if (!preferences.setup.completed) {
409
+ throw ConfigError.setupIncomplete();
410
+ }
411
+ const preferredAgentName = preferences.defaults.defaultAgent;
412
+ const { getAgentRegistry: getAgentRegistry2 } = await import("./registry-RALMVM3P.js");
413
+ const registry = getAgentRegistry2();
414
+ return await registry.resolveAgent(preferredAgentName, autoInstall, injectPreferences);
208
415
  }
209
416
 
210
417
  // src/app/cli/cli.ts
@@ -219,7 +426,11 @@ var CLISubscriber = class {
219
426
  currentLines = 0;
220
427
  subscribe(eventBus) {
221
428
  eventBus.on("llmservice:thinking", this.onThinking.bind(this));
222
- eventBus.on("llmservice:chunk", (payload) => this.onChunk(payload.content));
429
+ eventBus.on("llmservice:chunk", (payload) => {
430
+ if (payload.type === "text") {
431
+ this.onChunk(payload.content);
432
+ }
433
+ });
223
434
  eventBus.on(
224
435
  "llmservice:toolCall",
225
436
  (payload) => this.onToolCall(payload.toolName, payload.args)
@@ -248,8 +459,8 @@ var CLISubscriber = class {
248
459
  onThinking() {
249
460
  logger.info("AI thinking...", null, "yellow");
250
461
  }
251
- onChunk(text2) {
252
- this.accumulatedResponse += text2;
462
+ onChunk(text3) {
463
+ this.accumulatedResponse += text3;
253
464
  const box = boxen(chalk.white(this.accumulatedResponse), {
254
465
  padding: 1,
255
466
  borderColor: "yellow",
@@ -270,15 +481,28 @@ var CLISubscriber = class {
270
481
  onToolResult(toolName, result) {
271
482
  logger.toolResult(result);
272
483
  }
273
- onResponse(text2) {
484
+ onResponse(text3) {
274
485
  this.accumulatedResponse = "";
275
486
  this.currentLines = 0;
276
- logger.displayAIResponse({ content: text2 });
487
+ logger.displayAIResponse({ content: text3 });
277
488
  }
278
489
  onError(error) {
279
490
  this.accumulatedResponse = "";
280
491
  this.currentLines = 0;
281
- logger.error(`\u274C Error: ${error.message}`, null, "red");
492
+ logger.displayError(error.message, error);
493
+ if (logger.getLevel() === "debug") {
494
+ logger.error(
495
+ `\u274C Error: ${error.message}`,
496
+ {
497
+ stack: error.stack,
498
+ name: error.name,
499
+ cause: error.cause
500
+ },
501
+ "red"
502
+ );
503
+ } else {
504
+ logger.error(`\u274C Error: ${error.message}`, null, "red");
505
+ }
282
506
  }
283
507
  onConversationReset() {
284
508
  this.accumulatedResponse = "";
@@ -287,7 +511,7 @@ var CLISubscriber = class {
287
511
  }
288
512
  };
289
513
 
290
- // src/app/cli/interactive-commands/command-parser.ts
514
+ // src/app/cli/commands/interactive-commands/command-parser.ts
291
515
  import chalk2 from "chalk";
292
516
  function parseQuotedArguments(input) {
293
517
  const args = [];
@@ -401,7 +625,7 @@ function displayAllCommands(commands) {
401
625
  console.log(chalk2.dim("\u{1F4A1} Tip: Type your message normally (without /) to chat with the AI\n"));
402
626
  }
403
627
 
404
- // src/app/cli/interactive-commands/general-commands.ts
628
+ // src/app/cli/commands/interactive-commands/general-commands.ts
405
629
  import chalk3 from "chalk";
406
630
  function createHelpCommand(getAllCommands) {
407
631
  return {
@@ -490,10 +714,10 @@ var generalCommands = [
490
714
  }
491
715
  ];
492
716
 
493
- // src/app/cli/interactive-commands/session/session-commands.ts
717
+ // src/app/cli/commands/interactive-commands/session/session-commands.ts
494
718
  import chalk5 from "chalk";
495
719
 
496
- // src/app/cli/interactive-commands/session/helpers/formatters.ts
720
+ // src/app/cli/commands/interactive-commands/session/helpers/formatters.ts
497
721
  import chalk4 from "chalk";
498
722
  function formatSessionInfo(sessionId, metadata, isCurrent = false) {
499
723
  const prefix = isCurrent ? chalk4.green("\u2192") : " ";
@@ -557,7 +781,7 @@ function formatHistoryMessage(message, index) {
557
781
  return ` ${chalk4.dim(timestamp)} ${roleColor.bold(displayLabel)}: ${content}${toolInfo}`;
558
782
  }
559
783
 
560
- // src/app/cli/interactive-commands/session/session-commands.ts
784
+ // src/app/cli/commands/interactive-commands/session/session-commands.ts
561
785
  async function getCurrentSessionInfo(agent) {
562
786
  const currentId = agent.getCurrentSessionId();
563
787
  const metadata = await agent.getSessionMetadata(currentId);
@@ -603,13 +827,30 @@ var sessionCommand = {
603
827
  );
604
828
  return true;
605
829
  }
606
- for (const sessionId of sessionIds) {
607
- const metadata = await agent.getSessionMetadata(sessionId);
608
- const isCurrent = sessionId === current.id;
609
- console.log(" " + formatSessionInfo(sessionId, metadata, isCurrent));
830
+ const entries = await Promise.all(
831
+ sessionIds.map(async (id) => {
832
+ try {
833
+ const metadata = await agent.getSessionMetadata(id);
834
+ return { id, metadata };
835
+ } catch (e) {
836
+ logger.error(
837
+ `Failed to fetch metadata for session ${id}: ${e instanceof Error ? e.message : String(e)}`
838
+ );
839
+ return { id, metadata: void 0 };
840
+ }
841
+ })
842
+ );
843
+ let displayed = 0;
844
+ for (const { id, metadata } of entries) {
845
+ if (!metadata) continue;
846
+ const isCurrent = id === current.id;
847
+ console.log(` ${formatSessionInfo(id, metadata, isCurrent)}`);
848
+ displayed++;
610
849
  }
611
- console.log(chalk5.dim(`
612
- Total: ${sessionIds.length} sessions`));
850
+ console.log(
851
+ chalk5.dim(`
852
+ Total: ${displayed} of ${sessionIds.length} sessions`)
853
+ );
613
854
  console.log(chalk5.dim(" \u{1F4A1} Use /session switch <id> to change sessions\n"));
614
855
  } catch (error) {
615
856
  logger.error(
@@ -919,10 +1160,10 @@ var searchCommand = {
919
1160
  }
920
1161
  };
921
1162
 
922
- // src/app/cli/interactive-commands/session/index.ts
1163
+ // src/app/cli/commands/interactive-commands/session/index.ts
923
1164
  var sessionCommands = [sessionCommand, historyCommand, searchCommand];
924
1165
 
925
- // src/app/cli/interactive-commands/model/model-commands.ts
1166
+ // src/app/cli/commands/interactive-commands/model/model-commands.ts
926
1167
  import chalk6 from "chalk";
927
1168
  var modelCommands = {
928
1169
  name: "model",
@@ -1016,20 +1257,21 @@ var modelCommands = {
1016
1257
  await agent.switchLLM(llmConfig);
1017
1258
  console.log(chalk6.green(`\u2705 Successfully switched to ${model} (${provider})`));
1018
1259
  } catch (error) {
1019
- if (error instanceof DextoLLMError) {
1260
+ if (error instanceof DextoRuntimeError) {
1020
1261
  console.log(chalk6.red("\u274C Failed to switch model:"));
1021
- const errors = error.issues.filter((issue) => issue.severity === "error");
1022
- for (const err of errors) {
1023
- console.log(chalk6.red(` ${err.message}`));
1024
- }
1025
- const warnings = error.issues.filter(
1026
- (issue) => issue.severity === "warning"
1027
- );
1028
- if (warnings.length > 0) {
1029
- for (const warning of warnings) {
1030
- console.log(chalk6.yellow(`\u26A0\uFE0F ${warning.message}`));
1262
+ console.log(chalk6.red(` ${error.message}`));
1263
+ console.log(chalk6.dim(` Code: ${error.code}`));
1264
+ if (error.recovery) {
1265
+ const recoverySteps = Array.isArray(error.recovery) ? error.recovery : [error.recovery];
1266
+ for (const step of recoverySteps) {
1267
+ console.log(chalk6.blue(`\u{1F4A1} ${step}`));
1031
1268
  }
1032
1269
  }
1270
+ } else if (error instanceof DextoValidationError) {
1271
+ console.log(chalk6.red("\u274C Validation failed:"));
1272
+ error.errors.forEach((err) => {
1273
+ console.log(chalk6.red(` - ${err.message}`));
1274
+ });
1033
1275
  } else {
1034
1276
  logger.error(
1035
1277
  `Failed to switch model: ${error instanceof Error ? error.message : String(error)}`
@@ -1091,10 +1333,10 @@ var modelCommands = {
1091
1333
  }
1092
1334
  };
1093
1335
 
1094
- // src/app/cli/interactive-commands/mcp/mcp-commands.ts
1336
+ // src/app/cli/commands/interactive-commands/mcp/mcp-commands.ts
1095
1337
  import chalk8 from "chalk";
1096
1338
 
1097
- // src/app/cli/interactive-commands/utils/arg-parser.ts
1339
+ // src/app/cli/commands/interactive-commands/utils/arg-parser.ts
1098
1340
  function parseOptions(args) {
1099
1341
  const parsedArgs = [];
1100
1342
  const options = {};
@@ -1116,7 +1358,7 @@ function parseOptions(args) {
1116
1358
  return { parsedArgs, options, flags };
1117
1359
  }
1118
1360
 
1119
- // src/app/cli/interactive-commands/mcp/mcp-add-utils.ts
1361
+ // src/app/cli/commands/interactive-commands/mcp/mcp-add-utils.ts
1120
1362
  import chalk7 from "chalk";
1121
1363
  function parseStdioArgs(args) {
1122
1364
  const errors = [];
@@ -1276,7 +1518,7 @@ function validateAndShowErrors(serverName, config, existingServers = []) {
1276
1518
  return true;
1277
1519
  }
1278
1520
 
1279
- // src/app/cli/interactive-commands/mcp/mcp-commands.ts
1521
+ // src/app/cli/commands/interactive-commands/mcp/mcp-commands.ts
1280
1522
  async function handleMcpAddStdio(args, agent) {
1281
1523
  const { serverName, config, errors } = parseStdioArgs(args);
1282
1524
  if (errors.length > 0) {
@@ -1555,7 +1797,7 @@ var mcpCommands = {
1555
1797
  }
1556
1798
  };
1557
1799
 
1558
- // src/app/cli/interactive-commands/system/system-commands.ts
1800
+ // src/app/cli/commands/interactive-commands/system/system-commands.ts
1559
1801
  import chalk9 from "chalk";
1560
1802
  var systemCommands = [
1561
1803
  {
@@ -1670,7 +1912,7 @@ Current log level: ${chalk9.cyan(logger.getLevel())}`));
1670
1912
  }
1671
1913
  ];
1672
1914
 
1673
- // src/app/cli/interactive-commands/tool-commands.ts
1915
+ // src/app/cli/commands/interactive-commands/tool-commands.ts
1674
1916
  import chalk10 from "chalk";
1675
1917
  var toolCommands = [
1676
1918
  {
@@ -1718,7 +1960,7 @@ var toolCommands = [
1718
1960
  }
1719
1961
  ];
1720
1962
 
1721
- // src/app/cli/interactive-commands/prompt-commands.ts
1963
+ // src/app/cli/commands/interactive-commands/prompt-commands.ts
1722
1964
  import chalk11 from "chalk";
1723
1965
  var promptCommands = [
1724
1966
  {
@@ -1744,7 +1986,7 @@ var promptCommands = [
1744
1986
  }
1745
1987
  ];
1746
1988
 
1747
- // src/app/cli/interactive-commands/documentation-commands.ts
1989
+ // src/app/cli/commands/interactive-commands/documentation-commands.ts
1748
1990
  import chalk12 from "chalk";
1749
1991
  var documentationCommands = [
1750
1992
  {
@@ -1754,29 +1996,25 @@ var documentationCommands = [
1754
1996
  category: "Documentation",
1755
1997
  aliases: ["doc"],
1756
1998
  handler: async (_args, _agent) => {
1999
+ const docsUrl = "https://docs.dexto.ai/category/getting-started/";
1757
2000
  try {
1758
- const { spawn: spawn2 } = await import("child_process");
1759
- const url = "https://truffle-ai.github.io/dexto/docs/category/getting-started/";
1760
- console.log(chalk12.blue(`\u{1F310} Opening Dexto documentation: ${url}`));
2001
+ const { spawn: spawn3 } = await import("child_process");
2002
+ console.log(chalk12.blue(`\u{1F310} Opening Dexto documentation: ${docsUrl}`));
1761
2003
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1762
- spawn2(command, [url], { detached: true, stdio: "ignore" });
2004
+ spawn3(command, [docsUrl], { detached: true, stdio: "ignore" });
1763
2005
  console.log(chalk12.green("\u2705 Documentation opened in browser"));
1764
2006
  } catch (error) {
1765
2007
  logger.error(
1766
2008
  `Failed to open documentation: ${error instanceof Error ? error.message : String(error)}`
1767
2009
  );
1768
- console.log(
1769
- chalk12.yellow(
1770
- "\u{1F4A1} You can manually visit: https://truffle-ai.github.io/dexto/docs/category/getting-started/"
1771
- )
1772
- );
2010
+ console.log(chalk12.yellow(`\u{1F4A1} You can manually visit: ${docsUrl}`));
1773
2011
  }
1774
2012
  return true;
1775
2013
  }
1776
2014
  }
1777
2015
  ];
1778
2016
 
1779
- // src/app/cli/interactive-commands/commands.ts
2017
+ // src/app/cli/commands/interactive-commands/commands.ts
1780
2018
  var CLI_COMMANDS = [];
1781
2019
  var baseCommands = [
1782
2020
  // General commands (without help)
@@ -1893,25 +2131,48 @@ async function loadMostRecentSession(agent) {
1893
2131
  async function _initCli(agent) {
1894
2132
  await loadMostRecentSession(agent);
1895
2133
  registerGracefulShutdown(agent);
1896
- logger.debug(`Log level: ${logger.getLevel()}`);
1897
- logger.info(`Connected servers: ${agent.mcpManager.getClients().size}`, null, "green");
2134
+ const llmConfig = agent.getCurrentLLMConfig();
2135
+ const connectedServers = agent.mcpManager.getClients();
1898
2136
  const failedConnections = agent.mcpManager.getFailedConnections();
2137
+ const currentSessionId = agent.getCurrentSessionId();
2138
+ let toolStats;
2139
+ try {
2140
+ toolStats = await agent.toolManager.getToolStats();
2141
+ } catch (error) {
2142
+ logger.error(
2143
+ `Failed to load tools: ${error instanceof Error ? error.message : String(error)}`
2144
+ );
2145
+ }
2146
+ const startupInfo = {
2147
+ model: llmConfig.model,
2148
+ provider: llmConfig.provider,
2149
+ connectedServers: {
2150
+ count: connectedServers.size,
2151
+ names: Array.from(connectedServers.keys())
2152
+ },
2153
+ sessionId: currentSessionId,
2154
+ logLevel: logger.getLevel()
2155
+ };
1899
2156
  if (Object.keys(failedConnections).length > 0) {
1900
- logger.error(`Failed connections: ${Object.keys(failedConnections).length}.`, null, "red");
2157
+ startupInfo.failedConnections = failedConnections;
2158
+ }
2159
+ if (toolStats) {
2160
+ startupInfo.toolStats = toolStats;
1901
2161
  }
2162
+ const logFile = logger.getLogFilePath();
2163
+ if (logFile) {
2164
+ startupInfo.logFile = logFile;
2165
+ }
2166
+ logger.displayStartupInfo(startupInfo);
2167
+ logger.debug(`Startup configuration: ${JSON.stringify(startupInfo, null, 2)}`);
1902
2168
  logger.info("Setting up CLI event subscriptions...");
1903
2169
  const cliSubscriber = new CLISubscriber();
1904
2170
  cliSubscriber.subscribe(agent.agentEventBus);
1905
2171
  logger.info("Loading available tools...");
1906
- try {
1907
- const toolStats = await agent.toolManager.getToolStats();
2172
+ if (toolStats) {
1908
2173
  logger.info(
1909
2174
  `Loaded ${toolStats.total} total tools: ${toolStats.mcp} MCP, ${toolStats.internal} internal`
1910
2175
  );
1911
- } catch (error) {
1912
- logger.error(
1913
- `Failed to load tools: ${error instanceof Error ? error.message : String(error)}`
1914
- );
1915
2176
  }
1916
2177
  logger.info(`CLI initialized successfully. Ready for input.`, null, "green");
1917
2178
  console.log(chalk13.bold.cyan("\n\u{1F680} Welcome to Dexto CLI!"));
@@ -1999,11 +2260,14 @@ async function startHeadlessCli(agent, prompt) {
1999
2260
  await agent.run(prompt);
2000
2261
  }
2001
2262
  } catch (error) {
2002
- if (error instanceof UnknownProviderError) {
2003
- logger.error(`Provider error: ${error.message}`, null, "red");
2004
- } else if (error instanceof UnknownModelError) {
2005
- logger.error(`Model error: ${error.message}`, null, "red");
2006
- } else if (error instanceof ConfigurationError) {
2263
+ if (error instanceof DextoRuntimeError && error.code === "llm_model_unknown" /* MODEL_UNKNOWN */) {
2264
+ logger.error(`LLM error: ${error.message}`, null, "red");
2265
+ } else if (error instanceof DextoValidationError) {
2266
+ logger.error(`Validation failed:`, null, "red");
2267
+ error.errors.forEach((err) => {
2268
+ logger.error(` - ${err.message}`, null, "red");
2269
+ });
2270
+ } else if (error instanceof DextoRuntimeError && error.scope === "config" /* CONFIG */) {
2007
2271
  logger.error(`Configuration error: ${error.message}`, null, "red");
2008
2272
  } else {
2009
2273
  logger.error(
@@ -2063,7 +2327,8 @@ var WebSocketEventSubscriber = class {
2063
2327
  this.broadcast({
2064
2328
  event: "chunk",
2065
2329
  data: {
2066
- text: payload.content,
2330
+ type: payload.type,
2331
+ content: payload.content,
2067
2332
  isComplete: payload.isComplete,
2068
2333
  sessionId: payload.sessionId
2069
2334
  }
@@ -2105,12 +2370,17 @@ var WebSocketEventSubscriber = class {
2105
2370
  eventBus.on(
2106
2371
  "llmservice:response",
2107
2372
  (payload) => {
2373
+ logger.debug(
2374
+ `[websocket-subscriber]: llmservice:response: ${JSON.stringify(payload)}`
2375
+ );
2108
2376
  this.broadcast({
2109
2377
  event: "response",
2110
2378
  data: {
2111
2379
  text: payload.content,
2112
- tokenCount: payload.tokenCount,
2380
+ reasoning: payload.reasoning,
2381
+ tokenUsage: payload.tokenUsage,
2113
2382
  model: payload.model,
2383
+ router: payload.router,
2114
2384
  sessionId: payload.sessionId
2115
2385
  }
2116
2386
  });
@@ -2356,10 +2626,10 @@ var WebhookEventSubscriber = class {
2356
2626
  });
2357
2627
  };
2358
2628
  if (process.env.NODE_ENV === "test") {
2359
- const results = await Promise.allSettled(deliveryPromises.map((p3) => p3.promise));
2629
+ const results = await Promise.allSettled(deliveryPromises.map((p7) => p7.promise));
2360
2630
  handleSettled(results);
2361
2631
  } else {
2362
- Promise.allSettled(deliveryPromises.map((p3) => p3.promise)).then(handleSettled);
2632
+ Promise.allSettled(deliveryPromises.map((p7) => p7.promise)).then(handleSettled);
2363
2633
  }
2364
2634
  }
2365
2635
  /**
@@ -2517,11 +2787,11 @@ async function initializeMcpServer(agent, agentCardData, mcpTransport) {
2517
2787
  logger.info(
2518
2788
  `MCP tool '${toolName}' received message: ${message.substring(0, 100)}${message.length > 100 ? "..." : ""}`
2519
2789
  );
2520
- const text2 = await agent.run(message);
2790
+ const text3 = await agent.run(message);
2521
2791
  logger.info(
2522
- `MCP tool '${toolName}' sending response: ${text2?.substring(0, 100)}${(text2?.length ?? 0) > 100 ? "..." : ""}`
2792
+ `MCP tool '${toolName}' sending response: ${text3?.substring(0, 100)}${(text3?.length ?? 0) > 100 ? "..." : ""}`
2523
2793
  );
2524
- return { content: [{ type: "text", text: text2 ?? "" }] };
2794
+ return { content: [{ type: "text", text: text3 ?? "" }] };
2525
2795
  }
2526
2796
  );
2527
2797
  logger.info(
@@ -2579,57 +2849,6 @@ async function initializeMcpServerApiEndpoints(app, mcpTransport) {
2579
2849
  import { stringify as yamlStringify } from "yaml";
2580
2850
  import os from "os";
2581
2851
 
2582
- // src/app/api/middleware/redactor.ts
2583
- var SENSITIVE_FIELDS = [
2584
- "apikey",
2585
- "api_key",
2586
- "token",
2587
- "access_token",
2588
- "refresh_token",
2589
- "password",
2590
- "secret"
2591
- ];
2592
- var SENSITIVE_PATTERNS = [
2593
- /\bsk-[A-Za-z0-9]{20,}\b/g,
2594
- // OpenAI API keys (at least 20 chars after sk-)
2595
- /\bBearer\s+[A-Za-z0-9\-_.=]+\b/gi,
2596
- // Bearer tokens
2597
- /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
2598
- // Emails
2599
- /\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/g
2600
- // JWT tokens
2601
- ];
2602
- var REDACTED = "[REDACTED]";
2603
- var REDACTED_CIRCULAR = "[REDACTED_CIRCULAR]";
2604
- function redactSensitiveData(input, seen = /* @__PURE__ */ new WeakSet()) {
2605
- if (typeof input === "string") {
2606
- let result = input;
2607
- for (const pattern of SENSITIVE_PATTERNS) {
2608
- result = result.replace(pattern, REDACTED);
2609
- }
2610
- return result;
2611
- }
2612
- if (Array.isArray(input)) {
2613
- if (seen.has(input)) return REDACTED_CIRCULAR;
2614
- seen.add(input);
2615
- return input.map((item) => redactSensitiveData(item, seen));
2616
- }
2617
- if (input && typeof input === "object") {
2618
- if (seen.has(input)) return REDACTED_CIRCULAR;
2619
- seen.add(input);
2620
- const result = {};
2621
- for (const [key, value] of Object.entries(input)) {
2622
- if (SENSITIVE_FIELDS.includes(key.toLowerCase())) {
2623
- result[key] = REDACTED;
2624
- } else {
2625
- result[key] = redactSensitiveData(value, seen);
2626
- }
2627
- }
2628
- return result;
2629
- }
2630
- return input;
2631
- }
2632
-
2633
2852
  // src/app/api/middleware/expressRedactionMiddleware.ts
2634
2853
  function expressRedactionMiddleware(req, res, next) {
2635
2854
  const originalJson = res.json.bind(res);
@@ -2649,6 +2868,136 @@ function expressRedactionMiddleware(req, res, next) {
2649
2868
 
2650
2869
  // src/app/api/server.ts
2651
2870
  import { z as z2 } from "zod";
2871
+
2872
+ // src/app/api/middleware/errorHandler.ts
2873
+ import { ZodError } from "zod";
2874
+ var mapErrorTypeToStatus = (type) => {
2875
+ switch (type) {
2876
+ case "user" /* USER */:
2877
+ return 400;
2878
+ case "not_found" /* NOT_FOUND */:
2879
+ return 404;
2880
+ case "forbidden" /* FORBIDDEN */:
2881
+ return 403;
2882
+ case "timeout" /* TIMEOUT */:
2883
+ return 408;
2884
+ case "rate_limit" /* RATE_LIMIT */:
2885
+ return 429;
2886
+ case "system" /* SYSTEM */:
2887
+ return 500;
2888
+ case "third_party" /* THIRD_PARTY */:
2889
+ return 502;
2890
+ case "unknown" /* UNKNOWN */:
2891
+ default:
2892
+ return 500;
2893
+ }
2894
+ };
2895
+ var statusForValidation = (issues) => {
2896
+ const firstError = issues.find((i) => i.severity === "error");
2897
+ const type = firstError?.type ?? "user" /* USER */;
2898
+ return mapErrorTypeToStatus(type);
2899
+ };
2900
+ function errorHandler(err, _req, res, _next) {
2901
+ if (err instanceof DextoRuntimeError) {
2902
+ const status = mapErrorTypeToStatus(err.type);
2903
+ res.status(status).json(err.toJSON());
2904
+ return;
2905
+ }
2906
+ if (err instanceof DextoValidationError) {
2907
+ const status = statusForValidation(err.issues);
2908
+ res.status(status).json(err.toJSON());
2909
+ return;
2910
+ }
2911
+ if (err instanceof ZodError) {
2912
+ const issues = zodToIssues(err);
2913
+ const dexErr = new DextoValidationError(issues);
2914
+ const status = statusForValidation(issues);
2915
+ res.status(status).json(dexErr.toJSON());
2916
+ return;
2917
+ }
2918
+ const errorMessage = err instanceof Error ? err.message : String(err);
2919
+ const errorStack = err instanceof Error ? err.stack : void 0;
2920
+ logger.error(`Unhandled error in API middleware: ${errorMessage}`, {
2921
+ error: err,
2922
+ stack: errorStack,
2923
+ type: typeof err
2924
+ });
2925
+ res.status(500).json({
2926
+ code: "internal_error",
2927
+ message: "An unexpected error occurred",
2928
+ scope: "system",
2929
+ type: "system",
2930
+ severity: "error"
2931
+ });
2932
+ }
2933
+
2934
+ // src/app/api/websocket-error-handler.ts
2935
+ import { ZodError as ZodError2 } from "zod";
2936
+ function sendWebSocketError(ws, error) {
2937
+ let errorData;
2938
+ if (error instanceof DextoRuntimeError) {
2939
+ errorData = error.toJSON();
2940
+ } else if (error instanceof DextoValidationError) {
2941
+ errorData = error.toJSON();
2942
+ } else if (error instanceof ZodError2) {
2943
+ const issues = zodToIssues(error);
2944
+ const dexErr = new DextoValidationError(issues);
2945
+ errorData = dexErr.toJSON();
2946
+ } else {
2947
+ const errorObj = toError(error);
2948
+ const errorStack = error instanceof Error ? error.stack : void 0;
2949
+ logger.error(`Unhandled WebSocket error: ${errorObj.message}`, {
2950
+ error,
2951
+ stack: errorStack,
2952
+ type: typeof error
2953
+ });
2954
+ errorData = {
2955
+ code: "internal_error",
2956
+ message: errorObj.message,
2957
+ scope: "agent" /* AGENT */,
2958
+ type: "system" /* SYSTEM */,
2959
+ severity: "error"
2960
+ };
2961
+ }
2962
+ try {
2963
+ ws.send(
2964
+ JSON.stringify({
2965
+ event: "error",
2966
+ data: errorData
2967
+ })
2968
+ );
2969
+ } catch (sendErr) {
2970
+ const msg = sendErr instanceof Error ? sendErr.message : String(sendErr);
2971
+ logger.error(`Failed to send WebSocket error frame: ${msg}`, { errorData });
2972
+ }
2973
+ }
2974
+ function sendWebSocketValidationError(ws, message, context) {
2975
+ const dexErr = new DextoValidationError([
2976
+ {
2977
+ code: "agent_api_validation_error" /* API_VALIDATION_ERROR */,
2978
+ message,
2979
+ scope: "agent" /* AGENT */,
2980
+ type: "user" /* USER */,
2981
+ severity: "error",
2982
+ context
2983
+ }
2984
+ ]);
2985
+ const data = dexErr.toJSON();
2986
+ logger.error(`Sending WebSocket validation error: ${message}`, { data });
2987
+ try {
2988
+ ws.send(
2989
+ JSON.stringify({
2990
+ event: "error",
2991
+ data
2992
+ })
2993
+ );
2994
+ } catch (sendErr) {
2995
+ const msg = sendErr instanceof Error ? sendErr.message : String(sendErr);
2996
+ logger.error(`Failed to send WebSocket validation error: ${msg}`, { data });
2997
+ }
2998
+ }
2999
+
3000
+ // src/app/api/server.ts
2652
3001
  function sendJsonResponse(res, data, statusCode = 200) {
2653
3002
  const pretty = res.req.query.pretty === "true" || res.req.query.pretty === "1";
2654
3003
  res.status(statusCode);
@@ -2659,27 +3008,47 @@ function sendJsonResponse(res, data, statusCode = 200) {
2659
3008
  res.json(data);
2660
3009
  }
2661
3010
  }
2662
- var LLMSwitchRequestSchema = z2.object({
2663
- sessionId: z2.string().optional()
2664
- }).merge(LLMUpdatesSchema);
2665
- function validateBody(schema, body) {
2666
- const result = schema.safeParse(body);
2667
- if (!result.success) {
2668
- return {
2669
- success: false,
2670
- response: {
2671
- ok: false,
2672
- issues: result.error.errors.map((err) => ({
2673
- code: "schema_validation",
2674
- message: err.message,
2675
- path: err.path,
2676
- severity: "error",
2677
- context: { field: err.path.join(".") }
2678
- }))
2679
- }
2680
- };
2681
- }
2682
- return { success: true, data: result.data };
3011
+ var MessageRequestSchema = z2.object({
3012
+ message: z2.string().optional(),
3013
+ sessionId: z2.string().optional(),
3014
+ stream: z2.boolean().optional(),
3015
+ imageData: z2.object({
3016
+ base64: z2.string(),
3017
+ mimeType: z2.string()
3018
+ }).optional(),
3019
+ fileData: z2.object({
3020
+ base64: z2.string(),
3021
+ mimeType: z2.string(),
3022
+ filename: z2.string().optional()
3023
+ }).optional()
3024
+ }).refine(
3025
+ (data) => {
3026
+ const msg = (data.message ?? "").trim();
3027
+ return msg.length > 0 || !!data.imageData || !!data.fileData;
3028
+ },
3029
+ { message: "Must provide either message text, image data, or file data" }
3030
+ );
3031
+ var McpServerRequestSchema = z2.object({
3032
+ name: z2.string().min(1, "Server name is required"),
3033
+ config: McpServerConfigSchema
3034
+ });
3035
+ var WebhookRequestSchema = z2.object({
3036
+ url: z2.string().url("Invalid URL format"),
3037
+ secret: z2.string().optional(),
3038
+ description: z2.string().optional()
3039
+ });
3040
+ var SearchQuerySchema = z2.object({
3041
+ q: z2.string().min(1, "Search query is required"),
3042
+ limit: z2.coerce.number().min(1).max(100).optional(),
3043
+ offset: z2.coerce.number().min(0).optional(),
3044
+ sessionId: z2.string().optional(),
3045
+ role: z2.enum(["user", "assistant", "system", "tool"]).optional()
3046
+ });
3047
+ function parseBody(schema, body) {
3048
+ return schema.parse(body);
3049
+ }
3050
+ function parseQuery(schema, query) {
3051
+ return schema.parse(query);
2683
3052
  }
2684
3053
  async function initializeApi(agent, agentCardOverride) {
2685
3054
  const app = express2();
@@ -2694,145 +3063,96 @@ async function initializeApi(agent, agentCardOverride) {
2694
3063
  app.get("/health", (req, res) => {
2695
3064
  res.status(200).send("OK");
2696
3065
  });
2697
- app.post("/api/message", express2.json(), async (req, res) => {
3066
+ app.post("/api/message", express2.json(), async (req, res, next) => {
2698
3067
  logger.info("Received message via POST /api/message");
2699
- if (!req.body || !req.body.message) {
2700
- return res.status(400).send({ error: "Missing message content" });
2701
- }
2702
3068
  try {
2703
- const sessionId = req.body.sessionId;
2704
- const stream = req.body.stream === true;
2705
- const imageDataInput = req.body.imageData ? { image: req.body.imageData.base64, mimeType: req.body.imageData.mimeType } : void 0;
2706
- const fileDataInput = req.body.fileData ? {
2707
- data: req.body.fileData.base64,
2708
- mimeType: req.body.fileData.mimeType,
2709
- filename: req.body.fileData.filename
3069
+ const { message, sessionId, stream, imageData, fileData } = parseBody(
3070
+ MessageRequestSchema,
3071
+ req.body
3072
+ );
3073
+ const imageDataInput = imageData ? { image: imageData.base64, mimeType: imageData.mimeType } : void 0;
3074
+ const fileDataInput = fileData ? {
3075
+ data: fileData.base64,
3076
+ mimeType: fileData.mimeType,
3077
+ ...fileData.filename && { filename: fileData.filename }
2710
3078
  } : void 0;
2711
3079
  if (imageDataInput) logger.info("Image data included in message.");
2712
3080
  if (fileDataInput) logger.info("File data included in message.");
2713
3081
  if (sessionId) logger.info(`Message for session: ${sessionId}`);
2714
- const currentConfig = agent.getEffectiveConfig(sessionId);
2715
- const validation = validateInputForLLM(
2716
- {
2717
- text: req.body.message,
2718
- ...imageDataInput && { imageData: imageDataInput },
2719
- ...fileDataInput && { fileData: fileDataInput }
2720
- },
2721
- {
2722
- provider: currentConfig.llm.provider,
2723
- model: currentConfig.llm.model
2724
- }
3082
+ const response = await agent.run(
3083
+ message || "",
3084
+ imageDataInput,
3085
+ fileDataInput,
3086
+ sessionId,
3087
+ stream || false
2725
3088
  );
2726
- if (!validation.ok) {
2727
- const errorMessages = validation.issues.filter((issue) => issue.severity === "error").map((issue) => issue.message);
2728
- return res.status(400).send({
2729
- error: errorMessages.join("; "),
2730
- provider: currentConfig.llm.provider,
2731
- model: currentConfig.llm.model,
2732
- issues: validation.issues
2733
- });
2734
- }
2735
- await agent.run(req.body.message, imageDataInput, fileDataInput, sessionId, stream);
2736
- return res.status(202).send({ status: "processing", sessionId });
3089
+ return res.status(202).send({ response, sessionId });
2737
3090
  } catch (error) {
2738
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2739
- logger.error(`Error handling POST /api/message: ${errorMessage}`);
2740
- return res.status(500).send({ error: "Internal server error" });
3091
+ return next(error);
2741
3092
  }
2742
3093
  });
2743
- app.post("/api/message-sync", express2.json(), async (req, res) => {
3094
+ app.post("/api/message-sync", express2.json(), async (req, res, next) => {
2744
3095
  logger.info("Received message via POST /api/message-sync");
2745
- if (!req.body || !req.body.message) {
2746
- return res.status(400).send({ error: "Missing message content" });
2747
- }
2748
3096
  try {
2749
- const imageDataInput = req.body.imageData ? { image: req.body.imageData.base64, mimeType: req.body.imageData.mimeType } : void 0;
2750
- const fileDataInput = req.body.fileData ? {
2751
- data: req.body.fileData.base64,
2752
- mimeType: req.body.fileData.mimeType,
2753
- filename: req.body.fileData.filename
3097
+ const { message, sessionId, imageData, fileData } = parseBody(
3098
+ MessageRequestSchema,
3099
+ req.body
3100
+ );
3101
+ const imageDataInput = imageData ? { image: imageData.base64, mimeType: imageData.mimeType } : void 0;
3102
+ const fileDataInput = fileData ? {
3103
+ data: fileData.base64,
3104
+ mimeType: fileData.mimeType,
3105
+ ...fileData.filename && { filename: fileData.filename }
2754
3106
  } : void 0;
2755
- const sessionId = req.body.sessionId;
2756
- const stream = req.body.stream === true;
2757
3107
  if (imageDataInput) logger.info("Image data included in message.");
2758
3108
  if (fileDataInput) logger.info("File data included in message.");
2759
3109
  if (sessionId) logger.info(`Message for session: ${sessionId}`);
2760
- const currentConfig = agent.getEffectiveConfig(sessionId);
2761
- const validation = validateInputForLLM(
2762
- {
2763
- text: req.body.message,
2764
- ...imageDataInput && { imageData: imageDataInput },
2765
- ...fileDataInput && { fileData: fileDataInput }
2766
- },
2767
- {
2768
- provider: currentConfig.llm.provider,
2769
- model: currentConfig.llm.model
2770
- }
3110
+ const response = await agent.run(
3111
+ message || "",
3112
+ imageDataInput,
3113
+ fileDataInput,
3114
+ sessionId,
3115
+ false
3116
+ // Force non-streaming for sync endpoint
2771
3117
  );
2772
- if (!validation.ok) {
2773
- const errorMessages = validation.issues.filter((issue) => issue.severity === "error").map((issue) => issue.message);
2774
- return res.status(400).send({
2775
- error: errorMessages.join("; "),
2776
- provider: currentConfig.llm.provider,
2777
- model: currentConfig.llm.model,
2778
- issues: validation.issues
2779
- });
2780
- }
2781
- await agent.run(req.body.message, imageDataInput, fileDataInput, sessionId, stream);
2782
- return res.status(202).send({ status: "processing", sessionId });
3118
+ return res.status(200).json({ response, sessionId });
2783
3119
  } catch (error) {
2784
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2785
- logger.error(`Error handling POST /api/message-sync: ${errorMessage}`);
2786
- return res.status(500).send({ error: "Internal server error" });
3120
+ return next(error);
2787
3121
  }
2788
3122
  });
2789
- app.post("/api/reset", express2.json(), async (req, res) => {
3123
+ app.post("/api/reset", express2.json(), async (req, res, next) => {
2790
3124
  logger.info("Received request via POST /api/reset");
2791
3125
  try {
2792
- const sessionId = req.body.sessionId;
3126
+ const { sessionId } = parseBody(
3127
+ z2.object({ sessionId: z2.string().optional() }),
3128
+ req.body
3129
+ );
2793
3130
  await agent.resetConversation(sessionId);
2794
3131
  return res.status(200).send({ status: "reset initiated", sessionId });
2795
3132
  } catch (error) {
2796
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2797
- logger.error(`Error handling POST /api/reset: ${errorMessage}`);
2798
- return res.status(500).send({ error: "Internal server error" });
3133
+ return next(error);
2799
3134
  }
2800
3135
  });
2801
- app.post("/api/connect-server", express2.json(), async (req, res) => {
2802
- const { name, config } = req.body;
2803
- if (!name || typeof name !== "string" || name.trim() === "") {
2804
- return res.status(400).send({ error: "Missing or invalid server name" });
2805
- }
2806
- if (!config || typeof config !== "object") {
2807
- return res.status(400).send({ error: "Missing or invalid server config object" });
2808
- }
3136
+ app.post("/api/connect-server", express2.json(), async (req, res, next) => {
2809
3137
  try {
3138
+ const { name, config } = parseBody(McpServerRequestSchema, req.body);
2810
3139
  await agent.connectMcpServer(name, config);
2811
3140
  logger.info(`Successfully connected to new server '${name}' via API request.`);
2812
3141
  return res.status(200).send({ status: "connected", name });
2813
3142
  } catch (error) {
2814
- const errorMessage = error instanceof Error ? error.message : "Unknown error during connection";
2815
- logger.error(`Error handling POST /api/connect-server for '${name}': ${errorMessage}`);
2816
- return res.status(500).send({
2817
- error: `Failed to connect to server '${name}': ${errorMessage}`
2818
- });
3143
+ return next(error);
2819
3144
  }
2820
3145
  });
2821
- app.post("/api/mcp/servers", express2.json(), async (req, res) => {
2822
- const { name, config } = req.body;
2823
- if (!name || !config) {
2824
- return res.status(400).json({ error: "Missing name or config" });
2825
- }
3146
+ app.post("/api/mcp/servers", express2.json(), async (req, res, next) => {
2826
3147
  try {
3148
+ const { name, config } = parseBody(McpServerRequestSchema, req.body);
2827
3149
  await agent.connectMcpServer(name, config);
2828
3150
  return res.status(201).json({ status: "connected", name });
2829
3151
  } catch (error) {
2830
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2831
- logger.error(`Error connecting MCP server '${name}': ${errorMessage}`);
2832
- return res.status(500).json({ error: `Failed to connect server: ${errorMessage}` });
3152
+ return next(error);
2833
3153
  }
2834
3154
  });
2835
- app.get("/api/mcp/servers", async (req, res) => {
3155
+ app.get("/api/mcp/servers", async (req, res, next) => {
2836
3156
  try {
2837
3157
  const clientsMap = agent.getMcpClients();
2838
3158
  const failedConnections = agent.getMcpFailedConnections();
@@ -2845,12 +3165,10 @@ async function initializeApi(agent, agentCardOverride) {
2845
3165
  }
2846
3166
  return res.status(200).json({ servers });
2847
3167
  } catch (error) {
2848
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2849
- logger.error(`Error listing MCP servers: ${errorMessage}`);
2850
- return res.status(500).json({ error: "Failed to list servers" });
3168
+ return next(error);
2851
3169
  }
2852
3170
  });
2853
- app.get("/api/mcp/servers/:serverId/tools", async (req, res) => {
3171
+ app.get("/api/mcp/servers/:serverId/tools", async (req, res, next) => {
2854
3172
  const serverId = req.params.serverId;
2855
3173
  const client = agent.getMcpClients().get(serverId);
2856
3174
  if (!client) {
@@ -2866,12 +3184,10 @@ async function initializeApi(agent, agentCardOverride) {
2866
3184
  }));
2867
3185
  return res.status(200).json({ tools });
2868
3186
  } catch (error) {
2869
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2870
- logger.error(`Error fetching tools for server '${serverId}': ${errorMessage}`);
2871
- return res.status(500).json({ error: "Failed to fetch tools for server" });
3187
+ return next(error);
2872
3188
  }
2873
3189
  });
2874
- app.delete("/api/mcp/servers/:serverId", async (req, res) => {
3190
+ app.delete("/api/mcp/servers/:serverId", async (req, res, next) => {
2875
3191
  const { serverId } = req.params;
2876
3192
  logger.info(`Received request to DELETE /api/mcp/servers/${serverId}`);
2877
3193
  try {
@@ -2883,17 +3199,13 @@ async function initializeApi(agent, agentCardOverride) {
2883
3199
  await agent.removeMcpServer(serverId);
2884
3200
  return res.status(200).json({ status: "disconnected", id: serverId });
2885
3201
  } catch (error) {
2886
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2887
- logger.error(`Error deleting server '${serverId}': ${errorMessage}`);
2888
- return res.status(500).json({
2889
- error: `Failed to delete server '${serverId}': ${errorMessage}`
2890
- });
3202
+ return next(error);
2891
3203
  }
2892
3204
  });
2893
3205
  app.post(
2894
3206
  "/api/mcp/servers/:serverId/tools/:toolName/execute",
2895
3207
  express2.json(),
2896
- async (req, res) => {
3208
+ async (req, res, next) => {
2897
3209
  const { serverId, toolName } = req.params;
2898
3210
  const client = agent.getMcpClients().get(serverId);
2899
3211
  if (!client) {
@@ -2903,11 +3215,7 @@ async function initializeApi(agent, agentCardOverride) {
2903
3215
  const rawResult = await agent.executeTool(toolName, req.body);
2904
3216
  return res.json({ success: true, data: rawResult });
2905
3217
  } catch (error) {
2906
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2907
- logger.error(
2908
- `Error executing tool '${toolName}' on server '${serverId}': ${errorMessage}`
2909
- );
2910
- return res.status(500).json({ success: false, error: errorMessage });
3218
+ return next(error);
2911
3219
  }
2912
3220
  }
2913
3221
  );
@@ -2915,7 +3223,15 @@ async function initializeApi(agent, agentCardOverride) {
2915
3223
  logger.info("WebSocket client connected.");
2916
3224
  ws.on("message", async (messageBuffer) => {
2917
3225
  const messageString = messageBuffer.toString();
2918
- logger.debug(`WebSocket received message: ${messageString}`);
3226
+ try {
3227
+ const parsedMessage = JSON.parse(messageString);
3228
+ const redactedMessage = redactSensitiveData(parsedMessage);
3229
+ logger.debug(`WebSocket received message: ${JSON.stringify(redactedMessage)}`);
3230
+ } catch {
3231
+ const redacted = String(redactSensitiveData(messageString));
3232
+ const truncated = redacted.length > 200 ? `${redacted.substring(0, 200)}... (${redacted.length} total chars)` : redacted;
3233
+ logger.debug(`WebSocket received message: ${truncated}`);
3234
+ }
2919
3235
  try {
2920
3236
  const data = JSON.parse(messageString);
2921
3237
  if (data.type === "toolConfirmationResponse" && data.data) {
@@ -2923,13 +3239,13 @@ async function initializeApi(agent, agentCardOverride) {
2923
3239
  return;
2924
3240
  } else if (data.type === "message" && (data.content || data.imageData || data.fileData)) {
2925
3241
  logger.info(
2926
- `Processing message from WebSocket: ${data.content.substring(0, 50)}...`
3242
+ `Processing message from WebSocket: ${data.content ? data.content.substring(0, 50) + "..." : "[image/file only]"}`
2927
3243
  );
2928
3244
  const imageDataInput = data.imageData ? { image: data.imageData.base64, mimeType: data.imageData.mimeType } : void 0;
2929
3245
  const fileDataInput = data.fileData ? {
2930
3246
  data: data.fileData.base64,
2931
3247
  mimeType: data.fileData.mimeType,
2932
- filename: data.fileData.filename
3248
+ ...data.fileData.filename && { filename: data.fileData.filename }
2933
3249
  } : void 0;
2934
3250
  const sessionId = data.sessionId;
2935
3251
  const stream = data.stream === true;
@@ -2949,19 +3265,28 @@ async function initializeApi(agent, agentCardOverride) {
2949
3265
  }
2950
3266
  );
2951
3267
  if (!validation.ok) {
2952
- const errorMessages = validation.issues.filter((issue) => issue.severity === "error").map((issue) => issue.message);
2953
- const errorDetails = {
2954
- error: errorMessages.join("; "),
3268
+ const redactedIssues = redactSensitiveData(validation.issues);
3269
+ logger.error(`Invalid input for current LLM configuration`, {
2955
3270
  provider: currentConfig.llm.provider,
2956
3271
  model: currentConfig.llm.model,
2957
- issues: validation.issues
2958
- };
2959
- ws.send(
2960
- JSON.stringify({
2961
- event: "error",
2962
- data: errorDetails
2963
- })
2964
- );
3272
+ issues: redactedIssues
3273
+ });
3274
+ const hierarchicalError = new DextoValidationError([
3275
+ {
3276
+ code: "agent_api_validation_error" /* API_VALIDATION_ERROR */,
3277
+ message: "Invalid input for current LLM configuration",
3278
+ scope: "agent" /* AGENT */,
3279
+ type: "user" /* USER */,
3280
+ severity: "error",
3281
+ context: {
3282
+ provider: currentConfig.llm.provider,
3283
+ model: currentConfig.llm.model,
3284
+ detailedIssues: validation.issues
3285
+ // Nest the specific validation details
3286
+ }
3287
+ }
3288
+ ]);
3289
+ sendWebSocketError(ws, hierarchicalError);
2965
3290
  return;
2966
3291
  }
2967
3292
  await agent.run(data.content, imageDataInput, fileDataInput, sessionId, stream);
@@ -2973,22 +3298,15 @@ async function initializeApi(agent, agentCardOverride) {
2973
3298
  await agent.resetConversation(sessionId);
2974
3299
  } else {
2975
3300
  logger.warn(`Received unknown WebSocket message type: ${data.type}`);
2976
- ws.send(
2977
- JSON.stringify({
2978
- event: "error",
2979
- data: { message: "Unknown message type" }
2980
- })
2981
- );
3301
+ sendWebSocketValidationError(ws, "Unknown message type", {
3302
+ messageType: data.type
3303
+ });
2982
3304
  }
2983
3305
  } catch (error) {
2984
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2985
- logger.error(`Error processing WebSocket message: ${errorMessage}`);
2986
- ws.send(
2987
- JSON.stringify({
2988
- event: "error",
2989
- data: { message: "Failed to process message" }
2990
- })
3306
+ logger.error(
3307
+ `Error processing WebSocket message: ${error instanceof Error ? error.message : "Unknown error"}`
2991
3308
  );
3309
+ sendWebSocketError(ws, error);
2992
3310
  }
2993
3311
  });
2994
3312
  ws.on("close", () => {
@@ -3058,7 +3376,7 @@ async function initializeApi(agent, agentCardOverride) {
3058
3376
  }
3059
3377
  return redactedServers;
3060
3378
  }
3061
- app.get("/api/config.yaml", async (req, res) => {
3379
+ app.get("/api/config.yaml", async (req, res, next) => {
3062
3380
  try {
3063
3381
  const sessionId = req.query.sessionId;
3064
3382
  const config = agent.getEffectiveConfig(sessionId);
@@ -3074,21 +3392,19 @@ async function initializeApi(agent, agentCardOverride) {
3074
3392
  res.set("Content-Type", "application/x-yaml");
3075
3393
  res.send(yamlStr);
3076
3394
  } catch (error) {
3077
- logger.error(`Error exporting config: ${error.message}`);
3078
- res.status(500).json({ error: "Failed to export configuration" });
3395
+ return next(error);
3079
3396
  }
3080
3397
  });
3081
- app.get("/api/llm/current", async (req, res) => {
3398
+ app.get("/api/llm/current", async (req, res, next) => {
3082
3399
  try {
3083
3400
  const { sessionId } = req.query;
3084
3401
  const currentConfig = sessionId ? agent.getEffectiveConfig(sessionId).llm : agent.getCurrentLLMConfig();
3085
3402
  res.json({ config: currentConfig });
3086
3403
  } catch (error) {
3087
- logger.error(`Error getting current LLM config: ${error.message}`);
3088
- res.status(500).json({ error: "Failed to get current LLM configuration" });
3404
+ return next(error);
3089
3405
  }
3090
3406
  });
3091
- app.get("/api/llm/providers", async (req, res) => {
3407
+ app.get("/api/llm/providers", async (req, res, next) => {
3092
3408
  try {
3093
3409
  const providers = {};
3094
3410
  for (const provider of LLM_PROVIDERS) {
@@ -3103,67 +3419,50 @@ async function initializeApi(agent, agentCardOverride) {
3103
3419
  }
3104
3420
  res.json({ providers });
3105
3421
  } catch (error) {
3106
- logger.error(`Error getting LLM providers: ${error.message}`);
3107
- res.status(500).json({ error: "Failed to get LLM providers" });
3422
+ return next(error);
3108
3423
  }
3109
3424
  });
3110
- app.post("/api/llm/switch", express2.json(), async (req, res) => {
3111
- const validation = validateBody(LLMSwitchRequestSchema, req.body);
3112
- if (!validation.success) {
3113
- return res.status(400).json(validation.response);
3114
- }
3115
- const { sessionId, ...llmConfig } = validation.data;
3425
+ app.post("/api/llm/switch", express2.json(), async (req, res, next) => {
3116
3426
  try {
3427
+ const body = req.body ?? {};
3428
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId : void 0;
3429
+ const { sessionId: _omit, ...llmCandidate } = body;
3430
+ const llmConfig = LLMUpdatesSchema.parse(llmCandidate);
3117
3431
  const config = await agent.switchLLM(llmConfig, sessionId);
3118
- return res.status(200).json({
3119
- ok: true,
3120
- data: config,
3121
- issues: []
3122
- });
3432
+ return res.status(200).json({ config, sessionId });
3123
3433
  } catch (error) {
3124
- if (error instanceof DextoValidationError) {
3125
- return res.status(400).json({
3126
- ok: false,
3127
- issues: error.issues
3128
- });
3129
- } else {
3130
- logger.error("LLM switch failed:", error);
3131
- return res.status(500).json({
3132
- ok: false,
3133
- issues: [
3134
- {
3135
- code: "internal_server_error",
3136
- message: "Internal server error during LLM switch",
3137
- severity: "error",
3138
- context: {}
3139
- }
3140
- ]
3141
- });
3142
- }
3434
+ return next(error);
3143
3435
  }
3144
3436
  });
3145
- app.get("/api/sessions", async (req, res) => {
3437
+ app.get("/api/sessions", async (req, res, next) => {
3146
3438
  try {
3147
3439
  const sessionIds = await agent.listSessions();
3148
3440
  const sessions = await Promise.all(
3149
3441
  sessionIds.map(async (id) => {
3150
- const metadata = await agent.getSessionMetadata(id);
3151
- return {
3152
- id,
3153
- createdAt: metadata?.createdAt || null,
3154
- lastActivity: metadata?.lastActivity || null,
3155
- messageCount: metadata?.messageCount || 0
3156
- };
3442
+ try {
3443
+ const metadata = await agent.getSessionMetadata(id);
3444
+ return {
3445
+ id,
3446
+ createdAt: metadata?.createdAt || null,
3447
+ lastActivity: metadata?.lastActivity || null,
3448
+ messageCount: metadata?.messageCount || 0
3449
+ };
3450
+ } catch (_error) {
3451
+ return {
3452
+ id,
3453
+ createdAt: null,
3454
+ lastActivity: null,
3455
+ messageCount: 0
3456
+ };
3457
+ }
3157
3458
  })
3158
3459
  );
3159
3460
  return res.json({ sessions });
3160
3461
  } catch (error) {
3161
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3162
- logger.error(`Error listing sessions: ${errorMessage}`);
3163
- return res.status(500).json({ error: "Failed to list sessions" });
3462
+ return next(error);
3164
3463
  }
3165
3464
  });
3166
- app.post("/api/sessions", express2.json(), async (req, res) => {
3465
+ app.post("/api/sessions", express2.json(), async (req, res, next) => {
3167
3466
  try {
3168
3467
  const { sessionId } = req.body;
3169
3468
  const session = await agent.createSession(sessionId);
@@ -3171,24 +3470,26 @@ async function initializeApi(agent, agentCardOverride) {
3171
3470
  return res.status(201).json({
3172
3471
  session: {
3173
3472
  id: session.id,
3174
- createdAt: metadata?.createdAt || null,
3175
- lastActivity: metadata?.lastActivity || null,
3473
+ createdAt: metadata?.createdAt || Date.now(),
3474
+ lastActivity: metadata?.lastActivity || Date.now(),
3176
3475
  messageCount: metadata?.messageCount || 0
3177
3476
  }
3178
3477
  });
3179
3478
  } catch (error) {
3180
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3181
- logger.error(`Error creating session: ${errorMessage}`);
3182
- return res.status(500).json({ error: "Failed to create session" });
3479
+ return next(error);
3480
+ }
3481
+ });
3482
+ app.get("/api/sessions/current", async (req, res, next) => {
3483
+ try {
3484
+ const currentSessionId = agent.getCurrentSessionId();
3485
+ return res.json({ currentSessionId });
3486
+ } catch (error) {
3487
+ return next(error);
3183
3488
  }
3184
3489
  });
3185
- app.get("/api/sessions/:sessionId", async (req, res) => {
3490
+ app.get("/api/sessions/:sessionId", async (req, res, next) => {
3186
3491
  try {
3187
3492
  const { sessionId } = req.params;
3188
- const session = await agent.getSession(sessionId);
3189
- if (!session) {
3190
- return res.status(404).json({ error: "Session not found" });
3191
- }
3192
3493
  const metadata = await agent.getSessionMetadata(sessionId);
3193
3494
  const history = await agent.getSessionHistory(sessionId);
3194
3495
  return res.json({
@@ -3201,84 +3502,61 @@ async function initializeApi(agent, agentCardOverride) {
3201
3502
  }
3202
3503
  });
3203
3504
  } catch (error) {
3204
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3205
- logger.error(`Error getting session ${req.params.sessionId}: ${errorMessage}`);
3206
- return res.status(500).json({ error: "Failed to get session details" });
3505
+ return next(error);
3207
3506
  }
3208
3507
  });
3209
- app.get("/api/sessions/:sessionId/history", async (req, res) => {
3508
+ app.get("/api/sessions/:sessionId/history", async (req, res, next) => {
3210
3509
  try {
3211
3510
  const { sessionId } = req.params;
3212
- const session = await agent.getSession(sessionId);
3213
- if (!session) {
3214
- return res.status(404).json({ error: "Session not found" });
3215
- }
3216
3511
  const history = await agent.getSessionHistory(sessionId);
3217
3512
  return res.json({ history });
3218
3513
  } catch (error) {
3219
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3220
- logger.error(
3221
- `Error getting session history for ${req.params.sessionId}: ${errorMessage}`
3222
- );
3223
- return res.status(500).json({ error: "Failed to get session history" });
3514
+ return next(error);
3224
3515
  }
3225
3516
  });
3226
- app.get("/api/search/messages", async (req, res) => {
3517
+ app.get("/api/search/messages", async (req, res, next) => {
3227
3518
  try {
3228
- const query = req.query.q;
3229
- if (!query) {
3230
- return res.status(400).json({ error: "Search query is required" });
3231
- }
3519
+ const {
3520
+ q: query,
3521
+ limit,
3522
+ offset,
3523
+ sessionId,
3524
+ role
3525
+ } = parseQuery(SearchQuerySchema, req.query);
3232
3526
  const options = {
3233
- limit: req.query.limit ? parseInt(req.query.limit) : 20,
3234
- offset: req.query.offset ? parseInt(req.query.offset) : 0
3527
+ limit: limit || 20,
3528
+ offset: offset || 0,
3529
+ ...sessionId && { sessionId },
3530
+ ...role && { role }
3235
3531
  };
3236
- const sessionId = req.query.sessionId;
3237
- const role = req.query.role;
3238
- if (sessionId) {
3239
- options.sessionId = sessionId;
3240
- }
3241
- if (role) {
3242
- options.role = role;
3243
- }
3244
3532
  const searchResults = await agent.searchMessages(query, options);
3245
3533
  return sendJsonResponse(res, searchResults);
3246
3534
  } catch (error) {
3247
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3248
- logger.error(`Error searching messages: ${errorMessage}`);
3249
- return res.status(500).json({ error: "Failed to search messages" });
3535
+ return next(error);
3250
3536
  }
3251
3537
  });
3252
- app.get("/api/search/sessions", async (req, res) => {
3538
+ app.get("/api/search/sessions", async (req, res, next) => {
3253
3539
  try {
3254
- const query = req.query.q;
3255
- if (!query) {
3256
- return res.status(400).json({ error: "Search query is required" });
3257
- }
3540
+ const { q: query } = parseQuery(
3541
+ z2.object({ q: z2.string().min(1, "Search query is required") }),
3542
+ req.query
3543
+ );
3258
3544
  const searchResults = await agent.searchSessions(query);
3259
3545
  return sendJsonResponse(res, searchResults);
3260
3546
  } catch (error) {
3261
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3262
- logger.error(`Error searching sessions: ${errorMessage}`);
3263
- return res.status(500).json({ error: "Failed to search sessions" });
3547
+ return next(error);
3264
3548
  }
3265
3549
  });
3266
- app.delete("/api/sessions/:sessionId", async (req, res) => {
3550
+ app.delete("/api/sessions/:sessionId", async (req, res, next) => {
3267
3551
  try {
3268
3552
  const { sessionId } = req.params;
3269
- const session = await agent.getSession(sessionId);
3270
- if (!session) {
3271
- return res.status(404).json({ error: "Session not found" });
3272
- }
3273
3553
  await agent.deleteSession(sessionId);
3274
3554
  return res.json({ status: "deleted", sessionId });
3275
3555
  } catch (error) {
3276
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3277
- logger.error(`Error deleting session ${req.params.sessionId}: ${errorMessage}`);
3278
- return res.status(500).json({ error: "Failed to delete session" });
3556
+ return next(error);
3279
3557
  }
3280
3558
  });
3281
- app.post("/api/sessions/:sessionId/load", async (req, res) => {
3559
+ app.post("/api/sessions/:sessionId/load", async (req, res, next) => {
3282
3560
  try {
3283
3561
  const { sessionId } = req.params;
3284
3562
  if (sessionId === "null" || sessionId === "undefined") {
@@ -3290,10 +3568,6 @@ async function initializeApi(agent, agentCardOverride) {
3290
3568
  });
3291
3569
  return;
3292
3570
  }
3293
- const session = await agent.getSession(sessionId);
3294
- if (!session) {
3295
- return res.status(404).json({ error: "Session not found" });
3296
- }
3297
3571
  await agent.loadSession(sessionId);
3298
3572
  return res.json({
3299
3573
  status: "loaded",
@@ -3301,35 +3575,15 @@ async function initializeApi(agent, agentCardOverride) {
3301
3575
  currentSession: agent.getCurrentSessionId()
3302
3576
  });
3303
3577
  } catch (error) {
3304
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3305
- logger.error(`Error loading session ${req.params.sessionId}: ${errorMessage}`);
3306
- return res.status(500).json({ error: "Failed to load session" });
3578
+ return next(error);
3307
3579
  }
3308
3580
  });
3309
- app.get("/api/sessions/current", async (req, res) => {
3581
+ const webhookSubscriber = new WebhookEventSubscriber();
3582
+ logger.info("Setting up webhook event subscriptions...");
3583
+ webhookSubscriber.subscribe(agent.agentEventBus);
3584
+ app.post("/api/webhooks", express2.json(), async (req, res, next) => {
3310
3585
  try {
3311
- const currentSessionId = agent.getCurrentSessionId();
3312
- return res.json({ currentSessionId });
3313
- } catch (error) {
3314
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3315
- logger.error(`Error getting current session: ${errorMessage}`);
3316
- return res.status(500).json({ error: "Failed to get current session" });
3317
- }
3318
- });
3319
- const webhookSubscriber = new WebhookEventSubscriber();
3320
- logger.info("Setting up webhook event subscriptions...");
3321
- webhookSubscriber.subscribe(agent.agentEventBus);
3322
- app.post("/api/webhooks", express2.json(), async (req, res) => {
3323
- try {
3324
- const { url, secret, description } = req.body;
3325
- if (!url || typeof url !== "string") {
3326
- return res.status(400).json({ error: "Invalid or missing webhook URL" });
3327
- }
3328
- try {
3329
- new URL(url);
3330
- } catch {
3331
- return res.status(400).json({ error: "Invalid URL format" });
3332
- }
3586
+ const { url, secret, description } = parseBody(WebhookRequestSchema, req.body);
3333
3587
  const webhookId = `wh_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3334
3588
  const webhook = {
3335
3589
  id: webhookId,
@@ -3353,12 +3607,10 @@ async function initializeApi(agent, agentCardOverride) {
3353
3607
  201
3354
3608
  );
3355
3609
  } catch (error) {
3356
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3357
- logger.error(`Error registering webhook: ${errorMessage}`);
3358
- return res.status(500).json({ error: "Failed to register webhook" });
3610
+ return next(error);
3359
3611
  }
3360
3612
  });
3361
- app.get("/api/webhooks", async (req, res) => {
3613
+ app.get("/api/webhooks", async (req, res, next) => {
3362
3614
  try {
3363
3615
  const webhooks = webhookSubscriber.getWebhooks().map((webhook) => ({
3364
3616
  id: webhook.id,
@@ -3368,12 +3620,10 @@ async function initializeApi(agent, agentCardOverride) {
3368
3620
  }));
3369
3621
  return sendJsonResponse(res, { webhooks });
3370
3622
  } catch (error) {
3371
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3372
- logger.error(`Error listing webhooks: ${errorMessage}`);
3373
- return res.status(500).json({ error: "Failed to list webhooks" });
3623
+ return next(error);
3374
3624
  }
3375
3625
  });
3376
- app.get("/api/webhooks/:webhookId", async (req, res) => {
3626
+ app.get("/api/webhooks/:webhookId", async (req, res, next) => {
3377
3627
  try {
3378
3628
  const { webhookId } = req.params;
3379
3629
  const webhook = webhookSubscriber.getWebhook(webhookId);
@@ -3389,12 +3639,10 @@ async function initializeApi(agent, agentCardOverride) {
3389
3639
  }
3390
3640
  });
3391
3641
  } catch (error) {
3392
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3393
- logger.error(`Error getting webhook ${req.params.webhookId}: ${errorMessage}`);
3394
- return res.status(500).json({ error: "Failed to get webhook" });
3642
+ return next(error);
3395
3643
  }
3396
3644
  });
3397
- app.delete("/api/webhooks/:webhookId", async (req, res) => {
3645
+ app.delete("/api/webhooks/:webhookId", async (req, res, next) => {
3398
3646
  try {
3399
3647
  const { webhookId } = req.params;
3400
3648
  const removed = webhookSubscriber.removeWebhook(webhookId);
@@ -3404,12 +3652,10 @@ async function initializeApi(agent, agentCardOverride) {
3404
3652
  logger.info(`Webhook removed: ${webhookId}`);
3405
3653
  return res.json({ status: "removed", webhookId });
3406
3654
  } catch (error) {
3407
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3408
- logger.error(`Error removing webhook ${req.params.webhookId}: ${errorMessage}`);
3409
- return res.status(500).json({ error: "Failed to remove webhook" });
3655
+ return next(error);
3410
3656
  }
3411
3657
  });
3412
- app.post("/api/webhooks/:webhookId/test", async (req, res) => {
3658
+ app.post("/api/webhooks/:webhookId/test", async (req, res, next) => {
3413
3659
  try {
3414
3660
  const { webhookId } = req.params;
3415
3661
  const webhook = webhookSubscriber.getWebhook(webhookId);
@@ -3428,11 +3674,10 @@ async function initializeApi(agent, agentCardOverride) {
3428
3674
  }
3429
3675
  });
3430
3676
  } catch (error) {
3431
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3432
- logger.error(`Error testing webhook ${req.params.webhookId}: ${errorMessage}`);
3433
- return res.status(500).json({ error: "Failed to test webhook" });
3677
+ return next(error);
3434
3678
  }
3435
3679
  });
3680
+ app.use(errorHandler);
3436
3681
  return { app, server, wss, webSubscriber, webhookSubscriber };
3437
3682
  }
3438
3683
  function startLegacyWebUI(app) {
@@ -3481,11 +3726,11 @@ async function startApiAndLegacyWebUIServer(agent, port = 3e3, serveLegacyWebUI,
3481
3726
  }
3482
3727
 
3483
3728
  // src/app/discord/bot.ts
3484
- import dotenv from "dotenv";
3729
+ import dotenv2 from "dotenv";
3485
3730
  import { Client, GatewayIntentBits, Partials } from "discord.js";
3486
3731
  import https from "https";
3487
3732
  import http2 from "http";
3488
- dotenv.config();
3733
+ dotenv2.config();
3489
3734
  var token = process.env.DISCORD_BOT_TOKEN;
3490
3735
  var userCooldowns = /* @__PURE__ */ new Map();
3491
3736
  var RATE_LIMIT_ENABLED = process.env.DISCORD_RATE_LIMIT_ENABLED?.toLowerCase() !== "false";
@@ -3646,10 +3891,10 @@ function startDiscordBot(agent) {
3646
3891
  }
3647
3892
 
3648
3893
  // src/app/telegram/bot.ts
3649
- import dotenv2 from "dotenv";
3894
+ import dotenv3 from "dotenv";
3650
3895
  import { Bot, InlineKeyboard } from "grammy";
3651
3896
  import https2 from "https";
3652
- dotenv2.config();
3897
+ dotenv3.config();
3653
3898
  var token2 = process.env.TELEGRAM_BOT_TOKEN;
3654
3899
  var MAX_CONCURRENT_INLINE_QUERIES = process.env.TELEGRAM_INLINE_QUERY_CONCURRENCY ? Number(process.env.TELEGRAM_INLINE_QUERY_CONCURRENCY) : 5;
3655
3900
  var currentInlineQueries = 0;
@@ -3835,11 +4080,11 @@ function startTelegramBot(agent) {
3835
4080
 
3836
4081
  // src/app/cli/utils/options.ts
3837
4082
  import { z as z3 } from "zod";
4083
+ import chalk14 from "chalk";
3838
4084
  function validateCliOptions(opts) {
3839
- logger.debug("Validating command-line options", "cyanBright");
3840
- const supportedProviders = getSupportedProviders().map((p3) => p3.toLowerCase());
4085
+ const supportedProviders = getSupportedProviders().map((p7) => p7.toLowerCase());
3841
4086
  const cliOptionShape = z3.object({
3842
- agent: z3.string().nonempty("Agent config file path must not be empty"),
4087
+ agent: z3.string().min(1, "Agent name or path must not be empty").optional(),
3843
4088
  strict: z3.boolean().optional().default(false),
3844
4089
  verbose: z3.boolean().optional().default(true),
3845
4090
  mode: z3.enum(["cli", "web", "server", "discord", "telegram", "mcp"], {
@@ -3856,7 +4101,8 @@ function validateCliOptions(opts) {
3856
4101
  ),
3857
4102
  provider: z3.string().optional(),
3858
4103
  model: z3.string().optional(),
3859
- router: z3.enum(["vercel", "in-built"]).optional()
4104
+ router: z3.enum(["vercel", "in-built"]).optional(),
4105
+ interactive: z3.boolean().optional().default(true).describe("Enable interactive prompts (set to false with --no-interactive)")
3860
4106
  });
3861
4107
  const cliOptionSchema = cliOptionShape.refine(
3862
4108
  (data) => !data.provider || supportedProviders.includes(data.provider.toLowerCase()),
@@ -3895,28 +4141,387 @@ function validateCliOptions(opts) {
3895
4141
  webPort: opts.webPort,
3896
4142
  provider: opts.provider,
3897
4143
  model: opts.model,
3898
- router: opts.router
4144
+ router: opts.router,
4145
+ interactive: opts.interactive
3899
4146
  });
3900
- logger.debug("Command-line options validated successfully", "green");
3901
4147
  }
3902
4148
  function handleCliOptionsError(error) {
3903
4149
  if (error instanceof z3.ZodError) {
3904
- logger.error("Invalid command-line options detected:");
4150
+ console.error(chalk14.red("\u274C Invalid command-line options detected:"));
3905
4151
  error.errors.forEach((err) => {
3906
4152
  const fieldName = err.path.join(".") || "Unknown Option";
3907
- logger.error(`- Option '${fieldName}': ${err.message}`);
4153
+ console.error(chalk14.red(` \u2022 Option '${fieldName}': ${err.message}`));
3908
4154
  });
3909
- logger.error(
3910
- "Please check your command-line arguments or run with --help for usage details."
4155
+ console.error(
4156
+ chalk14.dim(
4157
+ "\nPlease check your command-line arguments or run with --help for usage details."
4158
+ )
3911
4159
  );
3912
4160
  } else {
3913
- logger.error(
3914
- `Validation error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
4161
+ console.error(
4162
+ chalk14.red(
4163
+ `\u274C Validation error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
4164
+ )
3915
4165
  );
3916
4166
  }
3917
4167
  process.exit(1);
3918
4168
  }
3919
4169
 
4170
+ // src/app/cli/utils/config-validation.ts
4171
+ import chalk17 from "chalk";
4172
+
4173
+ // src/app/cli/utils/api-key-setup.ts
4174
+ import * as p2 from "@clack/prompts";
4175
+ import chalk16 from "chalk";
4176
+
4177
+ // src/app/cli/utils/env-utils.ts
4178
+ async function updateEnvFileWithLLMKeys(envFilePath, llmProvider, llmApiKey) {
4179
+ logger.debug(
4180
+ `updateEnvFileWithLLMKeys: ${JSON.stringify({
4181
+ envFilePath,
4182
+ llmProvider,
4183
+ hasApiKey: Boolean(llmApiKey)
4184
+ })}`
4185
+ );
4186
+ const updates = {};
4187
+ if (llmProvider && llmApiKey) {
4188
+ const envVar = getPrimaryApiKeyEnvVar(llmProvider);
4189
+ updates[envVar] = llmApiKey;
4190
+ }
4191
+ await updateEnvFile(envFilePath, updates);
4192
+ }
4193
+
4194
+ // src/app/cli/utils/provider-setup.ts
4195
+ import * as p from "@clack/prompts";
4196
+ import chalk15 from "chalk";
4197
+ var PROVIDER_OPTIONS = [
4198
+ {
4199
+ value: "google",
4200
+ label: "\u{1F7E2} Google Gemini",
4201
+ hint: "Free tier available - Recommended for beginners"
4202
+ },
4203
+ {
4204
+ value: "groq",
4205
+ label: "\u{1F7E2} Groq",
4206
+ hint: "Free tier available - Very fast responses"
4207
+ },
4208
+ {
4209
+ value: "openai",
4210
+ label: "\u{1F7E1} OpenAI",
4211
+ hint: "Most popular, requires payment"
4212
+ },
4213
+ {
4214
+ value: "anthropic",
4215
+ label: "\u{1F7E1} Anthropic",
4216
+ hint: "High quality models, requires payment"
4217
+ }
4218
+ ];
4219
+ async function selectProvider() {
4220
+ const choice = await p.select({
4221
+ message: "Choose your AI provider",
4222
+ options: PROVIDER_OPTIONS
4223
+ });
4224
+ if (p.isCancel(choice)) {
4225
+ p.cancel("Setup cancelled");
4226
+ process.exit(1);
4227
+ }
4228
+ return choice;
4229
+ }
4230
+ function getProviderDisplayName(provider) {
4231
+ switch (provider) {
4232
+ case "google":
4233
+ return "Google Gemini";
4234
+ case "openai":
4235
+ return "OpenAI";
4236
+ case "anthropic":
4237
+ return "Anthropic";
4238
+ case "groq":
4239
+ return "Groq";
4240
+ default:
4241
+ return provider;
4242
+ }
4243
+ }
4244
+ function isValidApiKeyFormat(apiKey, provider) {
4245
+ switch (provider) {
4246
+ case "google":
4247
+ return apiKey.startsWith("AIza") && apiKey.length > 20;
4248
+ case "openai":
4249
+ return apiKey.startsWith("sk-") && apiKey.length > 40;
4250
+ case "anthropic":
4251
+ return apiKey.startsWith("sk-ant-") && apiKey.length > 40;
4252
+ case "groq":
4253
+ return apiKey.startsWith("gsk_") && apiKey.length > 40;
4254
+ default:
4255
+ return apiKey.length > 10;
4256
+ }
4257
+ }
4258
+ function getProviderInstructions(provider) {
4259
+ switch (provider) {
4260
+ case "google":
4261
+ return {
4262
+ title: chalk15.green("Google Gemini - Free API Key"),
4263
+ content: `1. Visit: ${chalk15.cyan("https://aistudio.google.com/apikey")}
4264
+ 2. Sign in with your Google account
4265
+ 3. Click "Create API Key"
4266
+ 4. Copy the key
4267
+
4268
+ ${chalk15.dim("\u2728 Free tier included")}`
4269
+ };
4270
+ case "openai":
4271
+ return {
4272
+ title: chalk15.blue("OpenAI API Key"),
4273
+ content: `1. Visit: ${chalk15.cyan("https://platform.openai.com/api-keys")}
4274
+ 2. Sign in to your OpenAI account
4275
+ 3. Click "Create new secret key"
4276
+ 4. Copy the key
4277
+
4278
+ ${chalk15.dim("\u{1F4B0} Requires payment")}`
4279
+ };
4280
+ case "anthropic":
4281
+ return {
4282
+ title: chalk15.magenta("Anthropic API Key"),
4283
+ content: `1. Visit: ${chalk15.cyan("https://console.anthropic.com/settings/keys")}
4284
+ 2. Sign in to your Anthropic account
4285
+ 3. Click "Create Key"
4286
+ 4. Copy the key
4287
+
4288
+ ${chalk15.dim("\u{1F4B0} Requires payment")}`
4289
+ };
4290
+ case "groq":
4291
+ return {
4292
+ title: chalk15.yellow("Groq API Key"),
4293
+ content: `1. Visit: ${chalk15.cyan("https://console.groq.com/keys")}
4294
+ 2. Sign in with your account
4295
+ 3. Click "Create API Key"
4296
+ 4. Copy the key
4297
+
4298
+ ${chalk15.dim("\u{1F193} Free tier included")}`
4299
+ };
4300
+ default:
4301
+ return null;
4302
+ }
4303
+ }
4304
+
4305
+ // src/app/cli/utils/api-key-setup.ts
4306
+ async function interactiveApiKeySetup(provider) {
4307
+ try {
4308
+ p2.intro(chalk16.cyan("\u{1F511} API Key Setup "));
4309
+ const instructions = getProviderInstructions(provider);
4310
+ p2.note(
4311
+ `Your configuration requires a ${getProviderDisplayName(provider)} API key.
4312
+
4313
+ ` + (instructions ? instructions.content : "Please get an API key for this provider."),
4314
+ chalk16.bold(`${getProviderDisplayName(provider)} API Key Required`)
4315
+ );
4316
+ const action = await p2.select({
4317
+ message: "What would you like to do?",
4318
+ options: [
4319
+ {
4320
+ value: "setup",
4321
+ label: "Set up an API key now",
4322
+ hint: "Interactive setup (recommended)"
4323
+ },
4324
+ {
4325
+ value: "manual",
4326
+ label: "Set up manually later",
4327
+ hint: "Get instructions for manual setup"
4328
+ },
4329
+ {
4330
+ value: "exit",
4331
+ label: "Exit",
4332
+ hint: "Quit Dexto for now"
4333
+ }
4334
+ ]
4335
+ });
4336
+ if (action === "exit") {
4337
+ p2.cancel("Setup cancelled. Run dexto again when you have an API key!");
4338
+ process.exit(0);
4339
+ }
4340
+ if (action === "manual") {
4341
+ showManualSetupInstructions(provider);
4342
+ console.log(chalk16.dim("\n\u{1F44B} Run dexto again once you have set up your API key!"));
4343
+ process.exit(0);
4344
+ }
4345
+ if (p2.isCancel(action)) {
4346
+ p2.cancel("Setup cancelled");
4347
+ process.exit(1);
4348
+ }
4349
+ const apiKey = await p2.password({
4350
+ message: `Enter your ${getProviderDisplayName(provider)} API key`,
4351
+ mask: "*",
4352
+ validate: (value) => {
4353
+ if (!value || value.trim().length === 0) {
4354
+ return "API key is required";
4355
+ }
4356
+ if (!isValidApiKeyFormat(value.trim(), provider)) {
4357
+ return `Invalid ${getProviderDisplayName(provider)} API key format`;
4358
+ }
4359
+ return void 0;
4360
+ }
4361
+ });
4362
+ if (p2.isCancel(apiKey)) {
4363
+ p2.cancel("Setup cancelled");
4364
+ process.exit(0);
4365
+ }
4366
+ const spinner4 = p2.spinner();
4367
+ spinner4.start("Saving API key...");
4368
+ try {
4369
+ const envFilePath = getDextoEnvPath(process.cwd());
4370
+ await updateEnvFileWithLLMKeys(envFilePath, provider, apiKey.trim());
4371
+ spinner4.stop(
4372
+ `\u2728 API key saved successfully for ${getProviderDisplayName(provider)} in ${envFilePath}!`
4373
+ );
4374
+ p2.outro(chalk16.green(`\u2728 API key setup complete!`));
4375
+ await applyLayeredEnvironmentLoading();
4376
+ } catch (error) {
4377
+ spinner4.stop("Failed to save API key");
4378
+ logger.error(`Failed to update .env file: ${error}`);
4379
+ let instructions2;
4380
+ if (getExecutionContext() === "global-cli") {
4381
+ instructions2 = `1. Create ~/.dexto/.env file
4382
+ 2. Add this line: ${getPrimaryApiKeyEnvVar(provider)}=your_api_key_here
4383
+ 3. Run dexto again
4384
+
4385
+ Alternatively:
4386
+ \u2022 Set environment variable: export ${getPrimaryApiKeyEnvVar(provider)}=your_api_key_here
4387
+ \u2022 Or run: dexto setup`;
4388
+ } else {
4389
+ instructions2 = `1. Create a .env file in your project root
4390
+ 2. Add this line: ${getPrimaryApiKeyEnvVar(provider)}=your_api_key_here
4391
+ 3. Run dexto again`;
4392
+ }
4393
+ p2.note(
4394
+ `Manual setup required:
4395
+
4396
+ ${instructions2}`,
4397
+ chalk16.yellow("Save this API key manually")
4398
+ );
4399
+ console.error(chalk16.red("\n\u274C API key setup required to continue."));
4400
+ process.exit(1);
4401
+ }
4402
+ } catch (error) {
4403
+ if (p2.isCancel(error)) {
4404
+ p2.cancel("Setup cancelled");
4405
+ process.exit(0);
4406
+ }
4407
+ console.error(chalk16.red("\n\u274C API key setup required to continue."));
4408
+ process.exit(1);
4409
+ }
4410
+ }
4411
+ function showManualSetupInstructions(provider) {
4412
+ const envVar = getPrimaryApiKeyEnvVar(provider);
4413
+ const envInstructions = getExecutionContext() === "global-cli" ? [
4414
+ `${chalk16.bold("2. Recommended: Use dexto setup (easiest):")}`,
4415
+ ` dexto setup`,
4416
+ ``,
4417
+ `${chalk16.bold("Or manually create ~/.dexto/.env:")}`,
4418
+ ` mkdir -p ~/.dexto && echo "${envVar}=your_api_key_here" > ~/.dexto/.env`
4419
+ ] : [
4420
+ `${chalk16.bold("2. Create a .env file in your project:")}`,
4421
+ ` echo "${envVar}=your_api_key_here" > .env`
4422
+ ];
4423
+ const instructions = [
4424
+ `${chalk16.bold("1. Get an API key:")}`,
4425
+ ` \u2022 ${chalk16.green("Google Gemini (Free)")}: https://aistudio.google.com/apikey`,
4426
+ ` \u2022 ${chalk16.blue("OpenAI")}: https://platform.openai.com/api-keys`,
4427
+ ` \u2022 ${chalk16.magenta("Anthropic")}: https://console.anthropic.com/keys`,
4428
+ ` \u2022 ${chalk16.yellow("Groq (Free)")}: https://console.groq.com/keys`,
4429
+ ``,
4430
+ ...envInstructions,
4431
+ ` # OR for other providers:`,
4432
+ ` # OPENAI_API_KEY=your_key_here`,
4433
+ ` # ANTHROPIC_API_KEY=your_key_here`,
4434
+ ` # GROQ_API_KEY=your_key_here`,
4435
+ ``,
4436
+ `${chalk16.bold("3. Run dexto again:")}`,
4437
+ ` dexto | npx dexto`,
4438
+ ``,
4439
+ `${chalk16.dim("\u{1F4A1} Tip: Start with Google Gemini for a free experience!")}`
4440
+ ].join("\n");
4441
+ p2.note(instructions, chalk16.bold("Manual Setup Instructions"));
4442
+ }
4443
+
4444
+ // src/app/cli/utils/config-validation.ts
4445
+ async function validateAgentConfig(config, interactive = false) {
4446
+ const parseResult = AgentConfigSchema.safeParse(config);
4447
+ if (!parseResult.success) {
4448
+ logger.error(`Agent config validation error: ${JSON.stringify(parseResult.error)}`);
4449
+ const apiKeyError = findApiKeyError(parseResult.error, config);
4450
+ if (apiKeyError && interactive) {
4451
+ logger.debug(
4452
+ `API key error found for ${apiKeyError.provider} provider, retriggering interactive setup`
4453
+ );
4454
+ console.log(
4455
+ chalk17.yellow(`
4456
+ \u{1F511} API key required for ${apiKeyError.provider} provider
4457
+ `)
4458
+ );
4459
+ await interactiveApiKeySetup(apiKeyError.provider);
4460
+ return validateAgentConfig(config, interactive);
4461
+ }
4462
+ console.error(chalk17.red("\u274C Configuration Error:"));
4463
+ formatZodError(parseResult.error);
4464
+ process.exit(1);
4465
+ }
4466
+ return parseResult.data;
4467
+ }
4468
+ function findApiKeyError(error, configData) {
4469
+ for (const issue of error.issues) {
4470
+ if (issue.code === "custom" && hasErrorCode(issue.params, "llm_api_key_missing" /* API_KEY_MISSING */)) {
4471
+ const provider = getProviderFromParams(issue.params);
4472
+ if (provider) {
4473
+ return { provider };
4474
+ }
4475
+ }
4476
+ if (issue.path.includes("apiKey") && issue.message.includes("Missing API key")) {
4477
+ const provider = configData.llm?.provider;
4478
+ if (provider) {
4479
+ return { provider };
4480
+ }
4481
+ }
4482
+ }
4483
+ return null;
4484
+ }
4485
+ function hasErrorCode(params, expectedCode) {
4486
+ return typeof params === "object" && params !== null && "code" in params && params.code === expectedCode;
4487
+ }
4488
+ function getProviderFromParams(params) {
4489
+ if (typeof params === "object" && params !== null && "provider" in params && typeof params.provider === "string") {
4490
+ return params.provider;
4491
+ }
4492
+ return null;
4493
+ }
4494
+ function formatZodError(error) {
4495
+ for (const issue of error.issues) {
4496
+ const path9 = issue.path.length > 0 ? issue.path.join(".") : "config";
4497
+ console.error(chalk17.red(` \u2022 ${path9}: ${issue.message}`));
4498
+ }
4499
+ }
4500
+
4501
+ // src/app/config/cli-overrides.ts
4502
+ function applyCLIOverrides(baseConfig, cliOverrides) {
4503
+ if (!cliOverrides || Object.keys(cliOverrides).length === 0) {
4504
+ return baseConfig;
4505
+ }
4506
+ const mergedConfig = JSON.parse(JSON.stringify(baseConfig));
4507
+ if (!mergedConfig.llm) {
4508
+ mergedConfig.llm = {};
4509
+ }
4510
+ if (cliOverrides.provider) {
4511
+ mergedConfig.llm.provider = cliOverrides.provider;
4512
+ }
4513
+ if (cliOverrides.model) {
4514
+ mergedConfig.llm.model = cliOverrides.model;
4515
+ }
4516
+ if (cliOverrides.router) {
4517
+ mergedConfig.llm.router = cliOverrides.router;
4518
+ }
4519
+ if (cliOverrides.apiKey) {
4520
+ mergedConfig.llm.apiKey = cliOverrides.apiKey;
4521
+ }
4522
+ return mergedConfig;
4523
+ }
4524
+
3920
4525
  // src/core/utils/port-utils.ts
3921
4526
  function getPort(envVar, defaultPort, varName) {
3922
4527
  if (envVar === void 0) {
@@ -3929,18 +4534,145 @@ function getPort(envVar, defaultPort, varName) {
3929
4534
  return port;
3930
4535
  }
3931
4536
 
3932
- // src/app/cli/project-commands/create.ts
3933
- import fs from "fs-extra";
3934
- import path from "path";
3935
- import chalk14 from "chalk";
3936
- import * as p from "@clack/prompts";
4537
+ // src/app/cli/commands/create-app.ts
4538
+ import fs3 from "fs-extra";
4539
+ import path4 from "path";
4540
+ import chalk18 from "chalk";
4541
+
4542
+ // src/app/cli/utils/execute.ts
4543
+ import { spawn } from "child_process";
4544
+ var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
4545
+ function executeWithTimeout(command, args, options) {
4546
+ return new Promise((resolve, reject) => {
4547
+ const { cwd, timeoutMs: timeout = DEFAULT_TIMEOUT_MS } = options;
4548
+ const child = spawn(command, args, { cwd });
4549
+ let stdout = "";
4550
+ let stderr = "";
4551
+ const timer = setTimeout(() => {
4552
+ logger.error(`Process timed out after ${timeout}ms, killing process`);
4553
+ child.kill();
4554
+ reject(new Error(`Process timed out after ${timeout}ms`));
4555
+ }, timeout);
4556
+ child.stdout.on("data", (data) => {
4557
+ const text3 = data.toString();
4558
+ stdout += text3;
4559
+ logger.debug(text3);
4560
+ });
4561
+ child.stderr.on("data", (data) => {
4562
+ const text3 = data.toString();
4563
+ stderr += text3;
4564
+ });
4565
+ child.on("error", (error) => {
4566
+ clearTimeout(timer);
4567
+ logger.error(`Error spawning process: ${error.message}`);
4568
+ reject(error);
4569
+ });
4570
+ child.on("close", (code) => {
4571
+ clearTimeout(timer);
4572
+ if (code !== 0) {
4573
+ logger.error(`Process exited with code ${code}
4574
+ ${stderr}`);
4575
+ reject(new Error(`Process exited with code ${code}`));
4576
+ } else {
4577
+ logger.debug(`${command} ${args.join(" ")} stdout: ${stdout}`);
4578
+ resolve();
4579
+ }
4580
+ });
4581
+ });
4582
+ }
4583
+
4584
+ // src/app/cli/commands/create-app.ts
4585
+ import * as p3 from "@clack/prompts";
4586
+
4587
+ // src/app/cli/utils/package-mgmt.ts
4588
+ import fsExtra from "fs-extra";
4589
+ import path3 from "path";
4590
+ function getPackageManagerInstallCommand(pm) {
4591
+ switch (pm) {
4592
+ case "npm":
4593
+ return "install";
4594
+ case "yarn":
4595
+ return "add";
4596
+ case "pnpm":
4597
+ return "add";
4598
+ case "bun":
4599
+ return "add";
4600
+ default:
4601
+ return "install";
4602
+ }
4603
+ }
4604
+ function getPackageManager() {
4605
+ const projectRoot = findPackageRoot(process.cwd());
4606
+ if (!projectRoot) {
4607
+ return "npm";
4608
+ }
4609
+ if (fsExtra.existsSync(path3.join(projectRoot, "pnpm-lock.yaml"))) {
4610
+ return "pnpm";
4611
+ }
4612
+ if (fsExtra.existsSync(path3.join(projectRoot, "yarn.lock"))) {
4613
+ return "yarn";
4614
+ }
4615
+ if (fsExtra.existsSync(path3.join(projectRoot, "bun.lockb")) || fsExtra.existsSync(path3.join(projectRoot, "bun.lock"))) {
4616
+ return "bun";
4617
+ }
4618
+ return "npm";
4619
+ }
4620
+ async function addScriptsToPackageJson(scripts) {
4621
+ let packageJson;
4622
+ try {
4623
+ packageJson = await fsExtra.readJSON("package.json");
4624
+ } catch (err) {
4625
+ throw new Error(
4626
+ `Failed to read package.json: ${err instanceof Error ? err.message : String(err)}`
4627
+ );
4628
+ }
4629
+ packageJson.scripts = {
4630
+ ...packageJson.scripts,
4631
+ ...scripts
4632
+ };
4633
+ logger.debug(`Adding scripts to package.json: ${JSON.stringify(scripts, null, 2)}`);
4634
+ try {
4635
+ logger.debug(
4636
+ `Writing to package.json.
4637
+ Contents: ${JSON.stringify(packageJson, null, 2)}`
4638
+ );
4639
+ await fsExtra.writeJSON("package.json", packageJson, { spaces: 4 });
4640
+ } catch (err) {
4641
+ throw new Error(
4642
+ `Failed to write to package.json: ${err instanceof Error ? err.message : String(err)}`
4643
+ );
4644
+ }
4645
+ }
4646
+ async function checkForFileInCurrentDirectory(fileName) {
4647
+ const file = path3.join(process.cwd(), fileName);
4648
+ let isFilePresent = false;
4649
+ try {
4650
+ await fsExtra.readJSON(file);
4651
+ isFilePresent = true;
4652
+ } catch {
4653
+ isFilePresent = false;
4654
+ }
4655
+ if (isFilePresent) {
4656
+ return;
4657
+ }
4658
+ logger.debug(`${fileName} not found in the current directory.`);
4659
+ throw new FileNotFoundError(`${fileName} not found in the current directory.`);
4660
+ }
4661
+ var FileNotFoundError = class extends Error {
4662
+ constructor(message) {
4663
+ super(message);
4664
+ this.name = "FileNotFoundError";
4665
+ }
4666
+ };
4667
+
4668
+ // src/app/cli/commands/create-app.ts
3937
4669
  async function createDextoProject(name) {
3938
4670
  const nameRegex = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
3939
4671
  let projectName;
3940
4672
  if (name) {
3941
4673
  if (!nameRegex.test(name)) {
3942
4674
  console.log(
3943
- chalk14.red(
4675
+ chalk18.red(
3944
4676
  "Invalid project name. Must start with a letter and contain only letters, numbers, hyphens or underscores."
3945
4677
  )
3946
4678
  );
@@ -3950,18 +4682,18 @@ async function createDextoProject(name) {
3950
4682
  } else {
3951
4683
  let input;
3952
4684
  do {
3953
- input = await p.text({
4685
+ input = await p3.text({
3954
4686
  message: "What do you want to name your Dexto project?",
3955
4687
  placeholder: "my-dexto-project",
3956
4688
  defaultValue: "my-dexto-project"
3957
4689
  });
3958
- if (p.isCancel(input)) {
3959
- p.cancel("Project creation cancelled");
4690
+ if (p3.isCancel(input)) {
4691
+ p3.cancel("Project creation cancelled");
3960
4692
  process.exit(0);
3961
4693
  }
3962
4694
  if (!nameRegex.test(input)) {
3963
4695
  console.log(
3964
- chalk14.red(
4696
+ chalk18.red(
3965
4697
  "Invalid project name. Must start with a letter and contain only letters, numbers, hyphens or underscores."
3966
4698
  )
3967
4699
  );
@@ -3969,31 +4701,31 @@ async function createDextoProject(name) {
3969
4701
  } while (!nameRegex.test(input));
3970
4702
  projectName = input;
3971
4703
  }
3972
- const spinner2 = p.spinner();
3973
- const projectPath = path.resolve(process.cwd(), projectName);
3974
- spinner2.start(`Creating dexto project in ${projectPath}...`);
4704
+ const spinner4 = p3.spinner();
4705
+ const projectPath = path4.resolve(process.cwd(), projectName);
4706
+ spinner4.start(`Creating dexto project in ${projectPath}...`);
3975
4707
  try {
3976
- await fs.mkdir(projectPath);
4708
+ await fs3.mkdir(projectPath);
3977
4709
  } catch (error) {
3978
4710
  if (error instanceof Error && "code" in error && error.code === "EEXIST") {
3979
- spinner2.stop(
4711
+ spinner4.stop(
3980
4712
  `Directory "${projectName}" already exists. Please choose a different name or delete the existing directory.`
3981
4713
  );
3982
4714
  process.exit(1);
3983
4715
  } else {
3984
- spinner2.stop(`Failed to create project: ${error}`);
4716
+ spinner4.stop(`Failed to create project: ${error}`);
3985
4717
  throw error;
3986
4718
  }
3987
4719
  }
3988
4720
  process.chdir(projectPath);
3989
4721
  await executeWithTimeout("npm", ["init", "-y"], { cwd: projectPath });
3990
4722
  await executeWithTimeout("git", ["init"], { cwd: projectPath });
3991
- await fs.writeFile(".gitignore", "node_modules\n.env\ndist\n.dexto\n*.log");
3992
- const packageJson = JSON.parse(await fs.readFile("package.json", "utf8"));
4723
+ await fs3.writeFile(".gitignore", "node_modules\n.env\ndist\n.dexto\n*.log");
4724
+ const packageJson = JSON.parse(await fs3.readFile("package.json", "utf8"));
3993
4725
  packageJson.type = "module";
3994
- await fs.writeFile("package.json", JSON.stringify(packageJson, null, 2));
3995
- spinner2.stop("Project files created successfully!");
3996
- spinner2.start("Installing dependencies...");
4726
+ await fs3.writeFile("package.json", JSON.stringify(packageJson, null, 2));
4727
+ spinner4.stop("Project files created successfully!");
4728
+ spinner4.start("Installing dependencies...");
3997
4729
  const packageManager = getPackageManager();
3998
4730
  const installCommand = getPackageManagerInstallCommand(packageManager);
3999
4731
  await executeWithTimeout(packageManager, [installCommand, "yaml", "dotenv"], {
@@ -4004,15 +4736,15 @@ async function createDextoProject(name) {
4004
4736
  [installCommand, "typescript", "tsx", "ts-node", "@types/node", "--save-dev"],
4005
4737
  { cwd: projectPath }
4006
4738
  );
4007
- spinner2.stop("Dependencies installed!");
4739
+ spinner4.stop("Dependencies installed!");
4008
4740
  return projectPath;
4009
4741
  }
4010
4742
  async function addDextoScriptsToPackageJson(directory, projectPath) {
4011
4743
  logger.debug(`Adding dexto scripts to package.json in ${projectPath}`);
4012
4744
  await addScriptsToPackageJson({
4013
4745
  build: "tsc",
4014
- start: `node dist/${path.join("dexto", "dexto-example.js")}`,
4015
- dev: `node --loader ts-node/esm ${path.join(directory, "dexto", "dexto-example.ts")}`
4746
+ start: `node dist/${path4.join("dexto", "dexto-example.js")}`,
4747
+ dev: `node --loader ts-node/esm ${path4.join(directory, "dexto", "dexto-example.ts")}`
4016
4748
  });
4017
4749
  logger.debug(`Successfully added dexto scripts to package.json in ${projectPath}`);
4018
4750
  }
@@ -4033,42 +4765,806 @@ async function createTsconfigJson(projectPath, directory) {
4033
4765
  include: [`${directory}/**/*.ts`],
4034
4766
  exclude: ["node_modules", "dist", ".dexto"]
4035
4767
  };
4036
- await fs.writeJSON(path.join(projectPath, "tsconfig.json"), tsconfig, { spaces: 4 });
4768
+ await fs3.writeJSON(path4.join(projectPath, "tsconfig.json"), tsconfig, { spaces: 4 });
4037
4769
  logger.debug(`Successfully created tsconfig.json in ${projectPath}`);
4038
4770
  }
4039
4771
  async function postCreateDexto(projectPath, directory) {
4040
4772
  const nextSteps = [
4041
- `1. Go to the project directory: ${chalk14.cyan(`cd ${projectPath}`)}`,
4042
- `2. Run the example: ${chalk14.cyan(`npm run dev`)}`,
4043
- `3. Add/update your API key(s) in ${chalk14.cyan(".env")}`,
4044
- `4. Check out the agent configuration file ${chalk14.cyan(path.join(directory, "dexto", "agents", "agent.yml"))}`,
4045
- `5. Try out different LLMs and MCP servers in the agent.yml file`,
4046
- `6. Read more about Dexto: ${chalk14.cyan("https://github.com/truffle-ai/dexto")}`
4773
+ `1. Go to the project directory: ${chalk18.cyan(`cd ${projectPath}`)}`,
4774
+ `2. Run the example: ${chalk18.cyan(`npm run dev`)}`,
4775
+ `3. Add/update your API key(s) in ${chalk18.cyan(".env")}`,
4776
+ `4. Check out the agent configuration file ${chalk18.cyan(path4.join(directory, "dexto", "agents", "default-agent.yml"))}`,
4777
+ `5. Try out different LLMs and MCP servers in the default-agent.yml file`,
4778
+ `6. Run dexto in your project directory to start the interactive CLI with default-agent.yml file`,
4779
+ `7. Read more about Dexto: ${chalk18.cyan("https://docs.dexto.ai")}`
4047
4780
  ].join("\n");
4048
- p.note(nextSteps, chalk14.yellow("Next steps:"));
4781
+ p3.note(nextSteps, chalk18.yellow("Next steps:"));
4049
4782
  }
4050
4783
 
4051
- // src/app/web.ts
4052
- import { spawn } from "child_process";
4784
+ // src/app/cli/commands/init-app.ts
4785
+ import * as p4 from "@clack/prompts";
4786
+ import chalk19 from "chalk";
4787
+ import fs5 from "node:fs/promises";
4788
+ import fsExtra2 from "fs-extra";
4789
+ import path5 from "node:path";
4790
+ import { createRequire } from "module";
4791
+
4792
+ // src/app/cli/utils/project-utils.ts
4793
+ import fs4 from "node:fs/promises";
4794
+ import { parseDocument } from "yaml";
4795
+ async function updateDextoConfigFile(filepath, llmProvider) {
4796
+ const fileContent = await fs4.readFile(filepath, "utf8");
4797
+ const doc = parseDocument(fileContent);
4798
+ doc.setIn(["llm", "provider"], llmProvider);
4799
+ doc.setIn(["llm", "apiKey"], `$${getPrimaryApiKeyEnvVar(llmProvider)}`);
4800
+ const defaultModel = getDefaultModelForProvider(llmProvider);
4801
+ if (defaultModel) {
4802
+ doc.setIn(["llm", "model"], defaultModel);
4803
+ }
4804
+ await fs4.writeFile(filepath, doc.toString(), "utf8");
4805
+ }
4806
+
4807
+ // src/app/cli/commands/init-app.ts
4808
+ var require2 = createRequire(import.meta.url);
4809
+ async function getUserInputToInitDextoApp() {
4810
+ const answers = await p4.group(
4811
+ {
4812
+ llmProvider: () => p4.select({
4813
+ message: "Choose your AI provider",
4814
+ options: PROVIDER_OPTIONS
4815
+ }),
4816
+ llmApiKey: async ({ results }) => {
4817
+ const llmProvider = results.llmProvider;
4818
+ const selection = await p4.select({
4819
+ message: `Enter your API key for ${getProviderDisplayName(llmProvider)}?`,
4820
+ options: [
4821
+ { value: "enter", label: "Enter", hint: "recommended" },
4822
+ { value: "skip", label: "Skip", hint: "" }
4823
+ ],
4824
+ initialValue: "enter"
4825
+ });
4826
+ if (p4.isCancel(selection)) {
4827
+ p4.cancel("Dexto initialization cancelled");
4828
+ process.exit(0);
4829
+ }
4830
+ if (selection === "enter") {
4831
+ const apiKey = await p4.password({
4832
+ message: `Enter your ${getProviderDisplayName(llmProvider)} API key`,
4833
+ mask: "*",
4834
+ validate: (value) => {
4835
+ if (!value || value.trim().length === 0) {
4836
+ return "API key is required";
4837
+ }
4838
+ if (!isValidApiKeyFormat(value.trim(), llmProvider)) {
4839
+ return `Invalid ${getProviderDisplayName(llmProvider)} API key format`;
4840
+ }
4841
+ return void 0;
4842
+ }
4843
+ });
4844
+ if (p4.isCancel(apiKey)) {
4845
+ p4.cancel("Dexto initialization cancelled");
4846
+ process.exit(0);
4847
+ }
4848
+ return apiKey;
4849
+ }
4850
+ return "";
4851
+ },
4852
+ directory: () => p4.text({
4853
+ message: "Enter the directory to add the dexto files in",
4854
+ placeholder: "src/",
4855
+ defaultValue: "src/"
4856
+ }),
4857
+ createExampleFile: () => p4.confirm({
4858
+ message: "Create a dexto example file? [Recommended]",
4859
+ initialValue: true
4860
+ })
4861
+ },
4862
+ {
4863
+ onCancel: () => {
4864
+ p4.cancel("Dexto initialization cancelled");
4865
+ process.exit(0);
4866
+ }
4867
+ }
4868
+ );
4869
+ return answers;
4870
+ }
4871
+ async function initDexto(directory, createExampleFile = true, llmProvider, llmApiKey) {
4872
+ const spinner4 = p4.spinner();
4873
+ try {
4874
+ const packageManager = getPackageManager();
4875
+ const installCommand = getPackageManagerInstallCommand(packageManager);
4876
+ spinner4.start("Installing Dexto...");
4877
+ const label = "latest";
4878
+ logger.debug(
4879
+ `Installing Dexto using ${packageManager} with install command: ${installCommand} and label: ${label}`
4880
+ );
4881
+ try {
4882
+ await executeWithTimeout(packageManager, [installCommand, `dexto@${label}`], {
4883
+ cwd: process.cwd()
4884
+ });
4885
+ } catch (installError) {
4886
+ logger.debug(`Install error: ${installError}`);
4887
+ if (packageManager === "pnpm" && "ERR_PNPM_ADDING_TO_ROOT") {
4888
+ spinner4.stop(chalk19.red("Error: Cannot install in pnpm workspace root"));
4889
+ p4.note(
4890
+ 'You are initializing dexto in a pnpm workspace root. Go to a specific package in the workspace and run "pnpm add dexto" instead.',
4891
+ chalk19.yellow("Workspace Error")
4892
+ );
4893
+ return { success: false };
4894
+ }
4895
+ throw installError;
4896
+ }
4897
+ spinner4.stop("Dexto installed successfully!");
4898
+ spinner4.start("Creating Dexto files...");
4899
+ const result = await createDextoDirectories(directory);
4900
+ if (!result.ok) {
4901
+ spinner4.stop(
4902
+ chalk19.inverse(
4903
+ `Dexto already initialized in ${path5.join(directory, "dexto")}. Would you like to overwrite it?`
4904
+ )
4905
+ );
4906
+ const overwrite = await p4.confirm({
4907
+ message: "Overwrite Dexto?",
4908
+ initialValue: false
4909
+ });
4910
+ if (p4.isCancel(overwrite) || !overwrite) {
4911
+ p4.cancel("Dexto initialization cancelled");
4912
+ return { success: false };
4913
+ }
4914
+ }
4915
+ logger.debug("Creating dexto config file...");
4916
+ const dextoDir = path5.join(directory, "dexto");
4917
+ const agentsDir = path5.join(dextoDir, "agents");
4918
+ let configPath;
4919
+ try {
4920
+ configPath = await createDextoConfigFile(agentsDir);
4921
+ logger.debug(`Dexto config file created at ${configPath}`);
4922
+ } catch (configError) {
4923
+ spinner4.stop(chalk19.red("Failed to create agent config file"));
4924
+ logger.error(`Config creation error: ${configError}`);
4925
+ throw new Error(
4926
+ `Failed to create default-agent.yml: ${configError instanceof Error ? configError.message : String(configError)}`
4927
+ );
4928
+ }
4929
+ if (llmProvider) {
4930
+ logger.debug(`Updating dexto config file based on llmProvider: ${llmProvider}`);
4931
+ await updateDextoConfigFile(configPath, llmProvider);
4932
+ logger.debug(`Dexto config file updated with llmProvider: ${llmProvider}`);
4933
+ }
4934
+ if (createExampleFile) {
4935
+ logger.debug("Creating dexto example file...");
4936
+ await createDextoExampleFile(dextoDir);
4937
+ logger.debug("Dexto example file created successfully!");
4938
+ }
4939
+ spinner4.start("Updating .env file with dexto env variables...");
4940
+ logger.debug(
4941
+ `Updating .env file with dexto env variables: directory ${directory}, llmProvider: ${llmProvider}, llmApiKey: [REDACTED]`
4942
+ );
4943
+ const envFilePath = path5.join(process.cwd(), ".env");
4944
+ await updateEnvFileWithLLMKeys(envFilePath, llmProvider, llmApiKey);
4945
+ spinner4.stop("Updated .env file with dexto env variables...");
4946
+ return { success: true };
4947
+ } catch (err) {
4948
+ spinner4.stop(chalk19.inverse("An error occurred initializing Dexto project"));
4949
+ logger.debug(`Error: ${err}`);
4950
+ return { success: false };
4951
+ }
4952
+ }
4953
+ async function postInitDexto(directory) {
4954
+ const nextSteps = [
4955
+ `1. Run the example: ${chalk19.cyan(`node --loader ts-node/esm ${path5.join(directory, "dexto", "dexto-example.ts")}`)}`,
4956
+ `2. Add/update your API key(s) in ${chalk19.cyan(".env")}`,
4957
+ `3. Check out the agent configuration file ${chalk19.cyan(path5.join(directory, "dexto", "agents", "default-agent.yml"))}`,
4958
+ `4. Try out different LLMs and MCP servers in the default-agent.yml file`,
4959
+ `5. Read more about Dexto: ${chalk19.cyan("https://github.com/truffle-ai/dexto")}`
4960
+ ].join("\n");
4961
+ p4.note(nextSteps, chalk19.yellow("Next steps:"));
4962
+ }
4963
+ async function createDextoDirectories(directory) {
4964
+ const dirPath = path5.join(directory, "dexto");
4965
+ const agentsPath = path5.join(directory, "dexto", "agents");
4966
+ try {
4967
+ await fs5.access(dirPath);
4968
+ return { ok: false };
4969
+ } catch {
4970
+ await fsExtra2.ensureDir(dirPath);
4971
+ await fsExtra2.ensureDir(agentsPath);
4972
+ return { ok: true, dirPath };
4973
+ }
4974
+ }
4975
+ async function createDextoConfigFile(directory) {
4976
+ await fsExtra2.ensureDir(directory);
4977
+ try {
4978
+ const pkgJsonPath = require2.resolve("dexto/package.json");
4979
+ const pkgDir = path5.dirname(pkgJsonPath);
4980
+ logger.debug(`Package directory: ${pkgDir}`);
4981
+ const templateConfigSrc = path5.join(pkgDir, "agents", "agent-template.yml");
4982
+ logger.debug(`Looking for template at: ${templateConfigSrc}`);
4983
+ const templateExists = await fsExtra2.pathExists(templateConfigSrc);
4984
+ if (!templateExists) {
4985
+ throw new Error(
4986
+ `Template file not found at: ${templateConfigSrc}. This indicates a build issue - the template should be included in the package.`
4987
+ );
4988
+ }
4989
+ const destConfigPath = path5.join(directory, "default-agent.yml");
4990
+ logger.debug(`Copying template to: ${destConfigPath}`);
4991
+ await fsExtra2.copy(templateConfigSrc, destConfigPath);
4992
+ logger.debug(`Successfully created config file at: ${destConfigPath}`);
4993
+ return destConfigPath;
4994
+ } catch (error) {
4995
+ logger.error(`Failed to create Dexto config file: ${error}`);
4996
+ throw error;
4997
+ }
4998
+ }
4999
+ async function createDextoExampleFile(directory) {
5000
+ const baseDir = path5.dirname(directory);
5001
+ const configPath = `./${path5.posix.join(baseDir, "dexto/agents/default-agent.yml")}`;
5002
+ const indexTsLines = [
5003
+ "import 'dotenv/config';",
5004
+ "import { DextoAgent, loadAgentConfig } from 'dexto';",
5005
+ "",
5006
+ "console.log('\u{1F680} Starting Dexto Basic Example\\n');",
5007
+ "",
5008
+ "try {",
5009
+ " // Load the agent configuration",
5010
+ ` const config = await loadAgentConfig('${configPath}');`,
5011
+ "",
5012
+ " // Create a new DextoAgent instance",
5013
+ " const agent = new DextoAgent(config);",
5014
+ "",
5015
+ " // Start the agent (connects to MCP servers)",
5016
+ " console.log('\u{1F517} Connecting to MCP servers...');",
5017
+ " await agent.start();",
5018
+ " console.log('\u2705 Agent started successfully!\\n');",
5019
+ "",
5020
+ " // Example 1: Simple task",
5021
+ " console.log('\u{1F4CB} Example 1: Simple information request');",
5022
+ " const request1 = 'What tools do you have available?';",
5023
+ " console.log('Request:', request1);",
5024
+ " const response1 = await agent.run(request1);",
5025
+ " console.log('Response:', response1);",
5026
+ " console.log('\\n\u2014\u2014\u2014\u2014\u2014\u2014\\n');",
5027
+ "",
5028
+ " // Example 2: File operation",
5029
+ " console.log('\u{1F4C4} Example 2: File creation');",
5030
+ ` const request2 = 'Create a file called test-output.txt with the content "Hello from Dexto!"';`,
5031
+ " console.log('Request:', request2);",
5032
+ " const response2 = await agent.run(request2);",
5033
+ " console.log('Response:', response2);",
5034
+ " console.log('\\n\u2014\u2014\u2014\u2014\u2014\u2014\\n');",
5035
+ "",
5036
+ " // Example 3: Multi-step conversation",
5037
+ " console.log('\u{1F5E3}\uFE0F Example 3: Multi-step conversation');",
5038
+ ` const request3a = 'Create a simple HTML file called demo.html with a heading that says "Dexto Demo"';`,
5039
+ " console.log('Request 3a:', request3a);",
5040
+ " const response3a = await agent.run(request3a);",
5041
+ " console.log('Response:', response3a);",
5042
+ " console.log('\\n\\n');",
5043
+ " const request3b = 'Now add a paragraph to that HTML file explaining what Dexto is';",
5044
+ " console.log('Request 3b:', request3b);",
5045
+ " const response3b = await agent.run(request3b);",
5046
+ " console.log('Response:', response3b);",
5047
+ " console.log('\\n\u2014\u2014\u2014\u2014\u2014\u2014\\n');",
5048
+ "",
5049
+ " // Reset conversation (clear context)",
5050
+ " console.log('\u{1F504} Resetting conversation context...');",
5051
+ " agent.resetConversation();",
5052
+ "",
5053
+ " // Example 4: Complex task",
5054
+ " console.log('\u{1F3D7}\uFE0F Example 4: Complex multi-tool task');",
5055
+ " const request4 = ",
5056
+ " 'Create a simple webpage about AI agents with HTML, CSS, and JavaScript. ' +",
5057
+ " 'The page should have a title, some content about what AI agents are, ' +",
5058
+ " 'and a button that shows an alert when clicked.';",
5059
+ " console.log('Request:', request4);",
5060
+ " const response4 = await agent.run(request4);",
5061
+ " console.log('Response:', response4);",
5062
+ " console.log('\\n\u2014\u2014\u2014\u2014\u2014\u2014\\n');",
5063
+ "",
5064
+ " // Stop the agent (disconnect from MCP servers)",
5065
+ " console.log('\\n\u{1F6D1} Stopping agent...');",
5066
+ " await agent.stop();",
5067
+ " console.log('\u2705 Agent stopped successfully!');",
5068
+ "",
5069
+ "} catch (error) {",
5070
+ " console.error('\u274C Error:', error);",
5071
+ "}",
5072
+ "",
5073
+ "console.log('\\n\u{1F4D6} Read Dexto documentation to understand more about using Dexto: https://docs.dexto.ai');"
5074
+ ];
5075
+ const indexTsContent = indexTsLines.join("\n");
5076
+ const outputPath = path5.join(directory, "dexto-example.ts");
5077
+ logger.debug(`Creating example file with config path: ${configPath}`);
5078
+ logger.debug(`Base directory: ${baseDir}, Output path: ${outputPath}`);
5079
+ logger.debug(`Generated file content:
5080
+ ${indexTsContent}`);
5081
+ await fs5.writeFile(outputPath, indexTsContent);
5082
+ return outputPath;
5083
+ }
5084
+
5085
+ // src/app/cli/commands/setup.ts
5086
+ import chalk20 from "chalk";
5087
+ import { z as z4 } from "zod";
5088
+
5089
+ // src/app/cli/utils/setup-utils.ts
5090
+ function isFirstTimeUser() {
5091
+ return !globalPreferencesExist();
5092
+ }
5093
+ async function requiresSetup() {
5094
+ if (getExecutionContext() !== "global-cli") {
5095
+ return false;
5096
+ }
5097
+ if (isFirstTimeUser()) {
5098
+ return true;
5099
+ }
5100
+ try {
5101
+ const preferences = await loadGlobalPreferences();
5102
+ if (!preferences.setup.completed) {
5103
+ return true;
5104
+ }
5105
+ if (!preferences.defaults.defaultAgent) {
5106
+ return true;
5107
+ }
5108
+ return false;
5109
+ } catch (_error) {
5110
+ return true;
5111
+ }
5112
+ }
5113
+
5114
+ // src/app/cli/commands/setup.ts
5115
+ import * as p5 from "@clack/prompts";
5116
+ var SetupCommandSchema = z4.object({
5117
+ provider: z4.enum(LLM_PROVIDERS).optional().describe("AI provider identifier to use for LLM calls"),
5118
+ model: z4.string().min(1, "Model name cannot be empty").optional().describe("Preferred model name for the selected provider"),
5119
+ defaultAgent: z4.string().min(1, "Default agent name cannot be empty").default("default-agent").describe("Registry agent id to use when none is specified"),
5120
+ interactive: z4.boolean().default(true).describe("Enable interactive prompts"),
5121
+ force: z4.boolean().default(false).describe("Overwrite existing setup when already configured")
5122
+ }).strict().superRefine((data, ctx) => {
5123
+ if (data.provider && data.model) {
5124
+ if (!isValidProviderModel(data.provider, data.model)) {
5125
+ const supportedModels = getSupportedModels(data.provider);
5126
+ ctx.addIssue({
5127
+ code: z4.ZodIssueCode.custom,
5128
+ path: ["model"],
5129
+ message: `Model '${data.model}' is not supported by provider '${data.provider}'. Supported models: ${supportedModels.join(", ")}`
5130
+ });
5131
+ }
5132
+ }
5133
+ });
5134
+ function validateSetupCommand(options) {
5135
+ const validated = SetupCommandSchema.parse(options);
5136
+ if (!validated.interactive && !validated.provider) {
5137
+ throw new Error("Provider required in non-interactive mode. Use --provider option.");
5138
+ }
5139
+ return validated;
5140
+ }
5141
+ async function handleSetupCommand(options) {
5142
+ const validated = validateSetupCommand(options);
5143
+ logger.debug(`Validated setup command options: ${JSON.stringify(validated, null, 2)}`);
5144
+ const needsSetup = await requiresSetup();
5145
+ if (!needsSetup) {
5146
+ if (!validated.interactive) {
5147
+ if (!validated.force) {
5148
+ console.error(chalk20.red("\u274C Setup is already complete."));
5149
+ console.error(
5150
+ chalk20.dim(
5151
+ " Use --force to overwrite existing setup, or run in interactive mode for confirmation."
5152
+ )
5153
+ );
5154
+ process.exit(1);
5155
+ }
5156
+ logger.warn("Overwriting existing setup due to --force flag");
5157
+ } else {
5158
+ p5.intro(chalk20.yellow("\u26A0\uFE0F Setup Already Complete"));
5159
+ p5.note(
5160
+ "Dexto is already set up and configured.\nRe-running setup will overwrite your current preferences.",
5161
+ "Current Setup Detected"
5162
+ );
5163
+ const shouldContinue = await p5.confirm({
5164
+ message: "Do you want to continue and overwrite your current setup?",
5165
+ initialValue: false
5166
+ });
5167
+ if (p5.isCancel(shouldContinue) || !shouldContinue) {
5168
+ p5.cancel("Setup cancelled. Your existing configuration remains unchanged.");
5169
+ process.exit(0);
5170
+ }
5171
+ p5.log.warn("Proceeding with setup override...");
5172
+ }
5173
+ }
5174
+ console.log(chalk20.cyan("\n\u{1F5FF} Setting up Dexto...\n"));
5175
+ let provider = validated.provider;
5176
+ if (!provider) {
5177
+ provider = await selectProvider();
5178
+ }
5179
+ const model = validated.model || getDefaultModelForProvider(provider);
5180
+ if (!model) {
5181
+ throw new Error(`Provider '${provider}' requires a specific model. Use --model option.`);
5182
+ }
5183
+ const apiKeyVar = getPrimaryApiKeyEnvVar(provider);
5184
+ const defaultAgent = validated.defaultAgent;
5185
+ const preferences = createInitialPreferences(provider, model, apiKeyVar, defaultAgent);
5186
+ await saveGlobalPreferences(preferences);
5187
+ if (validated.interactive) {
5188
+ const existingApiKey = resolveApiKeyForProvider(provider);
5189
+ if (existingApiKey) {
5190
+ p5.outro(chalk20.green(`\u2705 API key for ${provider} already configured`));
5191
+ } else {
5192
+ p5.outro(chalk20.cyan(`API key not found for ${provider}, starting api key setup...`));
5193
+ await interactiveApiKeySetup(provider);
5194
+ }
5195
+ }
5196
+ console.log(chalk20.green("\n\u2728 Setup complete! Dexto is ready to use.\n"));
5197
+ }
5198
+
5199
+ // src/app/cli/commands/install.ts
4053
5200
  import { existsSync } from "fs";
4054
- import path2 from "path";
5201
+ import path6 from "path";
5202
+ import { z as z5 } from "zod";
5203
+ var InstallCommandSchema = z5.object({
5204
+ agents: z5.array(z5.string().min(1, "Agent name cannot be empty")),
5205
+ all: z5.boolean().default(false),
5206
+ injectPreferences: z5.boolean().default(true),
5207
+ force: z5.boolean().default(false)
5208
+ }).strict();
5209
+ function validateInstallCommand(agents, options) {
5210
+ const registry = getAgentRegistry();
5211
+ const validated = InstallCommandSchema.parse({
5212
+ ...options,
5213
+ agents
5214
+ });
5215
+ const availableAgents = registry.getAvailableAgents();
5216
+ if (!validated.all && validated.agents.length === 0) {
5217
+ throw new Error(
5218
+ `No agents specified. Use agent names or --all flag. Available agents: ${Object.keys(availableAgents).join(", ")}`
5219
+ );
5220
+ }
5221
+ if (!validated.all) {
5222
+ const invalidAgents = validated.agents.filter((agent) => !registry.hasAgent(agent));
5223
+ if (invalidAgents.length > 0) {
5224
+ throw new Error(
5225
+ `Unknown agents: ${invalidAgents.join(", ")}. Available agents: ${Object.keys(availableAgents).join(", ")}`
5226
+ );
5227
+ }
5228
+ }
5229
+ return validated;
5230
+ }
5231
+ async function handleInstallCommand(agents, options) {
5232
+ const validated = validateInstallCommand(agents, options);
5233
+ const registry = getAgentRegistry();
5234
+ let agentsToInstall;
5235
+ if (validated.all) {
5236
+ agentsToInstall = Object.keys(registry.getAvailableAgents());
5237
+ console.log(`\u{1F4CB} Installing all ${agentsToInstall.length} available agents...`);
5238
+ } else {
5239
+ agentsToInstall = validated.agents;
5240
+ }
5241
+ console.log(`\u{1F680} Installing ${agentsToInstall.length} agents...`);
5242
+ let successCount = 0;
5243
+ let errorCount = 0;
5244
+ const errors = [];
5245
+ for (const agentName of agentsToInstall) {
5246
+ try {
5247
+ console.log(`
5248
+ \u{1F4E6} Installing ${agentName}...`);
5249
+ const globalAgentsDir = getDextoGlobalPath("agents");
5250
+ const installedPath = path6.join(globalAgentsDir, agentName);
5251
+ if (existsSync(installedPath) && !validated.force) {
5252
+ console.log(`\u23ED\uFE0F ${agentName} already installed (use --force to reinstall)`);
5253
+ successCount++;
5254
+ continue;
5255
+ }
5256
+ await registry.installAgent(agentName, validated.injectPreferences);
5257
+ successCount++;
5258
+ console.log(`\u2705 ${agentName} installed successfully`);
5259
+ } catch (error) {
5260
+ errorCount++;
5261
+ const errorMsg = `Failed to install ${agentName}: ${error instanceof Error ? error.message : String(error)}`;
5262
+ errors.push(errorMsg);
5263
+ console.error(`\u274C ${errorMsg}`);
5264
+ }
5265
+ }
5266
+ if (agentsToInstall.length === 1) {
5267
+ if (errorCount > 0) {
5268
+ throw new Error(errors[0]);
5269
+ }
5270
+ return;
5271
+ }
5272
+ console.log(`
5273
+ \u{1F4CA} Installation Summary:`);
5274
+ console.log(`\u2705 Successfully installed: ${successCount}`);
5275
+ if (errorCount > 0) {
5276
+ console.log(`\u274C Failed to install: ${errorCount}`);
5277
+ errors.forEach((error) => console.log(` \u2022 ${error}`));
5278
+ }
5279
+ if (errorCount > 0 && successCount === 0) {
5280
+ throw new Error("All installations failed");
5281
+ } else if (errorCount > 0) {
5282
+ console.log(`\u26A0\uFE0F Some installations failed, but ${successCount} succeeded.`);
5283
+ } else {
5284
+ console.log(`\u{1F389} All agents installed successfully!`);
5285
+ }
5286
+ }
5287
+
5288
+ // src/app/cli/commands/uninstall.ts
5289
+ import { z as z6 } from "zod";
5290
+ var UninstallCommandSchema = z6.object({
5291
+ agents: z6.array(z6.string().min(1, "Agent name cannot be empty")),
5292
+ all: z6.boolean().default(false),
5293
+ force: z6.boolean().default(false)
5294
+ }).strict();
5295
+ async function validateUninstallCommand(agents, options) {
5296
+ const validated = UninstallCommandSchema.parse({
5297
+ ...options,
5298
+ agents
5299
+ });
5300
+ const registry = getAgentRegistry();
5301
+ const installedAgents = await registry.getInstalledAgents();
5302
+ if (installedAgents.length === 0) {
5303
+ throw new Error("No agents are currently installed.");
5304
+ }
5305
+ if (!validated.all && validated.agents.length === 0) {
5306
+ throw new Error(
5307
+ `No agents specified. Use agent names or --all flag. Installed agents: ${installedAgents.join(", ")}`
5308
+ );
5309
+ }
5310
+ return validated;
5311
+ }
5312
+ async function handleUninstallCommand(agents, options) {
5313
+ const validated = await validateUninstallCommand(agents, options);
5314
+ const registry = getAgentRegistry();
5315
+ const installedAgents = await registry.getInstalledAgents();
5316
+ if (installedAgents.length === 0) {
5317
+ console.log("\u{1F4CB} No agents are currently installed.");
5318
+ return;
5319
+ }
5320
+ let agentsToUninstall;
5321
+ if (validated.all) {
5322
+ agentsToUninstall = installedAgents;
5323
+ console.log(`\u{1F4CB} Uninstalling all ${agentsToUninstall.length} installed agents...`);
5324
+ } else {
5325
+ agentsToUninstall = validated.agents;
5326
+ const notInstalled = agentsToUninstall.filter((agent) => !installedAgents.includes(agent));
5327
+ if (notInstalled.length > 0) {
5328
+ throw new Error(
5329
+ `Agents not installed: ${notInstalled.join(", ")}. Installed agents: ${installedAgents.join(", ")}`
5330
+ );
5331
+ }
5332
+ }
5333
+ console.log(`\u{1F5D1}\uFE0F Uninstalling ${agentsToUninstall.length} agents...`);
5334
+ let successCount = 0;
5335
+ let errorCount = 0;
5336
+ const errors = [];
5337
+ for (const agentName of agentsToUninstall) {
5338
+ try {
5339
+ console.log(`
5340
+ \u{1F5D1}\uFE0F Uninstalling ${agentName}...`);
5341
+ await registry.uninstallAgent(agentName, validated.force);
5342
+ successCount++;
5343
+ console.log(`\u2705 ${agentName} uninstalled successfully`);
5344
+ } catch (error) {
5345
+ errorCount++;
5346
+ const errorMsg = `Failed to uninstall ${agentName}: ${error instanceof Error ? error.message : String(error)}`;
5347
+ errors.push(errorMsg);
5348
+ console.error(`\u274C ${errorMsg}`);
5349
+ }
5350
+ }
5351
+ if (agentsToUninstall.length === 1) {
5352
+ if (errorCount > 0) {
5353
+ throw new Error(errors[0]);
5354
+ }
5355
+ return;
5356
+ }
5357
+ console.log(`
5358
+ \u{1F4CA} Uninstallation Summary:`);
5359
+ console.log(`\u2705 Successfully uninstalled: ${successCount}`);
5360
+ if (errorCount > 0) {
5361
+ console.log(`\u274C Failed to uninstall: ${errorCount}`);
5362
+ errors.forEach((error) => console.log(` \u2022 ${error}`));
5363
+ }
5364
+ if (errorCount > 0 && successCount === 0) {
5365
+ throw new Error("All uninstallations failed");
5366
+ } else if (errorCount > 0) {
5367
+ console.log(`\u26A0\uFE0F Some uninstallations failed, but ${successCount} succeeded.`);
5368
+ } else {
5369
+ console.log(`\u{1F389} All agents uninstalled successfully!`);
5370
+ }
5371
+ }
5372
+
5373
+ // src/app/cli/commands/list-agents.ts
5374
+ import { existsSync as existsSync2 } from "fs";
5375
+ import { promises as fs6 } from "fs";
5376
+ import path7 from "path";
5377
+ import chalk21 from "chalk";
5378
+ import { z as z7 } from "zod";
5379
+ var ListAgentsCommandSchema = z7.object({
5380
+ verbose: z7.boolean().default(false),
5381
+ installed: z7.boolean().default(false),
5382
+ available: z7.boolean().default(false)
5383
+ }).strict();
5384
+ async function getInstalledAgents() {
5385
+ const globalAgentsDir = getDextoGlobalPath("agents");
5386
+ if (!existsSync2(globalAgentsDir)) {
5387
+ return [];
5388
+ }
5389
+ const registry = getAgentRegistry();
5390
+ const installedAgents = [];
5391
+ try {
5392
+ const entries = await fs6.readdir(globalAgentsDir, { withFileTypes: true });
5393
+ for (const entry of entries) {
5394
+ if (entry.isDirectory() || entry.name.endsWith(".yml")) {
5395
+ const agentName = entry.isDirectory() ? entry.name : path7.basename(entry.name, ".yml");
5396
+ const agentPath = path7.join(globalAgentsDir, entry.name);
5397
+ try {
5398
+ const mainConfigPath = entry.isDirectory() ? registry.resolveMainConfig(agentPath, agentName) : agentPath;
5399
+ const stats = await fs6.stat(agentPath);
5400
+ let llmProvider;
5401
+ let llmModel;
5402
+ if (existsSync2(mainConfigPath)) {
5403
+ try {
5404
+ const configContent = await fs6.readFile(mainConfigPath, "utf-8");
5405
+ const configMatch = configContent.match(/provider:\s*([^\n\r]+)/);
5406
+ const modelMatch = configContent.match(/model:\s*([^\n\r]+)/);
5407
+ llmProvider = configMatch?.[1]?.trim();
5408
+ llmModel = modelMatch?.[1]?.trim();
5409
+ } catch (_error) {
5410
+ }
5411
+ }
5412
+ const registryData = registry.getAvailableAgents()[agentName];
5413
+ const description = registryData?.description || "Custom agent";
5414
+ const agentInfo = {
5415
+ name: agentName,
5416
+ description,
5417
+ path: mainConfigPath,
5418
+ installedAt: stats.birthtime || stats.mtime
5419
+ };
5420
+ if (llmProvider) agentInfo.llmProvider = llmProvider;
5421
+ if (llmModel) agentInfo.llmModel = llmModel;
5422
+ installedAgents.push(agentInfo);
5423
+ } catch (error) {
5424
+ console.warn(`Warning: Could not process agent '${agentName}': ${error}`);
5425
+ }
5426
+ }
5427
+ }
5428
+ } catch (_error) {
5429
+ return [];
5430
+ }
5431
+ return installedAgents.sort((a, b) => a.name.localeCompare(b.name));
5432
+ }
5433
+ function getAvailableAgents() {
5434
+ const registry = getAgentRegistry();
5435
+ const availableAgents = registry.getAvailableAgents();
5436
+ return Object.entries(availableAgents).map(([name, data]) => ({
5437
+ name,
5438
+ description: data.description,
5439
+ author: data.author,
5440
+ tags: data.tags
5441
+ })).sort((a, b) => a.name.localeCompare(b.name));
5442
+ }
5443
+ async function handleListAgentsCommand(options) {
5444
+ const validated = ListAgentsCommandSchema.parse(options);
5445
+ console.log(chalk21.cyan("\n\u{1F4CB} Dexto Agents\n"));
5446
+ let globalLLM;
5447
+ if (globalPreferencesExist()) {
5448
+ try {
5449
+ const preferences = await loadGlobalPreferences();
5450
+ globalLLM = `${preferences.llm.provider}/${preferences.llm.model}`;
5451
+ } catch {
5452
+ }
5453
+ }
5454
+ const installedAgents = await getInstalledAgents();
5455
+ const availableAgents = getAvailableAgents();
5456
+ const showInstalled = !validated.available || validated.installed;
5457
+ const showAvailable = !validated.installed || validated.available;
5458
+ if (showInstalled && installedAgents.length > 0) {
5459
+ console.log(chalk21.green("\u2705 Installed Agents:"));
5460
+ for (const agent of installedAgents) {
5461
+ const llmInfo = agent.llmProvider && agent.llmModel ? `${agent.llmProvider}/${agent.llmModel}` : globalLLM || "Unknown LLM";
5462
+ const llmDisplay = chalk21.gray(`(${llmInfo})`);
5463
+ if (validated.verbose) {
5464
+ console.log(` ${chalk21.bold(agent.name)} ${llmDisplay}`);
5465
+ console.log(` ${chalk21.gray(agent.description)}`);
5466
+ console.log(` ${chalk21.gray("Path:")} ${agent.path}`);
5467
+ if (agent.installedAt) {
5468
+ console.log(
5469
+ ` ${chalk21.gray("Installed:")} ${agent.installedAt.toLocaleDateString()}`
5470
+ );
5471
+ }
5472
+ console.log();
5473
+ } else {
5474
+ console.log(` \u2022 ${chalk21.bold(agent.name)} ${llmDisplay} - ${agent.description}`);
5475
+ }
5476
+ }
5477
+ console.log();
5478
+ } else if (showInstalled) {
5479
+ console.log(chalk21.yellow("\u{1F4E6} No agents installed yet."));
5480
+ console.log(
5481
+ chalk21.gray(" Use `dexto install <agent-name>` to install agents from the registry.\n")
5482
+ );
5483
+ }
5484
+ if (showAvailable) {
5485
+ const availableNotInstalled = availableAgents.filter(
5486
+ (available) => !installedAgents.some((installed) => installed.name === available.name)
5487
+ );
5488
+ if (availableNotInstalled.length > 0) {
5489
+ console.log(chalk21.blue("\u{1F4CB} Available to Install:"));
5490
+ for (const agent of availableNotInstalled) {
5491
+ if (validated.verbose) {
5492
+ console.log(` ${chalk21.bold(agent.name)}`);
5493
+ console.log(` ${chalk21.gray(agent.description)}`);
5494
+ console.log(` ${chalk21.gray("Author:")} ${agent.author}`);
5495
+ console.log(` ${chalk21.gray("Tags:")} ${agent.tags.join(", ")}`);
5496
+ console.log();
5497
+ } else {
5498
+ console.log(` \u2022 ${chalk21.bold(agent.name)} - ${agent.description}`);
5499
+ }
5500
+ }
5501
+ console.log();
5502
+ }
5503
+ }
5504
+ const totalInstalled = installedAgents.length;
5505
+ const availableToInstall = availableAgents.filter(
5506
+ (a) => !installedAgents.some((i) => i.name === a.name)
5507
+ ).length;
5508
+ if (!validated.verbose) {
5509
+ console.log(
5510
+ chalk21.gray(
5511
+ `\u{1F4CA} Summary: ${totalInstalled} installed, ${availableToInstall} available to install`
5512
+ )
5513
+ );
5514
+ if (availableToInstall > 0) {
5515
+ console.log(
5516
+ chalk21.gray(` Use \`dexto install <agent-name>\` to install more agents.`)
5517
+ );
5518
+ }
5519
+ console.log(chalk21.gray(` Use \`dexto list-agents --verbose\` for detailed information.`));
5520
+ }
5521
+ console.log();
5522
+ }
5523
+
5524
+ // src/app/cli/commands/which.ts
5525
+ import chalk22 from "chalk";
5526
+ import { z as z8 } from "zod";
5527
+ var WhichCommandSchema = z8.object({
5528
+ agentName: z8.string().min(1, "Agent name cannot be empty")
5529
+ }).strict();
5530
+ async function handleWhichCommand(agentName) {
5531
+ const validated = WhichCommandSchema.parse({ agentName });
5532
+ const registry = getAgentRegistry();
5533
+ const availableAgents = Object.keys(registry.getAvailableAgents());
5534
+ try {
5535
+ const resolvedPath = await resolveAgentPath(validated.agentName, false, false);
5536
+ console.log(resolvedPath);
5537
+ } catch (error) {
5538
+ console.error(
5539
+ chalk22.red(
5540
+ `\u274C dexto which command failed: ${error instanceof Error ? error.message : String(error)}. Available agents: ${availableAgents.join(", ")}`
5541
+ )
5542
+ );
5543
+ process.exit(1);
5544
+ }
5545
+ }
5546
+
5547
+ // src/app/web.ts
5548
+ import { spawn as spawn2 } from "child_process";
5549
+ import { existsSync as existsSync3 } from "fs";
5550
+ import path8 from "path";
4055
5551
  import { fileURLToPath } from "url";
4056
5552
  async function startNextJsWebServer(apiUrl, frontPort = 3e3, frontUrl = `http://localhost:${frontPort}`) {
4057
- const scriptDir = path2.dirname(fileURLToPath(import.meta.url));
5553
+ const scriptDir = path8.dirname(fileURLToPath(import.meta.url));
4058
5554
  logger.debug(`Script directory for web mode: ${scriptDir}`);
4059
- let webuiPath = path2.resolve(scriptDir, "webui");
4060
- if (!existsSync(webuiPath)) {
4061
- const srcPath = path2.resolve(scriptDir, "..", "..", "src", "app", "webui");
4062
- if (existsSync(srcPath)) {
5555
+ let webuiPath = path8.resolve(scriptDir, "webui");
5556
+ if (!existsSync3(webuiPath)) {
5557
+ const srcPath = path8.resolve(scriptDir, "..", "..", "src", "app", "webui");
5558
+ if (existsSync3(srcPath)) {
4063
5559
  return startDevServer(apiUrl, frontPort, frontUrl, srcPath);
4064
5560
  } else {
4065
5561
  logger.warn("Could not locate webui directory. Web UI may not be available.");
4066
5562
  return false;
4067
5563
  }
4068
5564
  }
4069
- const standaloneServerPath = path2.join(webuiPath, ".next", "standalone", "server.js");
4070
- const serverScriptPath = path2.join(webuiPath, "server.js");
4071
- if (!existsSync(standaloneServerPath) && !existsSync(serverScriptPath)) {
5565
+ const standaloneServerPath = path8.join(webuiPath, ".next", "standalone", "server.js");
5566
+ const serverScriptPath = path8.join(webuiPath, "server.js");
5567
+ if (!existsSync3(standaloneServerPath) && !existsSync3(serverScriptPath)) {
4072
5568
  logger.warn(
4073
5569
  "Built WebUI not found. This may indicate the package was not built correctly.",
4074
5570
  null,
@@ -4088,8 +5584,8 @@ async function startNextJsWebServer(apiUrl, frontPort = 3e3, frontUrl = `http://
4088
5584
  }
4089
5585
  })();
4090
5586
  logger.info(`Starting Next.js production server on ${frontUrl}`, null, "cyanBright");
4091
- const serverToUse = existsSync(serverScriptPath) ? serverScriptPath : standaloneServerPath;
4092
- const nextProc = spawn("node", [serverToUse], {
5587
+ const serverToUse = existsSync3(serverScriptPath) ? serverScriptPath : standaloneServerPath;
5588
+ const nextProc = spawn2("node", [serverToUse], {
4093
5589
  cwd: webuiPath,
4094
5590
  stdio: ["inherit", "pipe", "inherit"],
4095
5591
  env: {
@@ -4162,11 +5658,11 @@ async function startDevServer(apiUrl, frontPort, frontUrl, webuiPath) {
4162
5658
  return "3001";
4163
5659
  }
4164
5660
  })();
4165
- const nodeModulesPath = path2.join(webuiPath, "node_modules");
4166
- const needsInstall = !existsSync(nodeModulesPath);
5661
+ const nodeModulesPath = path8.join(webuiPath, "node_modules");
5662
+ const needsInstall = !existsSync3(nodeModulesPath);
4167
5663
  if (needsInstall) {
4168
5664
  logger.info("Installing Next.js dependencies...", null, "cyanBright");
4169
- const installProc = spawn("npm", ["install"], {
5665
+ const installProc = spawn2("npm", ["install"], {
4170
5666
  cwd: webuiPath,
4171
5667
  stdio: "inherit"
4172
5668
  });
@@ -4180,7 +5676,7 @@ async function startDevServer(apiUrl, frontPort, frontUrl, webuiPath) {
4180
5676
  }
4181
5677
  }
4182
5678
  logger.info(`Starting Next.js dev server on ${frontUrl}`, null, "cyanBright");
4183
- const nextProc = spawn("npm", ["run", "dev", "--", "--port", String(frontPort)], {
5679
+ const nextProc = spawn2("npm", ["run", "dev", "--", "--port", String(frontPort)], {
4184
5680
  cwd: webuiPath,
4185
5681
  stdio: ["inherit", "pipe", "inherit"],
4186
5682
  env: {
@@ -4322,16 +5818,19 @@ async function initializeMcpToolAggregationServer(serverConfigs, mcpTransport, s
4322
5818
  }
4323
5819
 
4324
5820
  // src/app/index.ts
4325
- dotenv3.config();
5821
+ await applyLayeredEnvironmentLoading();
4326
5822
  var program = new Command();
4327
- program.name("dexto").description("AI-powered CLI and WebUI for interacting with MCP servers").version(package_default.version, "-v, --version", "output the current version").option("-a, --agent <path>", "Path to agent config file", DEFAULT_CONFIG_PATH).option("-s, --strict", "Require all server connections to succeed").option("--no-verbose", "Disable verbose output").option("-m, --model <model>", "Specify the LLM model to use. ").option("-r, --router <router>", "Specify the LLM router to use (vercel or in-built)").option("--new-session [sessionId]", "Start with a new session (optionally specify session ID)").option(
5823
+ program.name("dexto").description("AI-powered CLI and WebUI for interacting with MCP servers").version(package_default.version, "-v, --version", "output the current version").option("-a, --agent <name|path>", "Agent name or path to agent config file").option(
5824
+ "-p, --prompt <text>",
5825
+ "One-shot prompt text. Alternatively provide a single quoted string as positional argument."
5826
+ ).option("-s, --strict", "Require all server connections to succeed").option("--no-verbose", "Disable verbose output").option("--no-interactive", "Disable interactive prompts and API key setup").option("-m, --model <model>", "Specify the LLM model to use").option("-r, --router <router>", "Specify the LLM router to use (vercel or in-built)").option("--new-session [sessionId]", "Start with a new session (optionally specify session ID)").option(
4328
5827
  "--mode <mode>",
4329
5828
  "The application in which dexto should talk to you - cli | web | server | discord | telegram | mcp",
4330
5829
  "cli"
4331
- ).option("--web-port <port>", "optional port for the web UI", "3000");
5830
+ ).option("--web-port <port>", "optional port for the web UI", "3000").option("--no-auto-install", "Disable automatic installation of missing agents from registry").enablePositionalOptions();
4332
5831
  program.command("create-app").description("Scaffold a new Dexto Typescript app").action(async () => {
4333
5832
  try {
4334
- p2.intro(chalk15.inverse("Dexto Create App"));
5833
+ p6.intro(chalk23.inverse("Dexto Create App"));
4335
5834
  const appPath = await createDextoProject();
4336
5835
  const userInput = await getUserInputToInitDextoApp();
4337
5836
  process.chdir(appPath);
@@ -4343,7 +5842,7 @@ program.command("create-app").description("Scaffold a new Dexto Typescript app")
4343
5842
  userInput.llmProvider,
4344
5843
  userInput.llmApiKey
4345
5844
  );
4346
- p2.outro(chalk15.greenBright("Dexto app created and initialized successfully!"));
5845
+ p6.outro(chalk23.greenBright("Dexto app created and initialized successfully!"));
4347
5846
  await postCreateDexto(appPath, userInput.directory);
4348
5847
  process.exit(0);
4349
5848
  } catch (err) {
@@ -4355,7 +5854,7 @@ program.command("init-app").description("Initialize an existing Typescript app w
4355
5854
  try {
4356
5855
  await checkForFileInCurrentDirectory("package.json");
4357
5856
  await checkForFileInCurrentDirectory("tsconfig.json");
4358
- p2.intro(chalk15.inverse("Dexto Init App"));
5857
+ p6.intro(chalk23.inverse("Dexto Init App"));
4359
5858
  const userInput = await getUserInputToInitDextoApp();
4360
5859
  await initDexto(
4361
5860
  userInput.directory,
@@ -4363,7 +5862,7 @@ program.command("init-app").description("Initialize an existing Typescript app w
4363
5862
  userInput.llmProvider,
4364
5863
  userInput.llmApiKey
4365
5864
  );
4366
- p2.outro(chalk15.greenBright("Dexto app initialized successfully!"));
5865
+ p6.outro(chalk23.greenBright("Dexto app initialized successfully!"));
4367
5866
  await postInitDexto(userInput.directory);
4368
5867
  process.exit(0);
4369
5868
  } catch (err) {
@@ -4375,6 +5874,53 @@ program.command("init-app").description("Initialize an existing Typescript app w
4375
5874
  process.exit(1);
4376
5875
  }
4377
5876
  });
5877
+ program.command("setup").description("Configure global Dexto preferences").option("--provider <provider>", "LLM provider (openai, anthropic, google, groq)").option("--model <model>", "Model name (uses provider default if not specified)").option("--default-agent <agent>", "Default agent name (default: default-agent)").option("--no-interactive", "Skip interactive prompts and API key setup").option("--force", "Overwrite existing setup without confirmation").action(async (options) => {
5878
+ try {
5879
+ await handleSetupCommand(options);
5880
+ process.exit(0);
5881
+ } catch (err) {
5882
+ console.error(
5883
+ `\u274C dexto setup command failed: ${err}. Check logs in ~/.dexto/logs/dexto.log for more information`
5884
+ );
5885
+ process.exit(1);
5886
+ }
5887
+ });
5888
+ program.command("install [agents...]").description("Install agents from the registry").option("--all", "Install all available agents from registry").option("--no-inject-preferences", "Skip injecting global preferences into installed agents").option("--force", "Force reinstall even if agent is already installed").action(async (agents = [], options) => {
5889
+ try {
5890
+ await handleInstallCommand(agents, options);
5891
+ process.exit(0);
5892
+ } catch (err) {
5893
+ console.error(`\u274C dexto install command failed: ${err}`);
5894
+ process.exit(1);
5895
+ }
5896
+ });
5897
+ program.command("uninstall [agents...]").description("Uninstall agents from the local installation").option("--all", "Uninstall all installed agents").option("--force", "Force uninstall even if agent is protected (e.g., default-agent)").action(async (agents, options) => {
5898
+ try {
5899
+ await handleUninstallCommand(agents, options);
5900
+ process.exit(0);
5901
+ } catch (err) {
5902
+ console.error(`\u274C dexto uninstall command failed: ${err}`);
5903
+ process.exit(1);
5904
+ }
5905
+ });
5906
+ program.command("list-agents").description("List available and installed agents").option("--verbose", "Show detailed agent information").option("--installed", "Show only installed agents").option("--available", "Show only available agents").action(async (options) => {
5907
+ try {
5908
+ await handleListAgentsCommand(options);
5909
+ process.exit(0);
5910
+ } catch (err) {
5911
+ console.error(`\u274C dexto list-agents command failed: ${err}`);
5912
+ process.exit(1);
5913
+ }
5914
+ });
5915
+ program.command("which <agent>").description("Show the path to an agent").action(async (agent) => {
5916
+ try {
5917
+ await handleWhichCommand(agent);
5918
+ process.exit(0);
5919
+ } catch (err) {
5920
+ console.error(`\u274C dexto which command failed: ${err}`);
5921
+ process.exit(1);
5922
+ }
5923
+ });
4378
5924
  program.command("mcp").description(
4379
5925
  "Start Dexto as an MCP server. Use --group-servers to aggregate and re-expose tools from configured MCP servers. In the future, this command will expose the agent as an MCP server by default."
4380
5926
  ).option("-s, --strict", "Require all MCP server connections to succeed").option(
@@ -4390,9 +5936,14 @@ program.command("mcp").description(
4390
5936
  process.exit(1);
4391
5937
  }
4392
5938
  const globalOpts = program.opts();
4393
- const configPath = globalOpts.agent === DEFAULT_CONFIG_PATH ? void 0 : globalOpts.agent;
5939
+ const nameOrPath = globalOpts.agent;
5940
+ const configPath = await resolveAgentPath(
5941
+ nameOrPath,
5942
+ globalOpts.autoInstall !== false,
5943
+ true
5944
+ );
4394
5945
  const config = await loadAgentConfig(configPath);
4395
- console.log(`\u{1F4C4} Loading Dexto config from: ${resolveConfigPath(configPath)}`);
5946
+ console.log(`\u{1F4C4} Loading Dexto config from: ${configPath}`);
4396
5947
  if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) {
4397
5948
  console.error(
4398
5949
  "\u274C No MCP servers configured. Please configure mcpServers in your config file."
@@ -4424,38 +5975,30 @@ program.argument(
4424
5975
  "[prompt...]",
4425
5976
  "Natural-language prompt to run once. If not passed, dexto will start as an interactive CLI"
4426
5977
  ).description(
4427
- "Dexto CLI allows you to talk to Dexto, build custom AI Agents, build complex AI applications like Cursor, and more.\n\nRun dexto interactive CLI with `dexto` or run a one-shot prompt with `dexto <prompt>`\nStart with a new session using `dexto --new-session [sessionId]`\nRun dexto web UI with `dexto --mode web`\nRun dexto as a server (REST APIs + WebSockets) with `dexto --mode server`\nRun dexto as a discord bot with `dexto --mode discord`\nRun dexto as a telegram bot with `dexto --mode telegram`\nRun dexto agent as an MCP server with `dexto --mode mcp`\nRun dexto as an MCP server aggregator with `dexto mcp --group-servers`\n\nCheck subcommands for more features. Check https://github.com/truffle-ai/dexto for documentation on how to customize dexto and other examples"
5978
+ 'Dexto CLI allows you to talk to Dexto, build custom AI Agents, build complex AI applications like Cursor, and more.\n\nRun dexto interactive CLI with `dexto` or run a one-shot prompt with `dexto -p "<prompt>"` or `dexto "<prompt>"`\nStart with a new session using `dexto --new-session [sessionId]`\nRun dexto web UI with `dexto --mode web`\nRun dexto as a server (REST APIs + WebSockets) with `dexto --mode server`\nRun dexto as a discord bot with `dexto --mode discord`\nRun dexto as a telegram bot with `dexto --mode telegram`\nRun dexto agent as an MCP server with `dexto --mode mcp`\nRun dexto as an MCP server aggregator with `dexto mcp --group-servers`\n\nCheck subcommands for more features. Check https://github.com/truffle-ai/dexto for documentation on how to customize dexto and other examples'
4428
5979
  ).action(async (prompt = []) => {
4429
- if (!existsSync2(".env")) {
5980
+ if (!existsSync4(".env")) {
4430
5981
  logger.debug("WARNING: .env file not found; copy .env.example and set your API keys.");
4431
5982
  }
4432
- const commonProviders = [
4433
- "openai",
4434
- "google",
4435
- "anthropic",
4436
- "groq",
4437
- "xai",
4438
- "cohere"
4439
- ];
4440
- const hasApiKey = commonProviders.some((provider) => resolveApiKeyForProvider(provider));
4441
- if (!hasApiKey) {
4442
- const { interactiveApiKeySetup } = await import("./interactive-api-key-setup-V3GAACLN.js");
4443
- const setupResult = await interactiveApiKeySetup();
4444
- if (!setupResult.success) {
4445
- if (setupResult.skipSetup) {
4446
- console.log(
4447
- chalk15.dim("\n\u{1F44B} Run dexto again once you have set up your API key!")
4448
- );
4449
- } else {
4450
- console.error(chalk15.red("\n\u274C API key setup required to continue."));
4451
- }
4452
- process.exit(0);
5983
+ const opts = program.opts();
5984
+ let headlessInput = void 0;
5985
+ if (opts.prompt !== void 0 && String(opts.prompt).trim() !== "") {
5986
+ headlessInput = String(opts.prompt);
5987
+ } else if (opts.prompt !== void 0) {
5988
+ console.error(
5989
+ "\u274C For headless one-shot mode, prompt cannot be empty. Provide a non-empty prompt with -p/--prompt or use positional argument."
5990
+ );
5991
+ process.exit(1);
5992
+ } else if (prompt.length > 0) {
5993
+ if (prompt.length === 1) {
5994
+ headlessInput = prompt[0];
5995
+ } else {
5996
+ console.error(
5997
+ '\u274C For headless one-shot mode, pass the prompt in double quotes as a single argument (e.g., "say hello") or use -p/--prompt.'
5998
+ );
5999
+ process.exit(1);
4453
6000
  }
4454
- dotenv3.config();
4455
- console.log(chalk15.green("\n\u2728 API key configured! Starting Dexto...\n"));
4456
6001
  }
4457
- const opts = program.opts();
4458
- const headlessInput = prompt.join(" ") || void 0;
4459
6002
  if (opts.model) {
4460
6003
  let provider;
4461
6004
  try {
@@ -4480,25 +6023,54 @@ program.argument(
4480
6023
  } catch (err) {
4481
6024
  handleCliOptionsError(err);
4482
6025
  }
6026
+ let validatedConfig;
6027
+ let resolvedPath;
6028
+ try {
6029
+ if (opts.agent && isPath(opts.agent)) {
6030
+ resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false, true);
6031
+ } else {
6032
+ if (opts.agent) {
6033
+ const registry = getAgentRegistry();
6034
+ if (!registry.hasAgent(opts.agent)) {
6035
+ console.error(`\u274C Agent '${opts.agent}' not found in registry`);
6036
+ const available = Object.keys(registry.getAvailableAgents());
6037
+ if (available.length > 0) {
6038
+ console.log(`\u{1F4CB} Available agents: ${available.join(", ")}`);
6039
+ } else {
6040
+ console.log("\u{1F4CB} No agents available in registry");
6041
+ }
6042
+ process.exit(1);
6043
+ }
6044
+ }
6045
+ if (await requiresSetup()) {
6046
+ if (opts.interactive === false) {
6047
+ console.error("\u274C Setup required but --no-interactive flag is set.");
6048
+ console.error("\u{1F4A1} Run `dexto setup` to configure preferences first.");
6049
+ process.exit(1);
6050
+ }
6051
+ await handleSetupCommand({ interactive: true });
6052
+ }
6053
+ resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false, true);
6054
+ }
6055
+ const rawConfig = await loadAgentConfig(resolvedPath);
6056
+ const mergedConfig = applyCLIOverrides(rawConfig, opts);
6057
+ validatedConfig = await validateAgentConfig(mergedConfig, opts.interactive !== false);
6058
+ } catch (err) {
6059
+ console.error(`\u274C Failed to load configuration: ${err}`);
6060
+ process.exit(1);
6061
+ }
4483
6062
  let agent;
4484
6063
  try {
4485
- const configPath = opts.agent === DEFAULT_CONFIG_PATH ? void 0 : opts.agent;
4486
- console.log(`\u{1F680} Initializing Dexto with config: ${resolveConfigPath(configPath)}`);
4487
- const cfg = await loadAgentConfig(configPath);
4488
- const cliOverrides = {
4489
- model: opts.model,
4490
- provider: opts.provider,
4491
- router: opts.router,
4492
- apiKey: opts.apiKey
4493
- };
6064
+ console.log(`\u{1F680} Initializing Dexto with config: ${resolvedPath}`);
4494
6065
  process.env.DEXTO_RUN_MODE = opts.mode;
4495
- const finalConfig = applyCLIOverrides(cfg, cliOverrides);
4496
- if (opts.strict && finalConfig.mcpServers) {
4497
- for (const [_serverName, serverConfig] of Object.entries(finalConfig.mcpServers)) {
6066
+ if (opts.strict && validatedConfig.mcpServers) {
6067
+ for (const [_serverName, serverConfig] of Object.entries(
6068
+ validatedConfig.mcpServers
6069
+ )) {
4498
6070
  serverConfig.connectionMode = "strict";
4499
6071
  }
4500
6072
  }
4501
- agent = new DextoAgent(finalConfig, configPath);
6073
+ agent = new DextoAgent(validatedConfig, opts.agent);
4502
6074
  await agent.start();
4503
6075
  if (opts.newSession !== void 0) {
4504
6076
  try {
@@ -4519,7 +6091,7 @@ program.argument(
4519
6091
  }
4520
6092
  switch (opts.mode) {
4521
6093
  case "cli": {
4522
- const { CLIToolConfirmationSubscriber } = await import("./cli-confirmation-handler-2APQRKHG.js");
6094
+ const { CLIToolConfirmationSubscriber } = await import("./cli-confirmation-handler-GJHPLGOL.js");
4523
6095
  const cliSubscriber = new CLIToolConfirmationSubscriber();
4524
6096
  cliSubscriber.subscribe(agent.agentEventBus);
4525
6097
  logger.info("Setting up CLI event subscriptions...");
@@ -4598,7 +6170,7 @@ program.argument(
4598
6170
  defaultBaseUrl: "stdio://local-dexto"
4599
6171
  },
4600
6172
  agentCardConfig
4601
- // preserve overrides from agent.yml
6173
+ // preserve overrides from agent file
4602
6174
  );
4603
6175
  const mcpTransport = await createMcpTransport("stdio");
4604
6176
  await initializeMcpServer(agent, agentCardData, mcpTransport);