dexto 1.6.11 → 1.6.13
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 +10 -2
- package/dist/analytics/events.d.ts +1 -1
- package/dist/analytics/events.d.ts.map +1 -1
- package/dist/cli/auth/api-client.d.ts +0 -2
- package/dist/cli/auth/api-client.d.ts.map +1 -1
- package/dist/cli/auth/api-client.js +7 -11
- package/dist/cli/auth/constants.d.ts +4 -4
- package/dist/cli/auth/constants.js +4 -4
- package/dist/cli/commands/agents/install.d.ts.map +1 -0
- package/dist/cli/commands/{install.js → agents/install.js} +4 -4
- package/dist/cli/commands/{list-agents.d.ts → agents/list.d.ts} +1 -1
- package/dist/cli/commands/agents/list.d.ts.map +1 -0
- package/dist/cli/commands/{list-agents.js → agents/list.js} +2 -2
- package/dist/cli/commands/agents/register.js +5 -5
- package/dist/cli/commands/{sync-agents.d.ts → agents/sync.d.ts} +10 -2
- package/dist/cli/commands/agents/sync.d.ts.map +1 -0
- package/dist/cli/commands/{sync-agents.js → agents/sync.js} +54 -5
- package/dist/cli/commands/agents/uninstall.d.ts +18 -0
- package/dist/cli/commands/agents/uninstall.d.ts.map +1 -0
- package/dist/cli/commands/agents/uninstall.js +141 -0
- package/dist/cli/commands/deploy/client.d.ts +44 -0
- package/dist/cli/commands/deploy/client.d.ts.map +1 -0
- package/dist/cli/commands/deploy/client.js +232 -0
- package/dist/cli/commands/deploy/config.d.ts +81 -0
- package/dist/cli/commands/deploy/config.d.ts.map +1 -0
- package/dist/cli/commands/deploy/config.js +144 -0
- package/dist/cli/commands/deploy/entry-agent.d.ts +3 -0
- package/dist/cli/commands/deploy/entry-agent.d.ts.map +1 -0
- package/dist/cli/commands/deploy/entry-agent.js +22 -0
- package/dist/cli/commands/deploy/index.d.ts +9 -0
- package/dist/cli/commands/deploy/index.d.ts.map +1 -0
- package/dist/cli/commands/deploy/index.js +204 -0
- package/dist/cli/commands/deploy/links.d.ts +3 -0
- package/dist/cli/commands/deploy/links.d.ts.map +1 -0
- package/dist/cli/commands/deploy/links.js +53 -0
- package/dist/cli/commands/deploy/register.d.ts +6 -0
- package/dist/cli/commands/deploy/register.d.ts.map +1 -0
- package/dist/cli/commands/deploy/register.js +75 -0
- package/dist/cli/commands/deploy/snapshot.d.ts +12 -0
- package/dist/cli/commands/deploy/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/deploy/snapshot.js +76 -0
- package/dist/cli/commands/deploy/state.d.ts +21 -0
- package/dist/cli/commands/deploy/state.d.ts.map +1 -0
- package/dist/cli/commands/deploy/state.js +122 -0
- package/dist/cli/commands/index.d.ts +6 -4
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +304 -31
- package/dist/cli/commands/uninstall.d.ts +9 -12
- package/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/dist/cli/commands/uninstall.js +99 -113
- package/dist/cli/commands/upgrade.d.ts +15 -0
- package/dist/cli/commands/upgrade.d.ts.map +1 -0
- package/dist/cli/commands/upgrade.js +106 -0
- package/dist/cli/modes/cli.d.ts.map +1 -1
- package/dist/cli/modes/cli.js +0 -12
- package/dist/cli/utils/config-validation.d.ts.map +1 -1
- package/dist/cli/utils/config-validation.js +34 -20
- package/dist/cli/utils/self-management.d.ts +93 -0
- package/dist/cli/utils/self-management.d.ts.map +1 -0
- package/dist/cli/utils/self-management.js +423 -0
- package/dist/cli/utils/version-check.d.ts +1 -1
- package/dist/cli/utils/version-check.d.ts.map +1 -1
- package/dist/cli/utils/version-check.js +53 -19
- package/dist/index-main.js +59 -2
- package/dist/webui/assets/{index-CNiOYnOb.js → index-UDAdxmci.js} +187 -187
- package/dist/webui/index.html +1 -1
- package/package.json +13 -11
- package/dist/cli/commands/install.d.ts.map +0 -1
- package/dist/cli/commands/list-agents.d.ts.map +0 -1
- package/dist/cli/commands/sync-agents.d.ts.map +0 -1
- /package/dist/cli/commands/{install.d.ts → agents/install.d.ts} +0 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const DEXTO_BINARY = process.platform === 'win32' ? 'dexto.exe' : 'dexto';
|
|
7
|
+
const DEXTO_PATH_COMMAND = process.platform === 'win32' ? 'where' : 'which';
|
|
8
|
+
const DEXTO_PATH_ARGS = process.platform === 'win32' ? ['dexto'] : ['-a', 'dexto'];
|
|
9
|
+
const DEFAULT_NATIVE_INSTALL_URL = 'https://dexto.ai/install';
|
|
10
|
+
const DEFAULT_WINDOWS_INSTALL_URL = 'https://dexto.ai/install.ps1';
|
|
11
|
+
const InstallMetadataSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
schemaVersion: z.number().int().positive().default(1),
|
|
14
|
+
method: z.enum(['native', 'npm']),
|
|
15
|
+
installedPath: z.string().min(1),
|
|
16
|
+
installedAt: z.string().min(1),
|
|
17
|
+
version: z.string().min(1),
|
|
18
|
+
sourceUrl: z.string().min(1).optional(),
|
|
19
|
+
releaseTag: z.string().min(1).optional(),
|
|
20
|
+
platform: z.string().min(1).optional(),
|
|
21
|
+
arch: z.string().min(1).optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export async function executeCommand(command, args, options = {}) {
|
|
25
|
+
const stdio = options.stdio ?? 'pipe';
|
|
26
|
+
return await new Promise((resolve) => {
|
|
27
|
+
const child = spawn(command, args, {
|
|
28
|
+
cwd: options.cwd,
|
|
29
|
+
env: options.env,
|
|
30
|
+
stdio,
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
});
|
|
33
|
+
let stdout = '';
|
|
34
|
+
let stderr = '';
|
|
35
|
+
if (stdio === 'pipe') {
|
|
36
|
+
if (child.stdout) {
|
|
37
|
+
child.stdout.on('data', (chunk) => {
|
|
38
|
+
stdout += chunk.toString();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (child.stderr) {
|
|
42
|
+
child.stderr.on('data', (chunk) => {
|
|
43
|
+
stderr += chunk.toString();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
child.on('error', (error) => {
|
|
48
|
+
resolve({
|
|
49
|
+
code: -1,
|
|
50
|
+
stdout,
|
|
51
|
+
stderr: `${stderr}\n${error.message}`.trim(),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
child.on('close', (code) => {
|
|
55
|
+
resolve({
|
|
56
|
+
code: code ?? -1,
|
|
57
|
+
stdout,
|
|
58
|
+
stderr,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export async function executeManagedCommand(commandSpec, options) {
|
|
64
|
+
if (options.dryRun) {
|
|
65
|
+
console.log(`[dry-run] ${commandSpec.displayCommand}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const executeOptions = { stdio: 'inherit' };
|
|
69
|
+
if (options.cwd) {
|
|
70
|
+
executeOptions.cwd = options.cwd;
|
|
71
|
+
}
|
|
72
|
+
if (commandSpec.env) {
|
|
73
|
+
executeOptions.env = commandSpec.env;
|
|
74
|
+
}
|
|
75
|
+
const result = await executeCommand(commandSpec.command, commandSpec.args, executeOptions);
|
|
76
|
+
if (result.code !== 0) {
|
|
77
|
+
throw new Error(`Command failed: ${commandSpec.displayCommand}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function getDextoHomePath() {
|
|
81
|
+
return path.join(os.homedir(), '.dexto');
|
|
82
|
+
}
|
|
83
|
+
export async function readInstallMetadata() {
|
|
84
|
+
const metadataPath = path.join(getDextoHomePath(), 'install.json');
|
|
85
|
+
try {
|
|
86
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
87
|
+
const parsed = JSON.parse(content);
|
|
88
|
+
const validated = InstallMetadataSchema.safeParse(parsed);
|
|
89
|
+
if (!validated.success) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return validated.data;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function normalizePathForComparison(targetPath) {
|
|
99
|
+
const normalized = path.normalize(targetPath);
|
|
100
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
101
|
+
}
|
|
102
|
+
function uniquePaths(paths) {
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const result = [];
|
|
105
|
+
for (const candidate of paths) {
|
|
106
|
+
const normalized = normalizePathForComparison(candidate);
|
|
107
|
+
if (seen.has(normalized)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
seen.add(normalized);
|
|
111
|
+
result.push(path.normalize(candidate));
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
async function getBinaryPathsFromPath() {
|
|
116
|
+
const result = await executeCommand(DEXTO_PATH_COMMAND, DEXTO_PATH_ARGS);
|
|
117
|
+
if (result.code !== 0) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const candidates = result.stdout
|
|
121
|
+
.split(/\r?\n/)
|
|
122
|
+
.map((entry) => entry.trim())
|
|
123
|
+
.filter((entry) => entry.length > 0);
|
|
124
|
+
return uniquePaths(candidates);
|
|
125
|
+
}
|
|
126
|
+
function inferInstallMethodFromPath(binaryPath) {
|
|
127
|
+
const normalized = normalizePathForComparison(binaryPath);
|
|
128
|
+
if (normalized.includes('.dexto') || normalized.includes(path.join('.local', 'bin'))) {
|
|
129
|
+
return 'native';
|
|
130
|
+
}
|
|
131
|
+
return 'unknown';
|
|
132
|
+
}
|
|
133
|
+
export function isProjectLocalBinaryPath(binaryPath) {
|
|
134
|
+
const normalized = normalizePathForSignatureMatch(binaryPath);
|
|
135
|
+
return (normalized.includes('/node_modules/.bin/') || normalized.includes('/node_modules/dexto/'));
|
|
136
|
+
}
|
|
137
|
+
function pathStartsWith(targetPath, candidatePrefix) {
|
|
138
|
+
const normalizedTarget = normalizePathForComparison(targetPath);
|
|
139
|
+
const normalizedPrefix = normalizePathForComparison(candidatePrefix);
|
|
140
|
+
return (normalizedTarget === normalizedPrefix ||
|
|
141
|
+
normalizedTarget.startsWith(`${normalizedPrefix}${path.sep}`));
|
|
142
|
+
}
|
|
143
|
+
function pathBasenameIsDexto(targetPath) {
|
|
144
|
+
const basename = path.basename(targetPath).toLowerCase();
|
|
145
|
+
return basename === 'dexto' || basename === 'dexto.exe' || basename === 'dexto.cmd';
|
|
146
|
+
}
|
|
147
|
+
async function detectNodePackageManagerFromPath(binaryPath) {
|
|
148
|
+
const npmPrefix = await executeCommand('npm', ['prefix', '-g']);
|
|
149
|
+
if (npmPrefix.code === 0) {
|
|
150
|
+
const prefix = npmPrefix.stdout.trim();
|
|
151
|
+
if (prefix.length > 0) {
|
|
152
|
+
const npmBinDir = process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
|
|
153
|
+
if (pathStartsWith(binaryPath, npmBinDir)) {
|
|
154
|
+
return 'npm';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function normalizePathForSignatureMatch(targetPath) {
|
|
161
|
+
return normalizePathForComparison(targetPath).replace(/\\/g, '/');
|
|
162
|
+
}
|
|
163
|
+
export function detectUnsupportedPackageManagerFromPath(binaryPath) {
|
|
164
|
+
const normalized = normalizePathForSignatureMatch(binaryPath);
|
|
165
|
+
const pnpmHome = process.env.PNPM_HOME;
|
|
166
|
+
if (pnpmHome && pathStartsWith(binaryPath, pnpmHome)) {
|
|
167
|
+
return 'pnpm';
|
|
168
|
+
}
|
|
169
|
+
const bunInstall = process.env.BUN_INSTALL;
|
|
170
|
+
if (bunInstall && pathStartsWith(binaryPath, path.join(bunInstall, 'bin'))) {
|
|
171
|
+
return 'bun';
|
|
172
|
+
}
|
|
173
|
+
if (normalized.includes('/.local/share/pnpm/') ||
|
|
174
|
+
normalized.includes('/appdata/local/pnpm/') ||
|
|
175
|
+
normalized.includes('/pnpm/')) {
|
|
176
|
+
return 'pnpm';
|
|
177
|
+
}
|
|
178
|
+
if (normalized.includes('/.bun/bin/')) {
|
|
179
|
+
return 'bun';
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
export function buildMultipleInstallWarning(allDetectedPaths, activePath) {
|
|
184
|
+
if (allDetectedPaths.length <= 1) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const formattedPaths = allDetectedPaths
|
|
188
|
+
.map((entry, index) => {
|
|
189
|
+
const isActive = activePath !== null &&
|
|
190
|
+
normalizePathForComparison(entry) === normalizePathForComparison(activePath);
|
|
191
|
+
const prefix = isActive || (activePath === null && index === 0) ? '*' : '-';
|
|
192
|
+
return `${prefix} ${entry}`;
|
|
193
|
+
})
|
|
194
|
+
.join('\n');
|
|
195
|
+
return [
|
|
196
|
+
'Multiple dexto binaries detected in PATH:',
|
|
197
|
+
formattedPaths,
|
|
198
|
+
'The active binary is marked with *.',
|
|
199
|
+
].join('\n');
|
|
200
|
+
}
|
|
201
|
+
export async function detectInstallMethod() {
|
|
202
|
+
return await detectInstallMethodWithDeps();
|
|
203
|
+
}
|
|
204
|
+
export async function detectInstallMethodWithDeps(deps = {}) {
|
|
205
|
+
const readMetadata = deps.readMetadata ?? readInstallMetadata;
|
|
206
|
+
const getPathEntries = deps.getPathEntries ?? getBinaryPathsFromPath;
|
|
207
|
+
const detectNodeManager = deps.detectNodeManager ?? detectNodePackageManagerFromPath;
|
|
208
|
+
const pathExistsFn = deps.pathExists ?? pathExists;
|
|
209
|
+
const metadata = await readMetadata();
|
|
210
|
+
const allDetectedPaths = await getPathEntries();
|
|
211
|
+
const activePath = allDetectedPaths[0] ?? null;
|
|
212
|
+
async function detectMethodFromActivePath(pathEntry) {
|
|
213
|
+
if (isProjectLocalBinaryPath(pathEntry)) {
|
|
214
|
+
return 'project-local';
|
|
215
|
+
}
|
|
216
|
+
let method = inferInstallMethodFromPath(pathEntry);
|
|
217
|
+
if (method === 'unknown' || method === 'native') {
|
|
218
|
+
const pmMethod = await detectNodeManager(pathEntry);
|
|
219
|
+
if (pmMethod) {
|
|
220
|
+
method = pmMethod;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (method === 'unknown' && pathBasenameIsDexto(pathEntry)) {
|
|
224
|
+
const normalizedPath = normalizePathForComparison(pathEntry);
|
|
225
|
+
if (normalizedPath.includes(normalizePathForComparison(path.join(os.homedir(), '.local', 'bin')))) {
|
|
226
|
+
method = 'native';
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return method;
|
|
230
|
+
}
|
|
231
|
+
if (metadata) {
|
|
232
|
+
const installedPath = metadata.installedPath;
|
|
233
|
+
const metadataPathExists = await pathExistsFn(installedPath);
|
|
234
|
+
const metadataMatchesActivePath = activePath !== null &&
|
|
235
|
+
normalizePathForComparison(activePath) === normalizePathForComparison(installedPath);
|
|
236
|
+
const metadataIsTrusted = metadataPathExists && (allDetectedPaths.length === 0 || metadataMatchesActivePath);
|
|
237
|
+
if (!metadataIsTrusted && activePath) {
|
|
238
|
+
const method = await detectMethodFromActivePath(activePath);
|
|
239
|
+
return {
|
|
240
|
+
method,
|
|
241
|
+
source: 'heuristic',
|
|
242
|
+
metadata,
|
|
243
|
+
installedPath: activePath,
|
|
244
|
+
installDir: path.dirname(activePath),
|
|
245
|
+
allDetectedPaths,
|
|
246
|
+
multipleInstallWarning: buildMultipleInstallWarning(allDetectedPaths, activePath),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (!metadataIsTrusted) {
|
|
250
|
+
return {
|
|
251
|
+
method: 'unknown',
|
|
252
|
+
source: 'heuristic',
|
|
253
|
+
metadata,
|
|
254
|
+
installedPath: null,
|
|
255
|
+
installDir: null,
|
|
256
|
+
allDetectedPaths,
|
|
257
|
+
multipleInstallWarning: null,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const installDir = path.dirname(installedPath);
|
|
261
|
+
return {
|
|
262
|
+
method: metadata.method,
|
|
263
|
+
source: 'metadata',
|
|
264
|
+
metadata,
|
|
265
|
+
installedPath,
|
|
266
|
+
installDir,
|
|
267
|
+
allDetectedPaths,
|
|
268
|
+
multipleInstallWarning: buildMultipleInstallWarning(allDetectedPaths, installedPath),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (!activePath) {
|
|
272
|
+
return {
|
|
273
|
+
method: 'unknown',
|
|
274
|
+
source: 'heuristic',
|
|
275
|
+
metadata: null,
|
|
276
|
+
installedPath: null,
|
|
277
|
+
installDir: null,
|
|
278
|
+
allDetectedPaths,
|
|
279
|
+
multipleInstallWarning: null,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const method = await detectMethodFromActivePath(activePath);
|
|
283
|
+
return {
|
|
284
|
+
method,
|
|
285
|
+
source: 'heuristic',
|
|
286
|
+
metadata: null,
|
|
287
|
+
installedPath: activePath,
|
|
288
|
+
installDir: path.dirname(activePath),
|
|
289
|
+
allDetectedPaths,
|
|
290
|
+
multipleInstallWarning: buildMultipleInstallWarning(allDetectedPaths, activePath),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function shellEscapeForPosix(value) {
|
|
294
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
295
|
+
}
|
|
296
|
+
function shellEscapeForPowerShell(value) {
|
|
297
|
+
return `'${value.replace(/'/g, `''`)}'`;
|
|
298
|
+
}
|
|
299
|
+
export function commandDisplayWithEnvPosix(command, envOverrides) {
|
|
300
|
+
const parts = [];
|
|
301
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
302
|
+
parts.push(`${key}=${shellEscapeForPosix(value)}`);
|
|
303
|
+
}
|
|
304
|
+
if (parts.length === 0) {
|
|
305
|
+
return command;
|
|
306
|
+
}
|
|
307
|
+
return `${parts.join(' ')} ${command}`;
|
|
308
|
+
}
|
|
309
|
+
export function commandDisplayWithEnvPowerShell(command, envOverrides) {
|
|
310
|
+
const parts = [];
|
|
311
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
312
|
+
parts.push(`$env:${key}=${shellEscapeForPowerShell(value)};`);
|
|
313
|
+
}
|
|
314
|
+
if (parts.length === 0) {
|
|
315
|
+
return command;
|
|
316
|
+
}
|
|
317
|
+
return `${parts.join(' ')} ${command}`;
|
|
318
|
+
}
|
|
319
|
+
export function createNativeInstallCommand(options) {
|
|
320
|
+
const env = { ...process.env };
|
|
321
|
+
const envOverrides = {};
|
|
322
|
+
if (options.version) {
|
|
323
|
+
env.DEXTO_VERSION = options.version;
|
|
324
|
+
envOverrides.DEXTO_VERSION = options.version;
|
|
325
|
+
}
|
|
326
|
+
if (options.installDir) {
|
|
327
|
+
env.DEXTO_INSTALL_DIR = options.installDir;
|
|
328
|
+
envOverrides.DEXTO_INSTALL_DIR = options.installDir;
|
|
329
|
+
}
|
|
330
|
+
if (options.force) {
|
|
331
|
+
env.DEXTO_INSTALL_FORCE = '1';
|
|
332
|
+
envOverrides.DEXTO_INSTALL_FORCE = '1';
|
|
333
|
+
}
|
|
334
|
+
if (process.platform === 'win32') {
|
|
335
|
+
const commandText = `irm ${DEFAULT_WINDOWS_INSTALL_URL} | iex`;
|
|
336
|
+
return {
|
|
337
|
+
command: 'powershell',
|
|
338
|
+
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandText],
|
|
339
|
+
env,
|
|
340
|
+
displayCommand: commandDisplayWithEnvPowerShell(`powershell -NoProfile -ExecutionPolicy Bypass -Command ${shellEscapeForPowerShell(commandText)}`, envOverrides),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const commandText = `curl -fsSL ${DEFAULT_NATIVE_INSTALL_URL} | bash`;
|
|
344
|
+
return {
|
|
345
|
+
command: 'bash',
|
|
346
|
+
args: ['-lc', commandText],
|
|
347
|
+
env,
|
|
348
|
+
displayCommand: commandDisplayWithEnvPosix(commandText, envOverrides),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
export function createLegacyNpmUninstallCommand() {
|
|
352
|
+
return {
|
|
353
|
+
command: 'npm',
|
|
354
|
+
args: ['uninstall', '-g', 'dexto'],
|
|
355
|
+
displayCommand: 'npm uninstall -g dexto',
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
export function normalizeRequestedVersion(version) {
|
|
359
|
+
if (!version) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const trimmed = version.trim();
|
|
363
|
+
if (trimmed.length === 0) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
return trimmed.startsWith('dexto@') ? trimmed.slice('dexto@'.length) : trimmed;
|
|
367
|
+
}
|
|
368
|
+
export async function scheduleDeferredWindowsRemoval(targetPaths) {
|
|
369
|
+
const uniqueTargets = uniquePaths(targetPaths);
|
|
370
|
+
const removalScript = uniqueTargets
|
|
371
|
+
.map((targetPath) => {
|
|
372
|
+
const escapedPath = shellEscapeForPowerShell(targetPath);
|
|
373
|
+
return [
|
|
374
|
+
`if (Test-Path -LiteralPath ${escapedPath}) {`,
|
|
375
|
+
`Remove-Item -LiteralPath ${escapedPath} -Recurse -Force -ErrorAction SilentlyContinue`,
|
|
376
|
+
'}',
|
|
377
|
+
].join(' ');
|
|
378
|
+
})
|
|
379
|
+
.join('; ');
|
|
380
|
+
const commandText = `Start-Sleep -Seconds 2; ${removalScript}`;
|
|
381
|
+
await new Promise((resolve, reject) => {
|
|
382
|
+
const child = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandText], {
|
|
383
|
+
detached: true,
|
|
384
|
+
stdio: 'ignore',
|
|
385
|
+
windowsHide: true,
|
|
386
|
+
});
|
|
387
|
+
child.once('error', reject);
|
|
388
|
+
child.once('spawn', () => {
|
|
389
|
+
child.unref();
|
|
390
|
+
resolve();
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
export async function pathExists(targetPath) {
|
|
395
|
+
try {
|
|
396
|
+
await fs.lstat(targetPath);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function hasErrorCode(error, code) {
|
|
404
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
|
405
|
+
}
|
|
406
|
+
export async function removePath(targetPath) {
|
|
407
|
+
try {
|
|
408
|
+
const stat = await fs.lstat(targetPath);
|
|
409
|
+
await fs.rm(targetPath, { recursive: stat.isDirectory(), force: true });
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (hasErrorCode(error, 'ENOENT')) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
export function getDefaultNativeBinaryPath() {
|
|
419
|
+
if (process.platform === 'win32') {
|
|
420
|
+
return path.join(os.homedir(), '.dexto', 'bin', DEXTO_BINARY);
|
|
421
|
+
}
|
|
422
|
+
return path.join(os.homedir(), '.local', 'bin', DEXTO_BINARY);
|
|
423
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/version-check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/version-check.ts"],"names":[],"mappings":"AAiBA;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACzB;AAkJD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA6DxF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAatE"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// packages/cli/src/cli/utils/version-check.ts
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
6
|
import boxen from 'boxen';
|
|
6
|
-
import { logger } from '@dexto/core';
|
|
7
|
-
import { getDextoGlobalPath } from '@dexto/agent-management';
|
|
8
7
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
9
|
-
const
|
|
10
|
-
const CACHE_FILE_PATH =
|
|
8
|
+
const GITHUB_RELEASES_LATEST_URL = 'https://api.github.com/repos/truffle-ai/dexto/releases/latest';
|
|
9
|
+
const CACHE_FILE_PATH = path.join(os.homedir(), '.dexto', 'cache', 'version-check.json');
|
|
10
|
+
function debugLog(message) {
|
|
11
|
+
if (process.env.DEXTO_DEBUG === 'true') {
|
|
12
|
+
console.debug(`[version-check] ${message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
11
15
|
/**
|
|
12
16
|
* Compare two semver versions.
|
|
13
17
|
* Returns:
|
|
@@ -63,32 +67,62 @@ async function saveCache(cache) {
|
|
|
63
67
|
}
|
|
64
68
|
catch (error) {
|
|
65
69
|
// Non-critical - just log and continue
|
|
66
|
-
|
|
70
|
+
debugLog(`Failed to save version cache: ${error instanceof Error ? error.message : String(error)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeReleaseTag(tagName) {
|
|
74
|
+
const trimmed = tagName.trim();
|
|
75
|
+
if (trimmed.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// GitHub releases in this monorepo can include scoped package tags like
|
|
79
|
+
// "@dexto/tools-filesystem@1.6.10". Always extract a trailing semver.
|
|
80
|
+
const trailingSemverMatch = trimmed.match(/(v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/);
|
|
81
|
+
if (!trailingSemverMatch) {
|
|
82
|
+
return null;
|
|
67
83
|
}
|
|
84
|
+
const version = trailingSemverMatch[1]?.trim();
|
|
85
|
+
return version && version.length > 0 ? version : null;
|
|
86
|
+
}
|
|
87
|
+
function extractLatestVersionFromRelease(payload) {
|
|
88
|
+
if (!payload || typeof payload !== 'object') {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const tagName = Reflect.get(payload, 'tag_name');
|
|
92
|
+
if (typeof tagName !== 'string') {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return normalizeReleaseTag(tagName);
|
|
68
96
|
}
|
|
69
97
|
/**
|
|
70
|
-
* Fetch latest version from
|
|
98
|
+
* Fetch latest version from GitHub Releases
|
|
71
99
|
*/
|
|
72
100
|
async function fetchLatestVersion() {
|
|
73
101
|
const controller = new AbortController();
|
|
74
102
|
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
|
75
103
|
try {
|
|
76
|
-
const response = await fetch(
|
|
104
|
+
const response = await fetch(GITHUB_RELEASES_LATEST_URL, {
|
|
77
105
|
signal: controller.signal,
|
|
78
106
|
headers: {
|
|
79
|
-
Accept: 'application/json',
|
|
107
|
+
Accept: 'application/vnd.github+json',
|
|
108
|
+
'User-Agent': 'dexto-cli-version-check',
|
|
80
109
|
},
|
|
81
110
|
});
|
|
82
111
|
if (!response.ok) {
|
|
83
|
-
|
|
112
|
+
debugLog(`GitHub releases API returned status ${response.status}`);
|
|
84
113
|
return null;
|
|
85
114
|
}
|
|
86
115
|
const data = (await response.json());
|
|
87
|
-
|
|
116
|
+
const latestVersion = extractLatestVersionFromRelease(data);
|
|
117
|
+
if (!latestVersion) {
|
|
118
|
+
debugLog('GitHub releases API response missing valid tag_name');
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return latestVersion;
|
|
88
122
|
}
|
|
89
123
|
catch (error) {
|
|
90
124
|
// Network errors, timeouts, etc. - silent fail
|
|
91
|
-
|
|
125
|
+
debugLog(`Failed to fetch latest version: ${error instanceof Error ? error.message : String(error)}`);
|
|
92
126
|
return null;
|
|
93
127
|
}
|
|
94
128
|
finally {
|
|
@@ -118,7 +152,7 @@ async function fetchLatestVersion() {
|
|
|
118
152
|
export async function checkForUpdates(currentVersion) {
|
|
119
153
|
// Check if update checks are disabled
|
|
120
154
|
if (process.env.DEXTO_NO_UPDATE_CHECK === 'true') {
|
|
121
|
-
|
|
155
|
+
debugLog('Version check disabled via DEXTO_NO_UPDATE_CHECK');
|
|
122
156
|
return null;
|
|
123
157
|
}
|
|
124
158
|
try {
|
|
@@ -128,20 +162,20 @@ export async function checkForUpdates(currentVersion) {
|
|
|
128
162
|
if (cache && cache.currentVersion === currentVersion) {
|
|
129
163
|
const cacheAge = now - cache.lastCheck;
|
|
130
164
|
if (cacheAge < CACHE_TTL_MS) {
|
|
131
|
-
|
|
165
|
+
debugLog(`Using cached version info (age: ${Math.round(cacheAge / 1000 / 60)} minutes)`);
|
|
132
166
|
// Return cached result if newer version exists
|
|
133
167
|
if (compareSemver(cache.latestVersion, currentVersion) > 0) {
|
|
134
168
|
return {
|
|
135
169
|
current: currentVersion,
|
|
136
170
|
latest: cache.latestVersion,
|
|
137
|
-
updateCommand: '
|
|
171
|
+
updateCommand: 'dexto upgrade',
|
|
138
172
|
};
|
|
139
173
|
}
|
|
140
174
|
return null;
|
|
141
175
|
}
|
|
142
176
|
}
|
|
143
|
-
// Cache expired or invalid - fetch from
|
|
144
|
-
|
|
177
|
+
// Cache expired or invalid - fetch from GitHub releases
|
|
178
|
+
debugLog('Fetching latest version from GitHub releases');
|
|
145
179
|
const latestVersion = await fetchLatestVersion();
|
|
146
180
|
if (!latestVersion) {
|
|
147
181
|
return null;
|
|
@@ -158,14 +192,14 @@ export async function checkForUpdates(currentVersion) {
|
|
|
158
192
|
return {
|
|
159
193
|
current: currentVersion,
|
|
160
194
|
latest: latestVersion,
|
|
161
|
-
updateCommand: '
|
|
195
|
+
updateCommand: 'dexto upgrade',
|
|
162
196
|
};
|
|
163
197
|
}
|
|
164
198
|
return null;
|
|
165
199
|
}
|
|
166
200
|
catch (error) {
|
|
167
201
|
// Never fail the CLI startup due to version check errors
|
|
168
|
-
|
|
202
|
+
debugLog(`Version check error: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
203
|
return null;
|
|
170
204
|
}
|
|
171
205
|
}
|
|
@@ -179,7 +213,7 @@ export async function checkForUpdates(currentVersion) {
|
|
|
179
213
|
* displayUpdateNotification({
|
|
180
214
|
* current: '1.5.4',
|
|
181
215
|
* latest: '1.6.0',
|
|
182
|
-
* updateCommand: '
|
|
216
|
+
* updateCommand: 'dexto upgrade'
|
|
183
217
|
* });
|
|
184
218
|
* ```
|
|
185
219
|
*/
|
package/dist/index-main.js
CHANGED
|
@@ -22,7 +22,7 @@ function readVersionFromPackageJson(packageJsonPath) {
|
|
|
22
22
|
return undefined;
|
|
23
23
|
}
|
|
24
24
|
function resolveCliVersion() {
|
|
25
|
-
// Regular installs
|
|
25
|
+
// Regular npm installs: package.json is next to dist/.
|
|
26
26
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
27
27
|
const localPackageJsonPath = path.resolve(scriptDir, '..', 'package.json');
|
|
28
28
|
const localVersion = readVersionFromPackageJson(localPackageJsonPath);
|
|
@@ -58,6 +58,7 @@ import { registerMcpCommand } from './cli/commands/mcp/register.js';
|
|
|
58
58
|
import { registerImageCommand } from './cli/commands/image/register.js';
|
|
59
59
|
import { registerPluginCommand } from './cli/commands/plugin/register.js';
|
|
60
60
|
import { registerAgentsCommand } from './cli/commands/agents/register.js';
|
|
61
|
+
import { registerDeployCommand } from './cli/commands/deploy/register.js';
|
|
61
62
|
const program = new Command();
|
|
62
63
|
let imageImporterConfigured = false;
|
|
63
64
|
let dextoApiKeyBootstrapped = false;
|
|
@@ -146,6 +147,7 @@ program
|
|
|
146
147
|
}
|
|
147
148
|
}));
|
|
148
149
|
registerImageCommand({ program });
|
|
150
|
+
registerDeployCommand({ program });
|
|
149
151
|
// 4) `init-app` SUB-COMMAND
|
|
150
152
|
program
|
|
151
153
|
.command('init-app')
|
|
@@ -211,7 +213,45 @@ program
|
|
|
211
213
|
}
|
|
212
214
|
}));
|
|
213
215
|
registerAgentsCommand({ program });
|
|
214
|
-
// 7) `
|
|
216
|
+
// 7) `upgrade` SUB-COMMAND
|
|
217
|
+
program
|
|
218
|
+
.command('upgrade [version]')
|
|
219
|
+
.description('Upgrade Dexto CLI (auto-migrates npm installs to native)')
|
|
220
|
+
.option('--dry-run', 'Print commands without executing them')
|
|
221
|
+
.option('--force', 'Force reinstall during upgrade')
|
|
222
|
+
.action(withAnalytics('upgrade', async (version, options) => {
|
|
223
|
+
try {
|
|
224
|
+
const { handleUpgradeCommand } = await import('./cli/commands/upgrade.js');
|
|
225
|
+
await handleUpgradeCommand(version, options);
|
|
226
|
+
safeExit('upgrade', 0);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
if (err instanceof ExitSignal)
|
|
230
|
+
throw err;
|
|
231
|
+
console.error(`❌ dexto upgrade command failed: ${err}`);
|
|
232
|
+
safeExit('upgrade', 1, 'error');
|
|
233
|
+
}
|
|
234
|
+
}));
|
|
235
|
+
// 8) `uninstall` SUB-COMMAND (CLI self uninstall)
|
|
236
|
+
program
|
|
237
|
+
.command('uninstall')
|
|
238
|
+
.description('Uninstall the Dexto CLI binary (does not uninstall agents)')
|
|
239
|
+
.option('--purge', 'Also remove ~/.dexto completely')
|
|
240
|
+
.option('--dry-run', 'Print actions without deleting files')
|
|
241
|
+
.action(withAnalytics('uninstall', async (options) => {
|
|
242
|
+
try {
|
|
243
|
+
const { handleUninstallCliCommand } = await import('./cli/commands/uninstall.js');
|
|
244
|
+
await handleUninstallCliCommand(options);
|
|
245
|
+
safeExit('uninstall', 0);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
if (err instanceof ExitSignal)
|
|
249
|
+
throw err;
|
|
250
|
+
console.error(`❌ dexto uninstall command failed: ${err}`);
|
|
251
|
+
safeExit('uninstall', 1, 'error');
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
// 9) `which` SUB-COMMAND
|
|
215
255
|
program
|
|
216
256
|
.command('which <agent>')
|
|
217
257
|
.description('Show the path to an agent')
|
|
@@ -520,6 +560,23 @@ program
|
|
|
520
560
|
}
|
|
521
561
|
// Now resolve agent (will auto-install since setup is complete)
|
|
522
562
|
resolvedPath = await resolveAgentPath(opts.agent, opts.autoInstall !== false);
|
|
563
|
+
if (opts.interactive !== false) {
|
|
564
|
+
const { getBundledSyncTargetForAgentPath, shouldPromptForSync, handleSyncAgentsCommand, } = await import('./cli/commands/agents/sync.js');
|
|
565
|
+
const syncTarget = getBundledSyncTargetForAgentPath(resolvedPath);
|
|
566
|
+
if (syncTarget && (await shouldPromptForSync(resolvedPath))) {
|
|
567
|
+
const shouldSync = await p.confirm({
|
|
568
|
+
message: `Bundled agent updates available for '${syncTarget.agentId}'. Sync now?`,
|
|
569
|
+
initialValue: true,
|
|
570
|
+
});
|
|
571
|
+
if (!p.isCancel(shouldSync) && shouldSync) {
|
|
572
|
+
await handleSyncAgentsCommand({
|
|
573
|
+
force: true,
|
|
574
|
+
quiet: true,
|
|
575
|
+
agentIds: [syncTarget.agentId],
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
523
580
|
}
|
|
524
581
|
// Load raw config and apply CLI overrides
|
|
525
582
|
const rawConfig = await loadAgentConfig(resolvedPath);
|