dexto 1.6.7 → 1.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/analytics/wrapper.d.ts.map +1 -1
- package/dist/analytics/wrapper.js +43 -9
- package/dist/cli/auth/api-client.d.ts +50 -0
- package/dist/cli/auth/api-client.d.ts.map +1 -1
- package/dist/cli/auth/api-client.js +379 -15
- package/dist/cli/auth/browser-launch.d.ts +6 -0
- package/dist/cli/auth/browser-launch.d.ts.map +1 -0
- package/dist/cli/auth/browser-launch.js +24 -0
- package/dist/cli/auth/device.d.ts +14 -0
- package/dist/cli/auth/device.d.ts.map +1 -0
- package/dist/cli/auth/device.js +93 -0
- package/dist/cli/auth/index.d.ts +3 -1
- package/dist/cli/auth/index.d.ts.map +1 -1
- package/dist/cli/auth/index.js +2 -1
- package/dist/cli/auth/login-persistence.d.ts +13 -0
- package/dist/cli/auth/login-persistence.d.ts.map +1 -0
- package/dist/cli/auth/login-persistence.js +22 -0
- package/dist/cli/auth/oauth.d.ts +4 -1
- package/dist/cli/auth/oauth.d.ts.map +1 -1
- package/dist/cli/auth/oauth.js +6 -2
- package/dist/cli/auth/types.d.ts +12 -0
- package/dist/cli/auth/types.d.ts.map +1 -0
- package/dist/cli/auth/types.js +1 -0
- package/dist/cli/commands/agents/register.d.ts +6 -0
- package/dist/cli/commands/agents/register.d.ts.map +1 -0
- package/dist/cli/commands/agents/register.js +85 -0
- package/dist/cli/commands/auth/index.d.ts +1 -1
- package/dist/cli/commands/auth/index.d.ts.map +1 -1
- package/dist/cli/commands/auth/index.js +1 -1
- package/dist/cli/commands/auth/login.d.ts +6 -6
- package/dist/cli/commands/auth/login.d.ts.map +1 -1
- package/dist/cli/commands/auth/login.js +85 -115
- package/dist/cli/commands/auth/logout.d.ts.map +1 -1
- package/dist/cli/commands/auth/logout.js +32 -2
- package/dist/cli/commands/auth/register.d.ts +3 -0
- package/dist/cli/commands/auth/register.d.ts.map +1 -0
- package/dist/cli/commands/auth/register.js +94 -0
- package/dist/cli/commands/billing/register.d.ts +3 -0
- package/dist/cli/commands/billing/register.d.ts.map +1 -0
- package/dist/cli/commands/billing/register.js +20 -0
- package/dist/cli/commands/helpers/formatters.d.ts.map +1 -1
- package/dist/cli/commands/helpers/formatters.js +9 -0
- package/dist/cli/commands/image/register.d.ts +6 -0
- package/dist/cli/commands/image/register.d.ts.map +1 -0
- package/dist/cli/commands/image/register.js +144 -0
- package/dist/cli/commands/install.d.ts +2 -2
- package/dist/cli/commands/list-agents.d.ts.map +1 -1
- package/dist/cli/commands/list-agents.js +3 -3
- package/dist/cli/commands/mcp/register.d.ts +6 -0
- package/dist/cli/commands/mcp/register.d.ts.map +1 -0
- package/dist/cli/commands/mcp/register.js +64 -0
- package/dist/cli/commands/plugin/register.d.ts +6 -0
- package/dist/cli/commands/plugin/register.d.ts.map +1 -0
- package/dist/cli/commands/plugin/register.js +183 -0
- package/dist/cli/commands/plugin.d.ts +4 -4
- package/dist/cli/commands/register-context.d.ts +12 -0
- package/dist/cli/commands/register-context.d.ts.map +1 -0
- package/dist/cli/commands/register-context.js +1 -0
- package/dist/cli/commands/run/headless.d.ts +20 -0
- package/dist/cli/commands/run/headless.d.ts.map +1 -0
- package/dist/cli/commands/run/headless.js +275 -0
- package/dist/cli/commands/run/register.d.ts +3 -0
- package/dist/cli/commands/run/register.d.ts.map +1 -0
- package/dist/cli/commands/run/register.js +78 -0
- package/dist/cli/commands/search/register.d.ts +3 -0
- package/dist/cli/commands/search/register.d.ts.map +1 -0
- package/dist/cli/commands/search/register.js +55 -0
- package/dist/cli/commands/session/register.d.ts +3 -0
- package/dist/cli/commands/session/register.d.ts.map +1 -0
- package/dist/cli/commands/session/register.js +75 -0
- package/dist/cli/commands/setup.js +4 -4
- package/dist/cli/commands/sync-agents.d.ts +3 -3
- package/dist/cli/commands/sync-agents.js +4 -4
- package/dist/cli/commands/uninstall.d.ts +2 -2
- package/dist/cli/modes/cli.d.ts +3 -0
- package/dist/cli/modes/cli.d.ts.map +1 -0
- package/dist/cli/modes/cli.js +170 -0
- package/dist/cli/modes/context.d.ts +20 -0
- package/dist/cli/modes/context.d.ts.map +1 -0
- package/dist/cli/modes/context.js +1 -0
- package/dist/cli/modes/dispatch.d.ts +3 -0
- package/dist/cli/modes/dispatch.d.ts.map +1 -0
- package/dist/cli/modes/dispatch.js +52 -0
- package/dist/cli/modes/mcp.d.ts +3 -0
- package/dist/cli/modes/mcp.d.ts.map +1 -0
- package/dist/cli/modes/mcp.js +23 -0
- package/dist/cli/modes/server.d.ts +3 -0
- package/dist/cli/modes/server.d.ts.map +1 -0
- package/dist/cli/modes/server.js +36 -0
- package/dist/cli/modes/web.d.ts +3 -0
- package/dist/cli/modes/web.d.ts.map +1 -0
- package/dist/cli/modes/web.js +50 -0
- package/dist/cli/utils/setup-utils.js +1 -1
- package/dist/index-main.js +150 -991
- package/dist/utils/port-utils.d.ts +1 -1
- package/dist/utils/port-utils.d.ts.map +1 -1
- package/dist/utils/port-utils.js +7 -3
- package/dist/webui/assets/index-Bn9YuTdA.css +1 -0
- package/dist/webui/assets/index-CNiOYnOb.js +2059 -0
- package/dist/webui/index.html +2 -2
- package/package.json +12 -12
- package/dist/webui/assets/index-d6c-yJNn.js +0 -2059
- package/dist/webui/assets/index-yKdFLN1k.css +0 -1
package/dist/index-main.js
CHANGED
|
@@ -4,9 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import * as p from '@clack/prompts';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { initAnalytics, capture, getWebUIAnalyticsConfig } from './analytics/index.js';
|
|
8
7
|
import { withAnalytics, safeExit, ExitSignal } from './analytics/wrapper.js';
|
|
9
|
-
import { createFileSessionLoggerFactory } from './utils/session-logger-factory.js';
|
|
10
8
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
11
9
|
if (!existsSync(packageJsonPath)) {
|
|
12
10
|
return undefined;
|
|
@@ -45,51 +43,63 @@ function resolveCliVersion() {
|
|
|
45
43
|
const cliVersion = resolveCliVersion();
|
|
46
44
|
// Set CLI version for Dexto Gateway usage tracking
|
|
47
45
|
process.env.DEXTO_CLI_VERSION = cliVersion;
|
|
48
|
-
// Populate DEXTO_API_KEY for Dexto gateway routing
|
|
49
|
-
// Resolution order in getDextoApiKey():
|
|
50
|
-
// 1. Explicit env var (CI, testing, account override)
|
|
51
|
-
// 2. auth.json from `dexto login`
|
|
52
|
-
import { isDextoAuthEnabled } from '@dexto/agent-management';
|
|
53
|
-
if (isDextoAuthEnabled()) {
|
|
54
|
-
const { getDextoApiKey } = await import('./cli/auth/index.js');
|
|
55
|
-
const dextoApiKey = await getDextoApiKey();
|
|
56
|
-
if (dextoApiKey) {
|
|
57
|
-
process.env.DEXTO_API_KEY = dextoApiKey;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
46
|
import { logger, getProviderFromModel, getAllSupportedModels, startLlmRegistryAutoUpdate, DextoAgent, isPath, resolveApiKeyForProvider, getPrimaryApiKeyEnvVar, } from '@dexto/core';
|
|
61
47
|
import { applyImageDefaults, cleanNullValues, AgentConfigSchema, loadImage, resolveServicesFromConfig, setImageImporter, toDextoAgentOptions, } from '@dexto/agent-config';
|
|
62
|
-
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, } from '@dexto/agent-management';
|
|
63
|
-
import { startHonoApiServer } from './api/server-hono.js';
|
|
48
|
+
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, enrichAgentConfig, isDextoAuthEnabled, } from '@dexto/agent-management';
|
|
64
49
|
import { validateCliOptions, handleCliOptionsError } from './cli/utils/options.js';
|
|
65
50
|
import { validateAgentConfig } from './cli/utils/config-validation.js';
|
|
66
51
|
import { applyCLIOverrides, applyUserPreferences } from './config/cli-overrides.js';
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
import { checkForUpdates, displayUpdateNotification } from './cli/utils/version-check.js';
|
|
77
|
-
import { resolveWebRoot } from './web.js';
|
|
78
|
-
import { initializeMcpServer, createMcpTransport } from '@dexto/server';
|
|
79
|
-
import { createAgentCard } from '@dexto/core';
|
|
80
|
-
import { initializeMcpToolAggregationServer } from './api/mcp/tool-aggregation-handler.js';
|
|
81
|
-
import { importImageModule } from './cli/utils/image-store.js';
|
|
52
|
+
import { registerRunCommand } from './cli/commands/run/register.js';
|
|
53
|
+
import { registerSessionCommand } from './cli/commands/session/register.js';
|
|
54
|
+
import { registerSearchCommand } from './cli/commands/search/register.js';
|
|
55
|
+
import { registerAuthCommand } from './cli/commands/auth/register.js';
|
|
56
|
+
import { registerBillingCommand } from './cli/commands/billing/register.js';
|
|
57
|
+
import { registerMcpCommand } from './cli/commands/mcp/register.js';
|
|
58
|
+
import { registerImageCommand } from './cli/commands/image/register.js';
|
|
59
|
+
import { registerPluginCommand } from './cli/commands/plugin/register.js';
|
|
60
|
+
import { registerAgentsCommand } from './cli/commands/agents/register.js';
|
|
82
61
|
const program = new Command();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
let imageImporterConfigured = false;
|
|
63
|
+
let dextoApiKeyBootstrapped = false;
|
|
64
|
+
let versionCheckPromise = null;
|
|
65
|
+
let llmRegistryAutoUpdateStarted = false;
|
|
66
|
+
async function ensureImageImporterConfigured() {
|
|
67
|
+
if (imageImporterConfigured) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { importImageModule } = await import('./cli/utils/image-store.js');
|
|
71
|
+
setImageImporter((specifier) => importImageModule(specifier));
|
|
72
|
+
imageImporterConfigured = true;
|
|
73
|
+
}
|
|
74
|
+
async function ensureDextoApiKeyBootstrap() {
|
|
75
|
+
if (dextoApiKeyBootstrapped) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!isDextoAuthEnabled()) {
|
|
79
|
+
dextoApiKeyBootstrapped = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const { getDextoApiKey } = await import('./cli/auth/index.js');
|
|
83
|
+
const dextoApiKey = await getDextoApiKey();
|
|
84
|
+
if (dextoApiKey) {
|
|
85
|
+
process.env.DEXTO_API_KEY = dextoApiKey;
|
|
86
|
+
}
|
|
87
|
+
dextoApiKeyBootstrapped = true;
|
|
88
|
+
}
|
|
89
|
+
async function getVersionCheckResult() {
|
|
90
|
+
if (!versionCheckPromise) {
|
|
91
|
+
const { checkForUpdates } = await import('./cli/utils/version-check.js');
|
|
92
|
+
versionCheckPromise = checkForUpdates(cliVersion);
|
|
93
|
+
}
|
|
94
|
+
return versionCheckPromise;
|
|
95
|
+
}
|
|
96
|
+
function ensureLlmRegistryAutoUpdateStarted() {
|
|
97
|
+
if (llmRegistryAutoUpdateStarted) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
startLlmRegistryAutoUpdate();
|
|
101
|
+
llmRegistryAutoUpdateStarted = true;
|
|
102
|
+
}
|
|
93
103
|
// 1) GLOBAL OPTIONS
|
|
94
104
|
program
|
|
95
105
|
.name('dexto')
|
|
@@ -123,6 +133,7 @@ program
|
|
|
123
133
|
try {
|
|
124
134
|
p.intro(chalk.inverse('Create Dexto App'));
|
|
125
135
|
// Create the app project structure (fully self-contained)
|
|
136
|
+
const { createDextoProject } = await import('./cli/commands/create-app.js');
|
|
126
137
|
await createDextoProject(name, options);
|
|
127
138
|
p.outro(chalk.greenBright('Dexto app created successfully!'));
|
|
128
139
|
safeExit('create-app', 0);
|
|
@@ -134,151 +145,23 @@ program
|
|
|
134
145
|
safeExit('create-app', 1, 'error');
|
|
135
146
|
}
|
|
136
147
|
}));
|
|
137
|
-
|
|
138
|
-
program
|
|
139
|
-
.command('create-image [name]', { hidden: true })
|
|
140
|
-
.description('Alias for `dexto image create`')
|
|
141
|
-
.action(withAnalytics('create-image', async (name) => {
|
|
142
|
-
try {
|
|
143
|
-
p.intro(chalk.inverse('Create Dexto Image'));
|
|
144
|
-
// Create the image project structure
|
|
145
|
-
const projectPath = await createImage(name);
|
|
146
|
-
p.outro(chalk.greenBright(`Dexto image created successfully at ${projectPath}!`));
|
|
147
|
-
safeExit('create-image', 0);
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
if (err instanceof ExitSignal)
|
|
151
|
-
throw err;
|
|
152
|
-
console.error(`❌ dexto create-image command failed: ${err}`);
|
|
153
|
-
safeExit('create-image', 1, 'error');
|
|
154
|
-
}
|
|
155
|
-
}));
|
|
156
|
-
// 3b) `image` SUB-COMMAND
|
|
157
|
-
const imageCommand = program.command('image').description('Manage images');
|
|
158
|
-
imageCommand.addHelpText('after', `
|
|
159
|
-
Examples:
|
|
160
|
-
$ dexto image create my-image
|
|
161
|
-
$ dexto image install @dexto/image-local
|
|
162
|
-
$ dexto image install @myorg/my-image@1.2.3
|
|
163
|
-
$ dexto image list
|
|
164
|
-
$ dexto image use @myorg/my-image@1.2.3
|
|
165
|
-
$ dexto image remove @myorg/my-image@1.2.3
|
|
166
|
-
$ dexto image doctor
|
|
167
|
-
`);
|
|
168
|
-
imageCommand
|
|
169
|
-
.command('create [name]')
|
|
170
|
-
.description('Create a Dexto image project (scaffold)')
|
|
171
|
-
.action(withAnalytics('image create', async (name) => {
|
|
172
|
-
try {
|
|
173
|
-
p.intro(chalk.inverse('Create Dexto Image'));
|
|
174
|
-
// Create the image project structure
|
|
175
|
-
const projectPath = await createImage(name);
|
|
176
|
-
p.outro(chalk.greenBright(`Dexto image created successfully at ${projectPath}!`));
|
|
177
|
-
safeExit('image create', 0);
|
|
178
|
-
}
|
|
179
|
-
catch (err) {
|
|
180
|
-
if (err instanceof ExitSignal)
|
|
181
|
-
throw err;
|
|
182
|
-
console.error(`❌ dexto image create command failed: ${err}`);
|
|
183
|
-
safeExit('image create', 1, 'error');
|
|
184
|
-
}
|
|
185
|
-
}));
|
|
186
|
-
imageCommand
|
|
187
|
-
.command('install <image>')
|
|
188
|
-
.description('Install an image into the local Dexto image store')
|
|
189
|
-
.option('--force', 'Force reinstall if already installed')
|
|
190
|
-
.option('--no-activate', 'Do not set as the active version')
|
|
191
|
-
.addHelpText('after', `
|
|
192
|
-
Examples:
|
|
193
|
-
$ dexto image install @dexto/image-local
|
|
194
|
-
$ dexto image install @myorg/my-image@1.2.3
|
|
195
|
-
$ dexto image install ./my-image-1.0.0.tgz
|
|
196
|
-
`)
|
|
197
|
-
.action(withAnalytics('image install', async (image, options) => {
|
|
198
|
-
try {
|
|
199
|
-
await handleImageInstallCommand({ ...options, image });
|
|
200
|
-
safeExit('image install', 0);
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
if (err instanceof ExitSignal)
|
|
204
|
-
throw err;
|
|
205
|
-
console.error(`❌ dexto image install command failed: ${err}`);
|
|
206
|
-
safeExit('image install', 1, 'error');
|
|
207
|
-
}
|
|
208
|
-
}));
|
|
209
|
-
imageCommand
|
|
210
|
-
.command('list')
|
|
211
|
-
.description('List installed images')
|
|
212
|
-
.action(withAnalytics('image list', async () => {
|
|
213
|
-
try {
|
|
214
|
-
await handleImageListCommand();
|
|
215
|
-
safeExit('image list', 0);
|
|
216
|
-
}
|
|
217
|
-
catch (err) {
|
|
218
|
-
if (err instanceof ExitSignal)
|
|
219
|
-
throw err;
|
|
220
|
-
console.error(`❌ dexto image list command failed: ${err}`);
|
|
221
|
-
safeExit('image list', 1, 'error');
|
|
222
|
-
}
|
|
223
|
-
}));
|
|
224
|
-
imageCommand
|
|
225
|
-
.command('use <image>')
|
|
226
|
-
.description('Set the active version for an installed image (image@version)')
|
|
227
|
-
.action(withAnalytics('image use', async (image) => {
|
|
228
|
-
try {
|
|
229
|
-
await handleImageUseCommand({ image });
|
|
230
|
-
safeExit('image use', 0);
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
if (err instanceof ExitSignal)
|
|
234
|
-
throw err;
|
|
235
|
-
console.error(`❌ dexto image use command failed: ${err}`);
|
|
236
|
-
safeExit('image use', 1, 'error');
|
|
237
|
-
}
|
|
238
|
-
}));
|
|
239
|
-
imageCommand
|
|
240
|
-
.command('remove <image>')
|
|
241
|
-
.description('Remove an image from the store (image or image@version)')
|
|
242
|
-
.action(withAnalytics('image remove', async (image) => {
|
|
243
|
-
try {
|
|
244
|
-
await handleImageRemoveCommand({ image });
|
|
245
|
-
safeExit('image remove', 0);
|
|
246
|
-
}
|
|
247
|
-
catch (err) {
|
|
248
|
-
if (err instanceof ExitSignal)
|
|
249
|
-
throw err;
|
|
250
|
-
console.error(`❌ dexto image remove command failed: ${err}`);
|
|
251
|
-
safeExit('image remove', 1, 'error');
|
|
252
|
-
}
|
|
253
|
-
}));
|
|
254
|
-
imageCommand
|
|
255
|
-
.command('doctor')
|
|
256
|
-
.description('Print image store diagnostics')
|
|
257
|
-
.action(withAnalytics('image doctor', async () => {
|
|
258
|
-
try {
|
|
259
|
-
await handleImageDoctorCommand();
|
|
260
|
-
safeExit('image doctor', 0);
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
if (err instanceof ExitSignal)
|
|
264
|
-
throw err;
|
|
265
|
-
console.error(`❌ dexto image doctor command failed: ${err}`);
|
|
266
|
-
safeExit('image doctor', 1, 'error');
|
|
267
|
-
}
|
|
268
|
-
}));
|
|
148
|
+
registerImageCommand({ program });
|
|
269
149
|
// 4) `init-app` SUB-COMMAND
|
|
270
150
|
program
|
|
271
151
|
.command('init-app')
|
|
272
152
|
.description('Initialize an existing Typescript app with Dexto')
|
|
273
153
|
.action(withAnalytics('init-app', async () => {
|
|
154
|
+
const { checkForFileInCurrentDirectory, FileNotFoundError } = await import('./cli/utils/package-mgmt.js');
|
|
274
155
|
try {
|
|
275
156
|
// pre-condition: check that package.json and tsconfig.json exist in current directory to know that project is valid
|
|
276
157
|
await checkForFileInCurrentDirectory('package.json');
|
|
277
158
|
await checkForFileInCurrentDirectory('tsconfig.json');
|
|
278
159
|
// start intro
|
|
279
160
|
p.intro(chalk.inverse('Dexto Init App'));
|
|
161
|
+
const { getUserInputToInitDextoApp, initDexto, postInitDexto } = await import('./cli/commands/init-app.js');
|
|
280
162
|
const userInput = await getUserInputToInitDextoApp();
|
|
281
163
|
try {
|
|
164
|
+
const { capture } = await import('./analytics/index.js');
|
|
282
165
|
capture('dexto_init', {
|
|
283
166
|
provider: userInput.llmProvider,
|
|
284
167
|
providedKey: Boolean(userInput.llmApiKey),
|
|
@@ -316,6 +199,7 @@ program
|
|
|
316
199
|
.option('--force', 'Overwrite existing setup without confirmation')
|
|
317
200
|
.action(withAnalytics('setup', async (options) => {
|
|
318
201
|
try {
|
|
202
|
+
const { handleSetupCommand } = await import('./cli/commands/setup.js');
|
|
319
203
|
await handleSetupCommand(options);
|
|
320
204
|
safeExit('setup', 0);
|
|
321
205
|
}
|
|
@@ -326,75 +210,14 @@ program
|
|
|
326
210
|
safeExit('setup', 1, 'error');
|
|
327
211
|
}
|
|
328
212
|
}));
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
.command('install [agents...]')
|
|
332
|
-
.description('Install agents from registry or custom YAML files/directories')
|
|
333
|
-
.option('--all', 'Install all available agents from registry')
|
|
334
|
-
.option('--no-inject-preferences', 'Skip injecting global preferences into installed agents')
|
|
335
|
-
.option('--force', 'Force reinstall even if agent is already installed')
|
|
336
|
-
.addHelpText('after', `
|
|
337
|
-
Examples:
|
|
338
|
-
$ dexto install coding-agent Install agent from registry
|
|
339
|
-
$ dexto install agent1 agent2 Install multiple registry agents
|
|
340
|
-
$ dexto install --all Install all available registry agents
|
|
341
|
-
$ dexto install ./my-agent.yml Install custom agent from YAML file
|
|
342
|
-
$ dexto install ./my-agent-dir/ Install custom agent from directory (interactive)`)
|
|
343
|
-
.action(withAnalytics('install', async (agents = [], options) => {
|
|
344
|
-
try {
|
|
345
|
-
await handleInstallCommand(agents, options);
|
|
346
|
-
safeExit('install', 0);
|
|
347
|
-
}
|
|
348
|
-
catch (err) {
|
|
349
|
-
if (err instanceof ExitSignal)
|
|
350
|
-
throw err;
|
|
351
|
-
console.error(`❌ dexto install command failed: ${err}`);
|
|
352
|
-
safeExit('install', 1, 'error');
|
|
353
|
-
}
|
|
354
|
-
}));
|
|
355
|
-
// 7) `uninstall` SUB-COMMAND
|
|
356
|
-
program
|
|
357
|
-
.command('uninstall [agents...]')
|
|
358
|
-
.description('Uninstall agents from the local installation')
|
|
359
|
-
.option('--all', 'Uninstall all installed agents')
|
|
360
|
-
.option('--force', 'Force uninstall even if agent is protected (e.g., coding-agent)')
|
|
361
|
-
.action(withAnalytics('uninstall', async (agents, options) => {
|
|
362
|
-
try {
|
|
363
|
-
await handleUninstallCommand(agents, options);
|
|
364
|
-
safeExit('uninstall', 0);
|
|
365
|
-
}
|
|
366
|
-
catch (err) {
|
|
367
|
-
if (err instanceof ExitSignal)
|
|
368
|
-
throw err;
|
|
369
|
-
console.error(`❌ dexto uninstall command failed: ${err}`);
|
|
370
|
-
safeExit('uninstall', 1, 'error');
|
|
371
|
-
}
|
|
372
|
-
}));
|
|
373
|
-
// 8) `list-agents` SUB-COMMAND
|
|
374
|
-
program
|
|
375
|
-
.command('list-agents')
|
|
376
|
-
.description('List available and installed agents')
|
|
377
|
-
.option('--verbose', 'Show detailed agent information')
|
|
378
|
-
.option('--installed', 'Show only installed agents')
|
|
379
|
-
.option('--available', 'Show only available agents')
|
|
380
|
-
.action(withAnalytics('list-agents', async (options) => {
|
|
381
|
-
try {
|
|
382
|
-
await handleListAgentsCommand(options);
|
|
383
|
-
safeExit('list-agents', 0);
|
|
384
|
-
}
|
|
385
|
-
catch (err) {
|
|
386
|
-
if (err instanceof ExitSignal)
|
|
387
|
-
throw err;
|
|
388
|
-
console.error(`❌ dexto list-agents command failed: ${err}`);
|
|
389
|
-
safeExit('list-agents', 1, 'error');
|
|
390
|
-
}
|
|
391
|
-
}));
|
|
392
|
-
// 9) `which` SUB-COMMAND
|
|
213
|
+
registerAgentsCommand({ program });
|
|
214
|
+
// 7) `which` SUB-COMMAND
|
|
393
215
|
program
|
|
394
216
|
.command('which <agent>')
|
|
395
217
|
.description('Show the path to an agent')
|
|
396
218
|
.action(withAnalytics('which', async (agent) => {
|
|
397
219
|
try {
|
|
220
|
+
const { handleWhichCommand } = await import('./cli/commands/which.js');
|
|
398
221
|
await handleWhichCommand(agent);
|
|
399
222
|
safeExit('which', 0);
|
|
400
223
|
}
|
|
@@ -405,200 +228,44 @@ program
|
|
|
405
228
|
safeExit('which', 1, 'error');
|
|
406
229
|
}
|
|
407
230
|
}));
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
await handleSyncAgentsCommand(options);
|
|
417
|
-
safeExit('sync-agents', 0);
|
|
418
|
-
}
|
|
419
|
-
catch (err) {
|
|
420
|
-
if (err instanceof ExitSignal)
|
|
421
|
-
throw err;
|
|
422
|
-
console.error(`❌ dexto sync-agents command failed: ${err}`);
|
|
423
|
-
safeExit('sync-agents', 1, 'error');
|
|
424
|
-
}
|
|
425
|
-
}));
|
|
426
|
-
// 11) `plugin` SUB-COMMAND
|
|
427
|
-
const pluginCommand = program.command('plugin').description('Manage plugins');
|
|
428
|
-
pluginCommand
|
|
429
|
-
.command('list')
|
|
430
|
-
.description('List installed plugins')
|
|
431
|
-
.option('--verbose', 'Show detailed plugin information')
|
|
432
|
-
.action(withAnalytics('plugin list', async (options) => {
|
|
433
|
-
try {
|
|
434
|
-
await handlePluginListCommand(options);
|
|
435
|
-
safeExit('plugin list', 0);
|
|
436
|
-
}
|
|
437
|
-
catch (err) {
|
|
438
|
-
if (err instanceof ExitSignal)
|
|
439
|
-
throw err;
|
|
440
|
-
console.error(`❌ dexto plugin list command failed: ${err}`);
|
|
441
|
-
safeExit('plugin list', 1, 'error');
|
|
442
|
-
}
|
|
443
|
-
}));
|
|
444
|
-
pluginCommand
|
|
445
|
-
.command('install')
|
|
446
|
-
.description('Install a plugin from a local directory')
|
|
447
|
-
.requiredOption('--path <path>', 'Path to the plugin directory')
|
|
448
|
-
.option('--scope <scope>', 'Installation scope: user, project, or local', 'user')
|
|
449
|
-
.option('--force', 'Force overwrite if already installed')
|
|
450
|
-
.action(withAnalytics('plugin install', async (options) => {
|
|
451
|
-
try {
|
|
452
|
-
await handlePluginInstallCommand(options);
|
|
453
|
-
safeExit('plugin install', 0);
|
|
454
|
-
}
|
|
455
|
-
catch (err) {
|
|
456
|
-
if (err instanceof ExitSignal)
|
|
457
|
-
throw err;
|
|
458
|
-
console.error(`❌ dexto plugin install command failed: ${err}`);
|
|
459
|
-
safeExit('plugin install', 1, 'error');
|
|
460
|
-
}
|
|
461
|
-
}));
|
|
462
|
-
pluginCommand
|
|
463
|
-
.command('uninstall <name>')
|
|
464
|
-
.description('Uninstall a plugin by name')
|
|
465
|
-
.action(withAnalytics('plugin uninstall', async (name) => {
|
|
466
|
-
try {
|
|
467
|
-
await handlePluginUninstallCommand({ name });
|
|
468
|
-
safeExit('plugin uninstall', 0);
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
if (err instanceof ExitSignal)
|
|
472
|
-
throw err;
|
|
473
|
-
console.error(`❌ dexto plugin uninstall command failed: ${err}`);
|
|
474
|
-
safeExit('plugin uninstall', 1, 'error');
|
|
475
|
-
}
|
|
476
|
-
}));
|
|
477
|
-
pluginCommand
|
|
478
|
-
.command('validate [path]')
|
|
479
|
-
.description('Validate a plugin directory structure')
|
|
480
|
-
.action(withAnalytics('plugin validate', async (path) => {
|
|
481
|
-
try {
|
|
482
|
-
await handlePluginValidateCommand({ path: path || '.' });
|
|
483
|
-
safeExit('plugin validate', 0);
|
|
484
|
-
}
|
|
485
|
-
catch (err) {
|
|
486
|
-
if (err instanceof ExitSignal)
|
|
487
|
-
throw err;
|
|
488
|
-
console.error(`❌ dexto plugin validate command failed: ${err}`);
|
|
489
|
-
safeExit('plugin validate', 1, 'error');
|
|
490
|
-
}
|
|
491
|
-
}));
|
|
492
|
-
// 12) `plugin marketplace` SUB-COMMANDS
|
|
493
|
-
const marketplaceCommand = pluginCommand
|
|
494
|
-
.command('marketplace')
|
|
495
|
-
.alias('market')
|
|
496
|
-
.description('Manage plugin marketplaces');
|
|
497
|
-
marketplaceCommand
|
|
498
|
-
.command('add <source>')
|
|
499
|
-
.description('Add a marketplace (GitHub: owner/repo, git URL, or local path)')
|
|
500
|
-
.option('--name <name>', 'Custom name for the marketplace')
|
|
501
|
-
.action(withAnalytics('plugin marketplace add', async (source, options) => {
|
|
502
|
-
try {
|
|
503
|
-
await handleMarketplaceAddCommand({ source, name: options.name });
|
|
504
|
-
safeExit('plugin marketplace add', 0);
|
|
505
|
-
}
|
|
506
|
-
catch (err) {
|
|
507
|
-
if (err instanceof ExitSignal)
|
|
508
|
-
throw err;
|
|
509
|
-
console.error(`❌ dexto plugin marketplace add command failed: ${err}`);
|
|
510
|
-
safeExit('plugin marketplace add', 1, 'error');
|
|
511
|
-
}
|
|
512
|
-
}));
|
|
513
|
-
marketplaceCommand
|
|
514
|
-
.command('list')
|
|
515
|
-
.description('List registered marketplaces')
|
|
516
|
-
.option('--verbose', 'Show detailed marketplace information')
|
|
517
|
-
.action(withAnalytics('plugin marketplace list', async (options) => {
|
|
518
|
-
try {
|
|
519
|
-
await handleMarketplaceListCommand(options);
|
|
520
|
-
safeExit('plugin marketplace list', 0);
|
|
521
|
-
}
|
|
522
|
-
catch (err) {
|
|
523
|
-
if (err instanceof ExitSignal)
|
|
524
|
-
throw err;
|
|
525
|
-
console.error(`❌ dexto plugin marketplace list command failed: ${err}`);
|
|
526
|
-
safeExit('plugin marketplace list', 1, 'error');
|
|
527
|
-
}
|
|
528
|
-
}));
|
|
529
|
-
marketplaceCommand
|
|
530
|
-
.command('remove <name>')
|
|
531
|
-
.alias('rm')
|
|
532
|
-
.description('Remove a registered marketplace')
|
|
533
|
-
.action(withAnalytics('plugin marketplace remove', async (name) => {
|
|
534
|
-
try {
|
|
535
|
-
await handleMarketplaceRemoveCommand({ name });
|
|
536
|
-
safeExit('plugin marketplace remove', 0);
|
|
537
|
-
}
|
|
538
|
-
catch (err) {
|
|
539
|
-
if (err instanceof ExitSignal)
|
|
540
|
-
throw err;
|
|
541
|
-
console.error(`❌ dexto plugin marketplace remove command failed: ${err}`);
|
|
542
|
-
safeExit('plugin marketplace remove', 1, 'error');
|
|
543
|
-
}
|
|
544
|
-
}));
|
|
545
|
-
marketplaceCommand
|
|
546
|
-
.command('update [name]')
|
|
547
|
-
.description('Update marketplace(s) from remote (git pull)')
|
|
548
|
-
.action(withAnalytics('plugin marketplace update', async (name) => {
|
|
549
|
-
try {
|
|
550
|
-
await handleMarketplaceUpdateCommand({ name });
|
|
551
|
-
safeExit('plugin marketplace update', 0);
|
|
552
|
-
}
|
|
553
|
-
catch (err) {
|
|
554
|
-
if (err instanceof ExitSignal)
|
|
555
|
-
throw err;
|
|
556
|
-
console.error(`❌ dexto plugin marketplace update command failed: ${err}`);
|
|
557
|
-
safeExit('plugin marketplace update', 1, 'error');
|
|
558
|
-
}
|
|
559
|
-
}));
|
|
560
|
-
marketplaceCommand
|
|
561
|
-
.command('plugins [marketplace]')
|
|
562
|
-
.description('List plugins available in marketplaces')
|
|
563
|
-
.option('--verbose', 'Show plugin descriptions')
|
|
564
|
-
.action(withAnalytics('plugin marketplace plugins', async (marketplace, options) => {
|
|
565
|
-
try {
|
|
566
|
-
await handleMarketplacePluginsCommand({
|
|
567
|
-
marketplace,
|
|
568
|
-
verbose: options?.verbose,
|
|
569
|
-
});
|
|
570
|
-
safeExit('plugin marketplace plugins', 0);
|
|
571
|
-
}
|
|
572
|
-
catch (err) {
|
|
573
|
-
if (err instanceof ExitSignal)
|
|
574
|
-
throw err;
|
|
575
|
-
console.error(`❌ dexto plugin marketplace plugins command failed: ${err}`);
|
|
576
|
-
safeExit('plugin marketplace plugins', 1, 'error');
|
|
577
|
-
}
|
|
578
|
-
}));
|
|
579
|
-
marketplaceCommand
|
|
580
|
-
.command('install <plugin>')
|
|
581
|
-
.description('Install a plugin from marketplace (plugin or plugin@marketplace)')
|
|
582
|
-
.option('--scope <scope>', 'Installation scope: user, project, or local', 'user')
|
|
583
|
-
.option('--force', 'Force reinstall if already exists')
|
|
584
|
-
.action(withAnalytics('plugin marketplace install', async (plugin, options) => {
|
|
585
|
-
try {
|
|
586
|
-
await handleMarketplaceInstallCommand({ ...options, plugin });
|
|
587
|
-
safeExit('plugin marketplace install', 0);
|
|
588
|
-
}
|
|
589
|
-
catch (err) {
|
|
590
|
-
if (err instanceof ExitSignal)
|
|
591
|
-
throw err;
|
|
592
|
-
console.error(`❌ dexto plugin marketplace install command failed: ${err}`);
|
|
593
|
-
safeExit('plugin marketplace install', 1, 'error');
|
|
594
|
-
}
|
|
595
|
-
}));
|
|
596
|
-
// Helper to bootstrap a minimal agent for non-interactive session/search ops
|
|
597
|
-
async function bootstrapAgentFromGlobalOpts() {
|
|
231
|
+
registerPluginCommand({ program });
|
|
232
|
+
// Helper to bootstrap a minimal agent for non-interactive commands
|
|
233
|
+
async function bootstrapAgentFromGlobalOpts(options) {
|
|
234
|
+
const { mode, modelOverride } = options;
|
|
235
|
+
const isHeadlessRun = mode === 'headless-run';
|
|
236
|
+
await ensureDextoApiKeyBootstrap();
|
|
237
|
+
await ensureImageImporterConfigured();
|
|
598
238
|
const globalOpts = program.opts();
|
|
239
|
+
const effectiveModel = modelOverride ?? globalOpts.model;
|
|
240
|
+
let inferredProvider;
|
|
241
|
+
let inferredApiKey;
|
|
242
|
+
// Non-interactive subcommands bypass the main mode action, so replicate
|
|
243
|
+
// model -> provider/apiKey inference here.
|
|
244
|
+
if (effectiveModel) {
|
|
245
|
+
if (effectiveModel.includes('/')) {
|
|
246
|
+
throw new Error(`Model '${effectiveModel}' looks like an OpenRouter-format ID (provider/model). Please set provider/model explicitly in agent config for this command.`);
|
|
247
|
+
}
|
|
248
|
+
inferredProvider = getProviderFromModel(effectiveModel);
|
|
249
|
+
const apiKey = resolveApiKeyForProvider(inferredProvider);
|
|
250
|
+
if (!apiKey) {
|
|
251
|
+
const envVar = getPrimaryApiKeyEnvVar(inferredProvider);
|
|
252
|
+
throw new Error(`Missing API key for provider '${inferredProvider}' - please set $${envVar}`);
|
|
253
|
+
}
|
|
254
|
+
inferredApiKey = apiKey;
|
|
255
|
+
}
|
|
599
256
|
const resolvedPath = await resolveAgentPath(globalOpts.agent, globalOpts.autoInstall !== false);
|
|
600
257
|
const rawConfig = await loadAgentConfig(resolvedPath);
|
|
601
|
-
const mergedConfig = applyCLIOverrides(rawConfig,
|
|
258
|
+
const mergedConfig = applyCLIOverrides(rawConfig, {
|
|
259
|
+
...globalOpts,
|
|
260
|
+
...(modelOverride ? { model: modelOverride } : {}),
|
|
261
|
+
});
|
|
262
|
+
if (effectiveModel) {
|
|
263
|
+
mergedConfig.llm.model = effectiveModel;
|
|
264
|
+
}
|
|
265
|
+
if (inferredProvider && inferredApiKey) {
|
|
266
|
+
mergedConfig.llm.provider = inferredProvider;
|
|
267
|
+
mergedConfig.llm.apiKey = inferredApiKey;
|
|
268
|
+
}
|
|
602
269
|
// Load image first to apply defaults and resolve DI services
|
|
603
270
|
// Priority: CLI flag > Agent config > Environment variable > Default
|
|
604
271
|
const imageName = globalOpts.image || // --image flag
|
|
@@ -620,10 +287,21 @@ async function bootstrapAgentFromGlobalOpts() {
|
|
|
620
287
|
const configWithImageDefaults = applyImageDefaults(mergedConfig, image.defaults);
|
|
621
288
|
// Enrich config with per-agent paths BEFORE validation
|
|
622
289
|
const enrichedConfig = enrichAgentConfig(configWithImageDefaults, resolvedPath, {
|
|
623
|
-
|
|
290
|
+
// Headless run keeps output deterministic and noise-free.
|
|
291
|
+
// Other non-interactive commands keep visible logs.
|
|
292
|
+
logLevel: isHeadlessRun ? 'error' : 'info',
|
|
624
293
|
});
|
|
625
|
-
|
|
626
|
-
|
|
294
|
+
if (isHeadlessRun) {
|
|
295
|
+
// Force silent transport in headless mode even when agent config defines logger settings.
|
|
296
|
+
// `dexto run` owns stderr formatting and should be the only writer.
|
|
297
|
+
enrichedConfig.logger = {
|
|
298
|
+
level: 'error',
|
|
299
|
+
transports: [{ type: 'silent' }],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Override approval config for non-interactive commands.
|
|
303
|
+
// Headless operations default to auto-approve and disable elicitation to
|
|
304
|
+
// avoid waiting for interactive approval handlers.
|
|
627
305
|
enrichedConfig.permissions = {
|
|
628
306
|
...(enrichedConfig.permissions ?? {}),
|
|
629
307
|
mode: 'auto-approve',
|
|
@@ -651,285 +329,27 @@ async function bootstrapAgentFromGlobalOpts() {
|
|
|
651
329
|
process.on('SIGTERM', shutdown);
|
|
652
330
|
return agent;
|
|
653
331
|
}
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
mostRecentId = sessionId;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
return mostRecentId;
|
|
671
|
-
}
|
|
672
|
-
// 11) `session` SUB-COMMAND
|
|
673
|
-
const sessionCommand = program.command('session').description('Manage chat sessions');
|
|
674
|
-
sessionCommand
|
|
675
|
-
.command('list')
|
|
676
|
-
.description('List all sessions')
|
|
677
|
-
.action(withAnalytics('session list', async () => {
|
|
678
|
-
try {
|
|
679
|
-
const agent = await bootstrapAgentFromGlobalOpts();
|
|
680
|
-
await handleSessionListCommand(agent);
|
|
681
|
-
await agent.stop();
|
|
682
|
-
safeExit('session list', 0);
|
|
683
|
-
}
|
|
684
|
-
catch (err) {
|
|
685
|
-
if (err instanceof ExitSignal)
|
|
686
|
-
throw err;
|
|
687
|
-
console.error(`❌ dexto session list command failed: ${err}`);
|
|
688
|
-
safeExit('session list', 1, 'error');
|
|
689
|
-
}
|
|
690
|
-
}));
|
|
691
|
-
sessionCommand
|
|
692
|
-
.command('history')
|
|
693
|
-
.description('Show session history')
|
|
694
|
-
.argument('[sessionId]', 'Session ID (defaults to current session)')
|
|
695
|
-
.action(withAnalytics('session history', async (sessionId) => {
|
|
696
|
-
try {
|
|
697
|
-
const agent = await bootstrapAgentFromGlobalOpts();
|
|
698
|
-
await handleSessionHistoryCommand(agent, sessionId);
|
|
699
|
-
await agent.stop();
|
|
700
|
-
safeExit('session history', 0);
|
|
701
|
-
}
|
|
702
|
-
catch (err) {
|
|
703
|
-
if (err instanceof ExitSignal)
|
|
704
|
-
throw err;
|
|
705
|
-
console.error(`❌ dexto session history command failed: ${err}`);
|
|
706
|
-
safeExit('session history', 1, 'error');
|
|
707
|
-
}
|
|
708
|
-
}));
|
|
709
|
-
sessionCommand
|
|
710
|
-
.command('delete')
|
|
711
|
-
.description('Delete a session')
|
|
712
|
-
.argument('<sessionId>', 'Session ID to delete')
|
|
713
|
-
.action(withAnalytics('session delete', async (sessionId) => {
|
|
714
|
-
try {
|
|
715
|
-
const agent = await bootstrapAgentFromGlobalOpts();
|
|
716
|
-
await handleSessionDeleteCommand(agent, sessionId);
|
|
717
|
-
await agent.stop();
|
|
718
|
-
safeExit('session delete', 0);
|
|
719
|
-
}
|
|
720
|
-
catch (err) {
|
|
721
|
-
if (err instanceof ExitSignal)
|
|
722
|
-
throw err;
|
|
723
|
-
console.error(`❌ dexto session delete command failed: ${err}`);
|
|
724
|
-
safeExit('session delete', 1, 'error');
|
|
725
|
-
}
|
|
726
|
-
}));
|
|
727
|
-
// 12) `search` SUB-COMMAND
|
|
728
|
-
program
|
|
729
|
-
.command('search')
|
|
730
|
-
.description('Search session history')
|
|
731
|
-
.argument('<query>', 'Search query')
|
|
732
|
-
.option('--session <sessionId>', 'Search in specific session')
|
|
733
|
-
.option('--role <role>', 'Filter by role (user, assistant, system, tool)')
|
|
734
|
-
.option('--limit <number>', 'Limit number of results', '10')
|
|
735
|
-
.action(withAnalytics('search', async (query, options) => {
|
|
736
|
-
try {
|
|
737
|
-
const agent = await bootstrapAgentFromGlobalOpts();
|
|
738
|
-
const searchOptions = {};
|
|
739
|
-
if (options.session) {
|
|
740
|
-
searchOptions.sessionId = options.session;
|
|
741
|
-
}
|
|
742
|
-
if (options.role) {
|
|
743
|
-
const allowed = new Set(['user', 'assistant', 'system', 'tool']);
|
|
744
|
-
if (!allowed.has(options.role)) {
|
|
745
|
-
console.error(`❌ Invalid role: ${options.role}. Use one of: user, assistant, system, tool`);
|
|
746
|
-
safeExit('search', 1, 'invalid-role');
|
|
747
|
-
}
|
|
748
|
-
searchOptions.role = options.role;
|
|
749
|
-
}
|
|
750
|
-
if (options.limit) {
|
|
751
|
-
const parsed = parseInt(options.limit, 10);
|
|
752
|
-
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
753
|
-
console.error(`❌ Invalid --limit: ${options.limit}. Use a positive integer (e.g., 10).`);
|
|
754
|
-
safeExit('search', 1, 'invalid-limit');
|
|
755
|
-
}
|
|
756
|
-
searchOptions.limit = parsed;
|
|
757
|
-
}
|
|
758
|
-
await handleSessionSearchCommand(agent, query, searchOptions);
|
|
759
|
-
await agent.stop();
|
|
760
|
-
safeExit('search', 0);
|
|
761
|
-
}
|
|
762
|
-
catch (err) {
|
|
763
|
-
if (err instanceof ExitSignal)
|
|
764
|
-
throw err;
|
|
765
|
-
console.error(`❌ dexto search command failed: ${err}`);
|
|
766
|
-
safeExit('search', 1, 'error');
|
|
767
|
-
}
|
|
768
|
-
}));
|
|
769
|
-
// 13) `auth` SUB-COMMAND GROUP
|
|
770
|
-
const authCommand = program.command('auth').description('Manage authentication');
|
|
771
|
-
authCommand
|
|
772
|
-
.command('login')
|
|
773
|
-
.description('Login to Dexto')
|
|
774
|
-
.option('--api-key <key>', 'Use Dexto API key instead of browser login')
|
|
775
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
776
|
-
.action(withAnalytics('auth login', async (options) => {
|
|
777
|
-
try {
|
|
778
|
-
await handleLoginCommand(options);
|
|
779
|
-
safeExit('auth login', 0);
|
|
780
|
-
}
|
|
781
|
-
catch (err) {
|
|
782
|
-
if (err instanceof ExitSignal)
|
|
783
|
-
throw err;
|
|
784
|
-
console.error(`❌ dexto auth login command failed: ${err}`);
|
|
785
|
-
safeExit('auth login', 1, 'error');
|
|
786
|
-
}
|
|
787
|
-
}));
|
|
788
|
-
authCommand
|
|
789
|
-
.command('logout')
|
|
790
|
-
.description('Logout from Dexto')
|
|
791
|
-
.option('--force', 'Skip confirmation prompt')
|
|
792
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
793
|
-
.action(withAnalytics('auth logout', async (options) => {
|
|
794
|
-
try {
|
|
795
|
-
await handleLogoutCommand(options);
|
|
796
|
-
safeExit('auth logout', 0);
|
|
797
|
-
}
|
|
798
|
-
catch (err) {
|
|
799
|
-
if (err instanceof ExitSignal)
|
|
800
|
-
throw err;
|
|
801
|
-
console.error(`❌ dexto auth logout command failed: ${err}`);
|
|
802
|
-
safeExit('auth logout', 1, 'error');
|
|
803
|
-
}
|
|
804
|
-
}));
|
|
805
|
-
authCommand
|
|
806
|
-
.command('status')
|
|
807
|
-
.description('Show authentication status')
|
|
808
|
-
.action(withAnalytics('auth status', async () => {
|
|
809
|
-
try {
|
|
810
|
-
await handleStatusCommand();
|
|
811
|
-
safeExit('auth status', 0);
|
|
812
|
-
}
|
|
813
|
-
catch (err) {
|
|
814
|
-
if (err instanceof ExitSignal)
|
|
815
|
-
throw err;
|
|
816
|
-
console.error(`❌ dexto auth status command failed: ${err}`);
|
|
817
|
-
safeExit('auth status', 1, 'error');
|
|
818
|
-
}
|
|
819
|
-
}));
|
|
820
|
-
// Also add convenience aliases at root level
|
|
821
|
-
program
|
|
822
|
-
.command('login')
|
|
823
|
-
.description('Login to Dexto (alias for `dexto auth login`)')
|
|
824
|
-
.option('--api-key <key>', 'Use Dexto API key instead of browser login')
|
|
825
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
826
|
-
.action(withAnalytics('login', async (options) => {
|
|
827
|
-
try {
|
|
828
|
-
await handleLoginCommand(options);
|
|
829
|
-
safeExit('login', 0);
|
|
830
|
-
}
|
|
831
|
-
catch (err) {
|
|
832
|
-
if (err instanceof ExitSignal)
|
|
833
|
-
throw err;
|
|
834
|
-
console.error(`❌ dexto login command failed: ${err}`);
|
|
835
|
-
safeExit('login', 1, 'error');
|
|
836
|
-
}
|
|
837
|
-
}));
|
|
838
|
-
program
|
|
839
|
-
.command('logout')
|
|
840
|
-
.description('Logout from Dexto (alias for `dexto auth logout`)')
|
|
841
|
-
.option('--force', 'Skip confirmation prompt')
|
|
842
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
843
|
-
.action(withAnalytics('logout', async (options) => {
|
|
844
|
-
try {
|
|
845
|
-
await handleLogoutCommand(options);
|
|
846
|
-
safeExit('logout', 0);
|
|
847
|
-
}
|
|
848
|
-
catch (err) {
|
|
849
|
-
if (err instanceof ExitSignal)
|
|
850
|
-
throw err;
|
|
851
|
-
console.error(`❌ dexto logout command failed: ${err}`);
|
|
852
|
-
safeExit('logout', 1, 'error');
|
|
853
|
-
}
|
|
854
|
-
}));
|
|
855
|
-
// 14) `billing` COMMAND
|
|
856
|
-
program
|
|
857
|
-
.command('billing')
|
|
858
|
-
.description('Show billing status and credit balance')
|
|
859
|
-
.option('--buy', 'Open Dexto Nova credits purchase page')
|
|
860
|
-
.action(withAnalytics('billing', async (options) => {
|
|
861
|
-
try {
|
|
862
|
-
await handleBillingStatusCommand(options);
|
|
863
|
-
safeExit('billing', 0);
|
|
864
|
-
}
|
|
865
|
-
catch (err) {
|
|
866
|
-
if (err instanceof ExitSignal)
|
|
867
|
-
throw err;
|
|
868
|
-
console.error(`❌ dexto billing command failed: ${err}`);
|
|
869
|
-
safeExit('billing', 1, 'error');
|
|
870
|
-
}
|
|
871
|
-
}));
|
|
872
|
-
// 15) `mcp` SUB-COMMAND
|
|
873
|
-
// For now, this mode simply aggregates and re-expose tools from configured MCP servers (no agent)
|
|
874
|
-
// dexto --mode mcp will be moved to this sub-command in the future
|
|
875
|
-
program
|
|
876
|
-
.command('mcp')
|
|
877
|
-
.description('Start Dexto as an MCP server. Use --group-servers to aggregate and re-expose tools from configured MCP servers. \
|
|
878
|
-
In the future, this command will expose the agent as an MCP server by default.')
|
|
879
|
-
.option('-s, --strict', 'Require all MCP server connections to succeed')
|
|
880
|
-
.option('--group-servers', 'Aggregate and re-expose tools from configured MCP servers (required for now)')
|
|
881
|
-
.option('--name <n>', 'Name for the MCP server', 'dexto-tools')
|
|
882
|
-
.option('--version <version>', 'Version for the MCP server', '1.0.0')
|
|
883
|
-
.action(withAnalytics('mcp', async (options) => {
|
|
884
|
-
try {
|
|
885
|
-
// Validate that --group-servers flag is provided (mandatory for now)
|
|
886
|
-
if (!options.groupServers) {
|
|
887
|
-
console.error('❌ The --group-servers flag is required. This command currently only supports aggregating and re-exposing tools from configured MCP servers.');
|
|
888
|
-
console.error('Usage: dexto mcp --group-servers');
|
|
889
|
-
safeExit('mcp', 1, 'missing-group-servers');
|
|
890
|
-
}
|
|
891
|
-
// Load and resolve config
|
|
892
|
-
// Get the global agent option from the main program
|
|
893
|
-
const globalOpts = program.opts();
|
|
894
|
-
const nameOrPath = globalOpts.agent;
|
|
895
|
-
const configPath = await resolveAgentPath(nameOrPath, globalOpts.autoInstall !== false);
|
|
896
|
-
console.log(`📄 Loading Dexto config from: ${configPath}`);
|
|
897
|
-
const config = await loadAgentConfig(configPath);
|
|
898
|
-
logger.info(`Validating MCP servers...`);
|
|
899
|
-
// Validate that MCP servers are configured
|
|
900
|
-
if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) {
|
|
901
|
-
console.error('❌ No MCP servers configured. Please configure mcpServers in your config file.');
|
|
902
|
-
safeExit('mcp', 1, 'no-mcp-servers');
|
|
903
|
-
}
|
|
904
|
-
const { ServersConfigSchema } = await import('@dexto/core');
|
|
905
|
-
const validatedServers = ServersConfigSchema.parse(config.mcpServers);
|
|
906
|
-
logger.info(`Validated MCP servers. Configured servers: ${Object.keys(validatedServers).join(', ')}`);
|
|
907
|
-
// Logs are already redirected to file by default to prevent interference with stdio transport
|
|
908
|
-
const currentLogPath = logger.getLogFilePath();
|
|
909
|
-
logger.info(`MCP mode using log file: ${currentLogPath || 'default .dexto location'}`);
|
|
910
|
-
logger.info(`Starting MCP tool aggregation server: ${options.name} v${options.version}`);
|
|
911
|
-
// Create stdio transport for MCP tool aggregation
|
|
912
|
-
const mcpTransport = await createMcpTransport('stdio');
|
|
913
|
-
// Initialize tool aggregation server
|
|
914
|
-
await initializeMcpToolAggregationServer(validatedServers, mcpTransport, options.name, options.version, options.strict);
|
|
915
|
-
logger.info('MCP tool aggregation server started successfully');
|
|
916
|
-
}
|
|
917
|
-
catch (err) {
|
|
918
|
-
if (err instanceof ExitSignal)
|
|
919
|
-
throw err;
|
|
920
|
-
// Write to stderr to avoid interfering with MCP protocol
|
|
921
|
-
process.stderr.write(`MCP tool aggregation server startup failed: ${err}\n`);
|
|
922
|
-
safeExit('mcp', 1, 'mcp-agg-failed');
|
|
923
|
-
}
|
|
924
|
-
}, { timeoutMs: 0 }));
|
|
925
|
-
// 16) Main dexto CLI - Interactive/One shot (CLI/HEADLESS) or run in other modes (--mode web/server/mcp)
|
|
332
|
+
// 11) Runtime commands (`run`, `session`, `search`, `auth`, `billing`)
|
|
333
|
+
const runtimeCommandContext = {
|
|
334
|
+
program,
|
|
335
|
+
cliVersion,
|
|
336
|
+
bootstrapAgentFromGlobalOpts,
|
|
337
|
+
};
|
|
338
|
+
registerRunCommand(runtimeCommandContext);
|
|
339
|
+
registerSessionCommand(runtimeCommandContext);
|
|
340
|
+
registerSearchCommand(runtimeCommandContext);
|
|
341
|
+
registerAuthCommand(runtimeCommandContext);
|
|
342
|
+
registerBillingCommand(runtimeCommandContext);
|
|
343
|
+
registerMcpCommand({ program });
|
|
344
|
+
// 13) Main dexto CLI - Interactive (CLI) or run in other modes (--mode web/server/mcp)
|
|
926
345
|
program
|
|
927
346
|
// Main customer facing description
|
|
928
347
|
.description('Dexto CLI - AI-powered assistant with session management.\n\n' +
|
|
929
348
|
'Basic Usage:\n' +
|
|
930
349
|
' dexto Start web UI (default)\n' +
|
|
931
350
|
' dexto --mode cli Start interactive CLI\n' +
|
|
932
|
-
' dexto --prompt "query" Start interactive CLI and run the prompt\n
|
|
351
|
+
' dexto --prompt "query" Start interactive CLI and run the prompt\n' +
|
|
352
|
+
' dexto run "query" Run one-off headless task\n\n' +
|
|
933
353
|
'Session Management Commands:\n' +
|
|
934
354
|
' dexto session list List all sessions\n' +
|
|
935
355
|
' dexto session history [id] Show session history\n' +
|
|
@@ -949,6 +369,9 @@ program
|
|
|
949
369
|
' dexto --mode mcp Run as MCP server\n\n' +
|
|
950
370
|
'Docs: https://docs.dexto.ai')
|
|
951
371
|
.action(withAnalytics('main', async () => {
|
|
372
|
+
await ensureDextoApiKeyBootstrap();
|
|
373
|
+
await ensureImageImporterConfigured();
|
|
374
|
+
ensureLlmRegistryAutoUpdateStarted();
|
|
952
375
|
// ——— ENV CHECK (optional) ———
|
|
953
376
|
if (!existsSync('.env')) {
|
|
954
377
|
logger.debug('WARNING: .env file not found; copy .env.example and set your API keys.');
|
|
@@ -994,7 +417,7 @@ program
|
|
|
994
417
|
// The interactive CLI requires a TTY (Ink uses stdin for keypress input).
|
|
995
418
|
if (opts.mode === 'cli' && !process.stdin.isTTY) {
|
|
996
419
|
console.error('❌ Interactive CLI requires a TTY.');
|
|
997
|
-
console.error('💡
|
|
420
|
+
console.error('💡 For non-interactive runs, use `dexto run "<prompt>"`, or use --mode server for automation.');
|
|
998
421
|
safeExit('main', 1, 'no-tty');
|
|
999
422
|
}
|
|
1000
423
|
// ——— Infer provider & API key from model ———
|
|
@@ -1073,12 +496,14 @@ program
|
|
|
1073
496
|
}
|
|
1074
497
|
// Check setup state and auto-trigger if needed
|
|
1075
498
|
// Skip if --skip-setup flag is set (for MCP mode, automation, etc.)
|
|
499
|
+
const { requiresSetup } = await import('./cli/utils/setup-utils.js');
|
|
1076
500
|
if (!opts.skipSetup && (await requiresSetup())) {
|
|
1077
501
|
if (opts.interactive === false) {
|
|
1078
502
|
console.error('❌ Setup required but --no-interactive flag is set.');
|
|
1079
503
|
console.error('💡 Run `dexto setup` first, or use --skip-setup to bypass global setup.');
|
|
1080
504
|
safeExit('main', 1, 'setup-required-non-interactive');
|
|
1081
505
|
}
|
|
506
|
+
const { handleSetupCommand } = await import('./cli/commands/setup.js');
|
|
1082
507
|
await handleSetupCommand({ interactive: true });
|
|
1083
508
|
// Reload preferences after setup to get the newly selected default mode
|
|
1084
509
|
// (setup may have just saved a different mode than the default 'web')
|
|
@@ -1124,7 +549,7 @@ program
|
|
|
1124
549
|
if (authCheck.action === 'login') {
|
|
1125
550
|
// User wants to log in - run login flow then restart
|
|
1126
551
|
const { handleLoginCommand } = await import('./cli/commands/auth/login.js');
|
|
1127
|
-
await handleLoginCommand(
|
|
552
|
+
await handleLoginCommand();
|
|
1128
553
|
// Verify key was actually provisioned (provisionKeys silently catches errors)
|
|
1129
554
|
const { canUseDextoProvider } = await import('./cli/utils/dexto-setup.js');
|
|
1130
555
|
if (!(await canUseDextoProvider())) {
|
|
@@ -1297,6 +722,7 @@ program
|
|
|
1297
722
|
// Config is already enriched and validated - ready for agent creation
|
|
1298
723
|
// DextoAgent will parse/validate again (parse-twice pattern)
|
|
1299
724
|
// isInteractiveMode is already defined above for validateAgentConfig
|
|
725
|
+
const { createFileSessionLoggerFactory } = await import('./utils/session-logger-factory.js');
|
|
1300
726
|
const sessionLoggerFactory = createFileSessionLoggerFactory();
|
|
1301
727
|
const mcpAuthProviderFactory = opts.mode === 'cli'
|
|
1302
728
|
? (await import('./cli/mcp/oauth-factory.js')).createMcpAuthProviderFactory({
|
|
@@ -1332,290 +758,23 @@ program
|
|
|
1332
758
|
console.error(`❌ Configuration Error: ${err.message}`);
|
|
1333
759
|
safeExit('main', 1, 'config-error');
|
|
1334
760
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
logger.debug('CLI approval handler configured for Ink CLI');
|
|
1353
|
-
}
|
|
1354
|
-
// Start the agent now that approval handler is configured
|
|
1355
|
-
await agent.start();
|
|
1356
|
-
// Session management - CLI uses explicit sessionId like WebUI
|
|
1357
|
-
// NOTE: Migrated from defaultSession pattern which will be deprecated in core
|
|
1358
|
-
// We now pass sessionId explicitly to all agent methods (agent.run, agent.switchLLM, etc.)
|
|
1359
|
-
// Check if API key is configured before trying to create session
|
|
1360
|
-
// Session creation triggers LLM service init which requires API key
|
|
1361
|
-
const llmConfig = agent.getCurrentLLMConfig();
|
|
1362
|
-
const { requiresApiKey } = await import('@dexto/core');
|
|
1363
|
-
if (requiresApiKey(llmConfig.provider) && !llmConfig.apiKey?.trim()) {
|
|
1364
|
-
// Offer interactive API key setup instead of just exiting
|
|
1365
|
-
const { interactiveApiKeySetup } = await import('./cli/utils/api-key-setup.js');
|
|
1366
|
-
console.log(chalk.yellow(`\n⚠️ API key required for provider '${llmConfig.provider}'\n`));
|
|
1367
|
-
const setupResult = await interactiveApiKeySetup(llmConfig.provider, {
|
|
1368
|
-
exitOnCancel: false,
|
|
1369
|
-
model: llmConfig.model,
|
|
1370
|
-
});
|
|
1371
|
-
if (setupResult.cancelled) {
|
|
1372
|
-
await agent.stop().catch(() => { });
|
|
1373
|
-
safeExit('main', 0, 'api-key-setup-cancelled');
|
|
1374
|
-
}
|
|
1375
|
-
if (setupResult.skipped) {
|
|
1376
|
-
// User chose to skip - exit with instructions
|
|
1377
|
-
await agent.stop().catch(() => { });
|
|
1378
|
-
safeExit('main', 0, 'api-key-pending');
|
|
1379
|
-
}
|
|
1380
|
-
if (setupResult.success && setupResult.apiKey) {
|
|
1381
|
-
// API key was entered and saved - reload config and continue
|
|
1382
|
-
// Update the agent's LLM config with the new API key
|
|
1383
|
-
await agent.switchLLM({
|
|
1384
|
-
provider: llmConfig.provider,
|
|
1385
|
-
model: llmConfig.model,
|
|
1386
|
-
apiKey: setupResult.apiKey,
|
|
1387
|
-
});
|
|
1388
|
-
logger.info('API key configured successfully, continuing...');
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
// Resolve the initial session
|
|
1392
|
-
let cliSessionId;
|
|
1393
|
-
if (opts.resume) {
|
|
1394
|
-
const existing = await agent.getSession(opts.resume);
|
|
1395
|
-
if (!existing) {
|
|
1396
|
-
console.error(`❌ Session '${opts.resume}' not found`);
|
|
1397
|
-
console.error('💡 Use `dexto session list` to see available sessions');
|
|
1398
|
-
safeExit('main', 1, 'resume-failed');
|
|
1399
|
-
}
|
|
1400
|
-
cliSessionId = opts.resume;
|
|
1401
|
-
}
|
|
1402
|
-
else if (opts.continue) {
|
|
1403
|
-
const mostRecentSessionId = await getMostRecentSessionId(agent);
|
|
1404
|
-
if (mostRecentSessionId) {
|
|
1405
|
-
cliSessionId = mostRecentSessionId;
|
|
1406
|
-
}
|
|
1407
|
-
else {
|
|
1408
|
-
const session = await agent.createSession();
|
|
1409
|
-
cliSessionId = session.id;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
const session = await agent.createSession();
|
|
1414
|
-
cliSessionId = session.id;
|
|
1415
|
-
}
|
|
1416
|
-
// Check for updates (will be shown in Ink header)
|
|
1417
|
-
const cliUpdateInfo = await versionCheckPromise;
|
|
1418
|
-
// Check if installed agents differ from bundled and prompt to sync
|
|
1419
|
-
const needsSync = await shouldPromptForSync();
|
|
1420
|
-
if (needsSync) {
|
|
1421
|
-
const shouldSync = await p.confirm({
|
|
1422
|
-
message: 'Agent config updates available. Sync now?',
|
|
1423
|
-
initialValue: true,
|
|
1424
|
-
});
|
|
1425
|
-
if (!p.isCancel(shouldSync) && shouldSync) {
|
|
1426
|
-
await handleSyncAgentsCommand({ force: true, quiet: true });
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
// Interactive mode - use Ink CLI with session support
|
|
1430
|
-
// Suppress console output before starting Ink UI
|
|
1431
|
-
const originalConsole = {
|
|
1432
|
-
log: console.log,
|
|
1433
|
-
error: console.error,
|
|
1434
|
-
warn: console.warn,
|
|
1435
|
-
info: console.info,
|
|
1436
|
-
};
|
|
1437
|
-
const noOp = () => { };
|
|
1438
|
-
console.log = noOp;
|
|
1439
|
-
console.error = noOp;
|
|
1440
|
-
console.warn = noOp;
|
|
1441
|
-
console.info = noOp;
|
|
1442
|
-
let inkError = undefined;
|
|
1443
|
-
try {
|
|
1444
|
-
const [{ startInkCliRefactored, setTuiRuntimeServices }, { registerGracefulShutdown }, { applyLayeredEnvironmentLoading }, { getProviderDisplayName, isValidApiKeyFormat, getProviderInstructions, }, { beginOAuthLogin, DEFAULT_OAUTH_CONFIG, ensureDextoApiKeyForAuthToken, loadAuth, storeAuth, removeAuth, removeDextoApiKeyFromEnv, }, { isUsingDextoCredits }, { canUseDextoProvider },] = await Promise.all([
|
|
1445
|
-
import('@dexto/tui'),
|
|
1446
|
-
import('./utils/graceful-shutdown.js'),
|
|
1447
|
-
import('./utils/env.js'),
|
|
1448
|
-
import('./cli/utils/provider-setup.js'),
|
|
1449
|
-
import('./cli/auth/index.js'),
|
|
1450
|
-
import('./config/effective-llm.js'),
|
|
1451
|
-
import('./cli/utils/dexto-setup.js'),
|
|
1452
|
-
]);
|
|
1453
|
-
setTuiRuntimeServices({
|
|
1454
|
-
registerGracefulShutdown,
|
|
1455
|
-
capture: (event, properties) => {
|
|
1456
|
-
capture(event, properties);
|
|
1457
|
-
},
|
|
1458
|
-
applyLayeredEnvironmentLoading,
|
|
1459
|
-
getProviderDisplayName,
|
|
1460
|
-
isValidApiKeyFormat,
|
|
1461
|
-
getProviderInstructions,
|
|
1462
|
-
beginOAuthLogin,
|
|
1463
|
-
defaultOAuthConfig: DEFAULT_OAUTH_CONFIG,
|
|
1464
|
-
ensureDextoApiKeyForAuthToken,
|
|
1465
|
-
loadAuth,
|
|
1466
|
-
storeAuth,
|
|
1467
|
-
removeAuth,
|
|
1468
|
-
removeDextoApiKeyFromEnv,
|
|
1469
|
-
isUsingDextoCredits,
|
|
1470
|
-
canUseDextoProvider,
|
|
1471
|
-
});
|
|
1472
|
-
await startInkCliRefactored(agent, cliSessionId, {
|
|
1473
|
-
updateInfo: cliUpdateInfo ?? undefined,
|
|
1474
|
-
configFilePath: resolvedPath,
|
|
1475
|
-
...(initialPrompt && { initialPrompt }),
|
|
1476
|
-
bypassPermissions: opts.bypassPermissions,
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
catch (error) {
|
|
1480
|
-
inkError = error;
|
|
1481
|
-
}
|
|
1482
|
-
finally {
|
|
1483
|
-
// Restore console methods so any errors are visible
|
|
1484
|
-
console.log = originalConsole.log;
|
|
1485
|
-
console.error = originalConsole.error;
|
|
1486
|
-
console.warn = originalConsole.warn;
|
|
1487
|
-
console.info = originalConsole.info;
|
|
1488
|
-
}
|
|
1489
|
-
// Stop the agent after Ink CLI exits
|
|
1490
|
-
try {
|
|
1491
|
-
await agent.stop();
|
|
1492
|
-
}
|
|
1493
|
-
catch {
|
|
1494
|
-
// Ignore shutdown errors
|
|
1495
|
-
}
|
|
1496
|
-
// Handle any errors from Ink CLI
|
|
1497
|
-
if (inkError) {
|
|
1498
|
-
if (inkError instanceof ExitSignal)
|
|
1499
|
-
throw inkError;
|
|
1500
|
-
const errorMessage = inkError instanceof Error ? inkError.message : String(inkError);
|
|
1501
|
-
console.error(`❌ Ink CLI failed: ${errorMessage}`);
|
|
1502
|
-
if (inkError instanceof Error && inkError.stack) {
|
|
1503
|
-
console.error(inkError.stack);
|
|
1504
|
-
}
|
|
1505
|
-
safeExit('main', 1, 'ink-cli-error');
|
|
1506
|
-
}
|
|
1507
|
-
safeExit('main', 0);
|
|
1508
|
-
}
|
|
1509
|
-
// falls through - safeExit returns never, but eslint doesn't know that
|
|
1510
|
-
case 'web': {
|
|
1511
|
-
// Default to 3000 for web mode
|
|
1512
|
-
const defaultPort = opts.port ? parseInt(opts.port, 10) : 3000;
|
|
1513
|
-
const port = getPort(process.env.PORT, defaultPort, 'PORT');
|
|
1514
|
-
const serverUrl = process.env.DEXTO_URL ?? `http://localhost:${port}`;
|
|
1515
|
-
// Resolve webRoot path (embedded WebUI dist folder)
|
|
1516
|
-
const webRoot = resolveWebRoot();
|
|
1517
|
-
if (!webRoot) {
|
|
1518
|
-
console.warn(chalk.yellow('⚠️ WebUI not found in this build.'));
|
|
1519
|
-
console.info('For production: Run "pnpm build:all" to embed the WebUI');
|
|
1520
|
-
console.info('For development: Run "pnpm dev" for hot reload');
|
|
1521
|
-
}
|
|
1522
|
-
// Build WebUI runtime config (analytics, etc.) for injection into index.html
|
|
1523
|
-
const webUIConfig = webRoot
|
|
1524
|
-
? { analytics: await getWebUIAnalyticsConfig() }
|
|
1525
|
-
: undefined;
|
|
1526
|
-
// Start single Hono server serving both API and WebUI
|
|
1527
|
-
await startHonoApiServer(agent, port, agent.config.agentCard || {}, derivedAgentId, resolvedPath, webRoot, webUIConfig);
|
|
1528
|
-
console.log(chalk.green(`✅ Server running at ${serverUrl}`));
|
|
1529
|
-
// Show update notification if available
|
|
1530
|
-
const webUpdateInfo = await versionCheckPromise;
|
|
1531
|
-
if (webUpdateInfo) {
|
|
1532
|
-
displayUpdateNotification(webUpdateInfo);
|
|
1533
|
-
}
|
|
1534
|
-
// Open WebUI in browser if webRoot is available
|
|
1535
|
-
if (webRoot) {
|
|
1536
|
-
try {
|
|
1537
|
-
const { default: open } = await import('open');
|
|
1538
|
-
await open(serverUrl, { wait: false });
|
|
1539
|
-
console.log(chalk.green(`🌐 Opened WebUI in browser: ${serverUrl}`));
|
|
1540
|
-
}
|
|
1541
|
-
catch (_error) {
|
|
1542
|
-
console.log(chalk.yellow(`💡 WebUI is available at: ${serverUrl}`));
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
break;
|
|
1546
|
-
}
|
|
1547
|
-
// Start server with REST APIs and SSE on port 3001
|
|
1548
|
-
// This also enables dexto to be used as a remote mcp server at localhost:3001/mcp
|
|
1549
|
-
case 'server': {
|
|
1550
|
-
// Start server with REST APIs and SSE only
|
|
1551
|
-
const agentCard = agent.config.agentCard ?? {};
|
|
1552
|
-
// Default to 3001 for server mode
|
|
1553
|
-
const defaultPort = opts.port ? parseInt(opts.port, 10) : 3001;
|
|
1554
|
-
const apiPort = getPort(process.env.PORT, defaultPort, 'PORT');
|
|
1555
|
-
const apiUrl = process.env.DEXTO_URL ?? `http://localhost:${apiPort}`;
|
|
1556
|
-
console.log('🌐 Starting server (REST APIs + SSE)...');
|
|
1557
|
-
await startHonoApiServer(agent, apiPort, agentCard, derivedAgentId, resolvedPath);
|
|
1558
|
-
console.log(`✅ Server running at ${apiUrl}`);
|
|
1559
|
-
console.log('Available endpoints:');
|
|
1560
|
-
console.log(' POST /api/message - Send async message');
|
|
1561
|
-
console.log(' POST /api/message-sync - Send sync message');
|
|
1562
|
-
console.log(' POST /api/reset - Reset conversation');
|
|
1563
|
-
console.log(' GET /api/mcp/servers - List MCP servers');
|
|
1564
|
-
console.log(' SSE support available for real-time events');
|
|
1565
|
-
// Show update notification if available
|
|
1566
|
-
const serverUpdateInfo = await versionCheckPromise;
|
|
1567
|
-
if (serverUpdateInfo) {
|
|
1568
|
-
displayUpdateNotification(serverUpdateInfo);
|
|
1569
|
-
}
|
|
1570
|
-
break;
|
|
1571
|
-
}
|
|
1572
|
-
// TODO: Remove if server mode is stable and supports mcp
|
|
1573
|
-
// Starts dexto as a local mcp server
|
|
1574
|
-
// Use `dexto --mode mcp` to start dexto as a local mcp server
|
|
1575
|
-
// Use `dexto --mode server` to start dexto as a remote server
|
|
1576
|
-
case 'mcp': {
|
|
1577
|
-
// Start stdio mcp server only
|
|
1578
|
-
const agentCardConfig = agent.config.agentCard || {
|
|
1579
|
-
name: 'dexto',
|
|
1580
|
-
version: '1.0.0',
|
|
1581
|
-
};
|
|
1582
|
-
try {
|
|
1583
|
-
// Logs are already redirected to file by default to prevent interference with stdio transport
|
|
1584
|
-
const agentCardData = createAgentCard({
|
|
1585
|
-
defaultName: agentCardConfig.name ?? 'dexto',
|
|
1586
|
-
defaultVersion: agentCardConfig.version ?? '1.0.0',
|
|
1587
|
-
defaultBaseUrl: 'stdio://local-dexto',
|
|
1588
|
-
}, agentCardConfig // preserve overrides from agent file
|
|
1589
|
-
);
|
|
1590
|
-
// Use stdio transport in mcp mode
|
|
1591
|
-
const mcpTransport = await createMcpTransport('stdio');
|
|
1592
|
-
await initializeMcpServer(agent, agentCardData, mcpTransport);
|
|
1593
|
-
}
|
|
1594
|
-
catch (err) {
|
|
1595
|
-
// Write to stderr instead of stdout to avoid interfering with MCP protocol
|
|
1596
|
-
process.stderr.write(`MCP server startup failed: ${err}\n`);
|
|
1597
|
-
safeExit('main', 1, 'mcp-startup-failed');
|
|
1598
|
-
}
|
|
1599
|
-
break;
|
|
1600
|
-
}
|
|
1601
|
-
default:
|
|
1602
|
-
if (opts.mode === 'discord' || opts.mode === 'telegram') {
|
|
1603
|
-
console.error(`❌ Error: '${opts.mode}' mode has been moved to examples`);
|
|
1604
|
-
console.error('');
|
|
1605
|
-
console.error(`The ${opts.mode} bot is now a standalone example that you can customize.`);
|
|
1606
|
-
console.error('');
|
|
1607
|
-
console.error(`📖 See: examples/${opts.mode}-bot/README.md`);
|
|
1608
|
-
console.error('');
|
|
1609
|
-
console.error(`To run it:`);
|
|
1610
|
-
console.error(` cd examples/${opts.mode}-bot`);
|
|
1611
|
-
console.error(` pnpm install`);
|
|
1612
|
-
console.error(` pnpm start`);
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1615
|
-
console.error(`❌ Unknown mode '${opts.mode}'. Use web, cli, server, or mcp.`);
|
|
1616
|
-
}
|
|
1617
|
-
safeExit('main', 1, 'unknown-mode');
|
|
1618
|
-
}
|
|
761
|
+
const { dispatchMainMode } = await import('./cli/modes/dispatch.js');
|
|
762
|
+
const mainModeOpts = {
|
|
763
|
+
mode: opts.mode,
|
|
764
|
+
port: opts.port,
|
|
765
|
+
resume: opts.resume,
|
|
766
|
+
continue: opts.continue,
|
|
767
|
+
bypassPermissions: opts.bypassPermissions,
|
|
768
|
+
};
|
|
769
|
+
await dispatchMainMode({
|
|
770
|
+
agent,
|
|
771
|
+
opts: mainModeOpts,
|
|
772
|
+
validatedConfig,
|
|
773
|
+
resolvedPath,
|
|
774
|
+
derivedAgentId,
|
|
775
|
+
initialPrompt,
|
|
776
|
+
getVersionCheckResult,
|
|
777
|
+
});
|
|
1619
778
|
}, { timeoutMs: 0 }));
|
|
1620
|
-
//
|
|
779
|
+
// 14) PARSE & EXECUTE
|
|
1621
780
|
program.parseAsync(process.argv);
|