dexto 1.5.5 → 1.5.6
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/dist/agents/agent-template.yml +1 -1
- package/dist/agents/coding-agent/coding-agent.yml +2 -2
- package/dist/agents/podcast-agent/podcast-agent.yml +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/interactive-commands/commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/commands.js +2 -0
- package/dist/cli/commands/interactive-commands/export/index.d.ts +13 -0
- package/dist/cli/commands/interactive-commands/export/index.d.ts.map +1 -0
- package/dist/cli/commands/interactive-commands/export/index.js +21 -0
- package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/general-commands.js +1 -0
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/system/system-commands.js +2 -3
- package/dist/cli/commands/setup.js +102 -23
- package/dist/cli/commands/sync-agents.d.ts +44 -0
- package/dist/cli/commands/sync-agents.d.ts.map +1 -0
- package/dist/cli/commands/sync-agents.js +483 -0
- package/dist/cli/ink-cli/InkCLIRefactored.d.ts +14 -1
- package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/InkCLIRefactored.js +7 -2
- package/dist/cli/ink-cli/components/StatusBar.d.ts +5 -1
- package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/StatusBar.js +16 -4
- package/dist/cli/ink-cli/components/TodoPanel.d.ts +11 -8
- package/dist/cli/ink-cli/components/TodoPanel.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/TodoPanel.js +38 -36
- package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/Header.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +13 -4
- package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ExportWizard.d.ts +22 -0
- package/dist/cli/ink-cli/components/overlays/ExportWizard.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/ExportWizard.js +308 -0
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
- package/dist/cli/ink-cli/constants/tips.js +2 -2
- package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/InputContainer.js +1 -0
- package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.js +5 -1
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
- package/dist/cli/ink-cli/hooks/useInputOrchestrator.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useInputOrchestrator.js +5 -0
- package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
- package/dist/cli/ink-cli/state/initialState.js +1 -0
- package/dist/cli/ink-cli/state/types.d.ts +14 -1
- package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
- package/dist/cli/ink-cli/utils/messageFormatting.js +2 -2
- package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
- package/dist/cli/utils/api-key-setup.js +13 -90
- package/dist/cli/utils/version-check.d.ts +45 -0
- package/dist/cli/utils/version-check.d.ts.map +1 -0
- package/dist/cli/utils/version-check.js +195 -0
- package/dist/index.js +60 -31
- package/package.json +7 -7
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
// packages/cli/src/cli/commands/sync-agents.ts
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
import { logger } from '@dexto/core';
|
|
8
|
+
import { getDextoGlobalPath, resolveBundledScript, copyDirectory, loadBundledRegistryAgents, } from '@dexto/agent-management';
|
|
9
|
+
/**
|
|
10
|
+
* Calculate SHA256 hash of a file
|
|
11
|
+
*/
|
|
12
|
+
async function hashFile(filePath) {
|
|
13
|
+
const content = await fs.readFile(filePath);
|
|
14
|
+
return createHash('sha256').update(content).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Calculate combined hash for a directory
|
|
18
|
+
* Hashes all files recursively and combines them
|
|
19
|
+
*/
|
|
20
|
+
async function hashDirectory(dirPath) {
|
|
21
|
+
const hash = createHash('sha256');
|
|
22
|
+
const files = [];
|
|
23
|
+
async function collectFiles(dir) {
|
|
24
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const fullPath = path.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
await collectFiles(fullPath);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
files.push(fullPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
await collectFiles(dirPath);
|
|
36
|
+
// Sort for consistent ordering
|
|
37
|
+
files.sort();
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const relativePath = path.relative(dirPath, file);
|
|
40
|
+
const content = await fs.readFile(file);
|
|
41
|
+
hash.update(relativePath);
|
|
42
|
+
hash.update(content);
|
|
43
|
+
}
|
|
44
|
+
return hash.digest('hex');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get hash of bundled agent
|
|
48
|
+
*/
|
|
49
|
+
async function getBundledAgentHash(agentEntry) {
|
|
50
|
+
try {
|
|
51
|
+
const sourcePath = resolveBundledScript(`agents/${agentEntry.source}`);
|
|
52
|
+
const stat = await fs.stat(sourcePath);
|
|
53
|
+
if (stat.isDirectory()) {
|
|
54
|
+
return await hashDirectory(sourcePath);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return await hashFile(sourcePath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.debug(`Failed to hash bundled agent: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get hash of installed agent
|
|
67
|
+
*/
|
|
68
|
+
async function getInstalledAgentHash(agentId) {
|
|
69
|
+
try {
|
|
70
|
+
const installedPath = path.join(getDextoGlobalPath('agents'), agentId);
|
|
71
|
+
const stat = await fs.stat(installedPath);
|
|
72
|
+
if (stat.isDirectory()) {
|
|
73
|
+
return await hashDirectory(installedPath);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return await hashFile(installedPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
logger.debug(`Failed to hash installed agent: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if an agent is installed
|
|
86
|
+
*/
|
|
87
|
+
async function isAgentInstalled(agentId) {
|
|
88
|
+
try {
|
|
89
|
+
const installedPath = path.join(getDextoGlobalPath('agents'), agentId);
|
|
90
|
+
await fs.access(installedPath);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get list of all installed agent directories
|
|
99
|
+
*/
|
|
100
|
+
async function getInstalledAgentIds() {
|
|
101
|
+
try {
|
|
102
|
+
const agentsDir = getDextoGlobalPath('agents');
|
|
103
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
104
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
logger.debug(`Failed to list installed agents: ${error instanceof Error ? error.message : String(error)}`);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get agent status by comparing bundled vs installed
|
|
113
|
+
*/
|
|
114
|
+
async function getAgentStatus(agentId, agentEntry) {
|
|
115
|
+
const installed = await isAgentInstalled(agentId);
|
|
116
|
+
if (!installed) {
|
|
117
|
+
return {
|
|
118
|
+
id: agentId,
|
|
119
|
+
name: agentEntry.name,
|
|
120
|
+
description: agentEntry.description,
|
|
121
|
+
status: 'not_installed',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const bundledHash = await getBundledAgentHash(agentEntry);
|
|
126
|
+
const installedHash = await getInstalledAgentHash(agentId);
|
|
127
|
+
if (!bundledHash || !installedHash) {
|
|
128
|
+
return {
|
|
129
|
+
id: agentId,
|
|
130
|
+
name: agentEntry.name,
|
|
131
|
+
description: agentEntry.description,
|
|
132
|
+
status: 'error',
|
|
133
|
+
error: 'Could not compute hash',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (bundledHash === installedHash) {
|
|
137
|
+
return {
|
|
138
|
+
id: agentId,
|
|
139
|
+
name: agentEntry.name,
|
|
140
|
+
description: agentEntry.description,
|
|
141
|
+
status: 'up_to_date',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
return {
|
|
146
|
+
id: agentId,
|
|
147
|
+
name: agentEntry.name,
|
|
148
|
+
description: agentEntry.description,
|
|
149
|
+
status: 'changes_available',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
id: agentId,
|
|
156
|
+
name: agentEntry.name,
|
|
157
|
+
description: agentEntry.description,
|
|
158
|
+
status: 'error',
|
|
159
|
+
error: error instanceof Error ? error.message : String(error),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Store sync dismissed state in cache directory
|
|
164
|
+
const SYNC_DISMISSED_PATH = getDextoGlobalPath('cache', 'sync-dismissed.json');
|
|
165
|
+
/**
|
|
166
|
+
* Check if sync was dismissed for current version
|
|
167
|
+
*/
|
|
168
|
+
async function wasSyncDismissed(currentVersion) {
|
|
169
|
+
try {
|
|
170
|
+
const content = await fs.readFile(SYNC_DISMISSED_PATH, 'utf-8');
|
|
171
|
+
const data = JSON.parse(content);
|
|
172
|
+
return data.version === currentVersion;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
logger.debug(`Could not read sync dismissed state: ${error instanceof Error ? error.message : String(error)}`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Mark sync as dismissed for current version
|
|
181
|
+
*/
|
|
182
|
+
export async function markSyncDismissed(currentVersion) {
|
|
183
|
+
try {
|
|
184
|
+
await fs.mkdir(path.dirname(SYNC_DISMISSED_PATH), { recursive: true });
|
|
185
|
+
await fs.writeFile(SYNC_DISMISSED_PATH, JSON.stringify({ version: currentVersion }));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
logger.debug(`Could not save sync dismissed state: ${error instanceof Error ? error.message : String(error)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Clear sync dismissed state (called after successful sync)
|
|
193
|
+
*/
|
|
194
|
+
export async function clearSyncDismissed() {
|
|
195
|
+
try {
|
|
196
|
+
await fs.unlink(SYNC_DISMISSED_PATH);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
// File might not exist - only log if it's a different error
|
|
200
|
+
if (error.code !== 'ENOENT') {
|
|
201
|
+
logger.debug(`Could not clear sync dismissed state: ${error instanceof Error ? error.message : String(error)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Quick check if any installed agents have updates available
|
|
207
|
+
*
|
|
208
|
+
* Used at CLI startup to prompt for sync without full command output.
|
|
209
|
+
* Returns true if at least one installed agent differs from bundled
|
|
210
|
+
* AND the user hasn't dismissed the prompt for this version.
|
|
211
|
+
*
|
|
212
|
+
* @param currentVersion Current CLI version to check dismissal state
|
|
213
|
+
* @returns true if should prompt for sync
|
|
214
|
+
*/
|
|
215
|
+
export async function shouldPromptForSync(currentVersion) {
|
|
216
|
+
try {
|
|
217
|
+
// Check if user already dismissed for this version
|
|
218
|
+
if (await wasSyncDismissed(currentVersion)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
const bundledAgents = loadBundledRegistryAgents();
|
|
222
|
+
const installedAgentIds = await getInstalledAgentIds();
|
|
223
|
+
for (const agentId of installedAgentIds) {
|
|
224
|
+
const agentEntry = bundledAgents[agentId];
|
|
225
|
+
// Skip custom agents (not in bundled registry)
|
|
226
|
+
if (!agentEntry)
|
|
227
|
+
continue;
|
|
228
|
+
const bundledHash = await getBundledAgentHash(agentEntry);
|
|
229
|
+
const installedHash = await getInstalledAgentHash(agentId);
|
|
230
|
+
if (bundledHash && installedHash && bundledHash !== installedHash) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
logger.debug(`shouldPromptForSync check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Update an agent from bundled to installed
|
|
243
|
+
*/
|
|
244
|
+
async function updateAgent(agentId, agentEntry) {
|
|
245
|
+
const agentsDir = getDextoGlobalPath('agents');
|
|
246
|
+
const targetDir = path.join(agentsDir, agentId);
|
|
247
|
+
const sourcePath = resolveBundledScript(`agents/${agentEntry.source}`);
|
|
248
|
+
// Ensure agents directory exists
|
|
249
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
250
|
+
// Remove old installation
|
|
251
|
+
try {
|
|
252
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Ignore if doesn't exist
|
|
256
|
+
}
|
|
257
|
+
// Copy from bundled source
|
|
258
|
+
const stat = await fs.stat(sourcePath);
|
|
259
|
+
if (stat.isDirectory()) {
|
|
260
|
+
await copyDirectory(sourcePath, targetDir);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
264
|
+
const targetFile = path.join(targetDir, path.basename(sourcePath));
|
|
265
|
+
await fs.copyFile(sourcePath, targetFile);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Display agent status with appropriate colors
|
|
270
|
+
*/
|
|
271
|
+
function formatStatus(status) {
|
|
272
|
+
switch (status) {
|
|
273
|
+
case 'up_to_date':
|
|
274
|
+
return chalk.green('Up to date');
|
|
275
|
+
case 'changes_available':
|
|
276
|
+
return chalk.yellow('Changes available');
|
|
277
|
+
case 'not_installed':
|
|
278
|
+
return chalk.gray('Not installed');
|
|
279
|
+
case 'custom':
|
|
280
|
+
return chalk.blue('Custom (user-installed)');
|
|
281
|
+
case 'error':
|
|
282
|
+
return chalk.red('Error');
|
|
283
|
+
default:
|
|
284
|
+
return chalk.gray('Unknown');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Main handler for the sync-agents command
|
|
289
|
+
*
|
|
290
|
+
* @param options Command options
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```bash
|
|
294
|
+
* dexto sync-agents # Interactive - prompt for each
|
|
295
|
+
* dexto sync-agents --list # Show what would be updated
|
|
296
|
+
* dexto sync-agents --force # Update all without prompting
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export async function handleSyncAgentsCommand(options) {
|
|
300
|
+
const { list = false, force = false, quiet = false } = options;
|
|
301
|
+
if (!quiet) {
|
|
302
|
+
p.intro(chalk.cyan('Agent Sync'));
|
|
303
|
+
}
|
|
304
|
+
const spinner = p.spinner();
|
|
305
|
+
spinner.start('Checking agent configs...');
|
|
306
|
+
try {
|
|
307
|
+
// Load bundled registry (uses existing function from agent-management)
|
|
308
|
+
const bundledAgents = loadBundledRegistryAgents();
|
|
309
|
+
const bundledAgentIds = Object.keys(bundledAgents);
|
|
310
|
+
// Get installed agents
|
|
311
|
+
const installedAgentIds = await getInstalledAgentIds();
|
|
312
|
+
// Find custom agents (installed but not in bundled registry)
|
|
313
|
+
const customAgentIds = installedAgentIds.filter((id) => !bundledAgents[id]);
|
|
314
|
+
// Check status of all bundled agents
|
|
315
|
+
const agentInfos = [];
|
|
316
|
+
for (const agentId of bundledAgentIds) {
|
|
317
|
+
const entry = bundledAgents[agentId];
|
|
318
|
+
if (entry) {
|
|
319
|
+
const info = await getAgentStatus(agentId, entry);
|
|
320
|
+
agentInfos.push(info);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Add custom agents
|
|
324
|
+
for (const agentId of customAgentIds) {
|
|
325
|
+
agentInfos.push({
|
|
326
|
+
id: agentId,
|
|
327
|
+
name: agentId,
|
|
328
|
+
status: 'custom',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const updatableAgents = agentInfos.filter((a) => a.status === 'changes_available');
|
|
332
|
+
const upToDateAgents = agentInfos.filter((a) => a.status === 'up_to_date');
|
|
333
|
+
const notInstalledAgents = agentInfos.filter((a) => a.status === 'not_installed');
|
|
334
|
+
const customAgents = agentInfos.filter((a) => a.status === 'custom');
|
|
335
|
+
const errorAgents = agentInfos.filter((a) => a.status === 'error');
|
|
336
|
+
spinner.stop('Agent check complete');
|
|
337
|
+
// Quiet mode with force - minimal output for startup prompt
|
|
338
|
+
if (quiet && force) {
|
|
339
|
+
if (updatableAgents.length === 0) {
|
|
340
|
+
p.log.success('All agents up to date');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const updatedNames = [];
|
|
344
|
+
const failedNames = [];
|
|
345
|
+
for (const agent of updatableAgents) {
|
|
346
|
+
const entry = bundledAgents[agent.id];
|
|
347
|
+
if (entry) {
|
|
348
|
+
try {
|
|
349
|
+
await updateAgent(agent.id, entry);
|
|
350
|
+
updatedNames.push(agent.id);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
failedNames.push(agent.id);
|
|
354
|
+
logger.debug(`Failed to update ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (updatedNames.length > 0) {
|
|
359
|
+
p.log.success(`Updated: ${updatedNames.join(', ')}`);
|
|
360
|
+
}
|
|
361
|
+
if (failedNames.length > 0) {
|
|
362
|
+
p.log.warn(`Failed to update: ${failedNames.join(', ')}`);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// Display full status (non-quiet mode)
|
|
367
|
+
console.log('');
|
|
368
|
+
console.log(chalk.bold('Agent Status:'));
|
|
369
|
+
console.log('');
|
|
370
|
+
// Show updatable first
|
|
371
|
+
for (const agent of updatableAgents) {
|
|
372
|
+
console.log(` ${chalk.cyan(agent.id)}:`);
|
|
373
|
+
console.log(` Status: ${formatStatus(agent.status)}`);
|
|
374
|
+
if (agent.description) {
|
|
375
|
+
console.log(` ${chalk.gray(agent.description)}`);
|
|
376
|
+
}
|
|
377
|
+
console.log('');
|
|
378
|
+
}
|
|
379
|
+
// Show up-to-date
|
|
380
|
+
for (const agent of upToDateAgents) {
|
|
381
|
+
console.log(` ${chalk.green(agent.id)}: ${formatStatus(agent.status)}`);
|
|
382
|
+
}
|
|
383
|
+
// Show not installed (summarized)
|
|
384
|
+
if (notInstalledAgents.length > 0) {
|
|
385
|
+
console.log('');
|
|
386
|
+
console.log(chalk.gray(` ${notInstalledAgents.length} agents not installed: ${notInstalledAgents.map((a) => a.id).join(', ')}`));
|
|
387
|
+
}
|
|
388
|
+
// Show custom
|
|
389
|
+
if (customAgents.length > 0) {
|
|
390
|
+
console.log('');
|
|
391
|
+
for (const agent of customAgents) {
|
|
392
|
+
console.log(` ${chalk.blue(agent.id)}: ${formatStatus(agent.status)}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Show errors
|
|
396
|
+
for (const agent of errorAgents) {
|
|
397
|
+
console.log(` ${chalk.red(agent.id)}: ${formatStatus(agent.status)}`);
|
|
398
|
+
if (agent.error) {
|
|
399
|
+
console.log(` ${chalk.red(agent.error)}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
console.log('');
|
|
403
|
+
// Summary
|
|
404
|
+
console.log(chalk.bold('Summary:'));
|
|
405
|
+
console.log(` Up to date: ${chalk.green(upToDateAgents.length.toString())}`);
|
|
406
|
+
console.log(` Changes available: ${chalk.yellow(updatableAgents.length.toString())}`);
|
|
407
|
+
console.log(` Not installed: ${chalk.gray(notInstalledAgents.length.toString())}`);
|
|
408
|
+
if (customAgents.length > 0) {
|
|
409
|
+
console.log(` Custom: ${chalk.blue(customAgents.length.toString())}`);
|
|
410
|
+
}
|
|
411
|
+
console.log('');
|
|
412
|
+
// If list mode, stop here
|
|
413
|
+
if (list) {
|
|
414
|
+
p.outro('Use `dexto sync-agents` to update agents');
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// No updates needed
|
|
418
|
+
if (updatableAgents.length === 0) {
|
|
419
|
+
p.outro(chalk.green('All installed agents are up to date!'));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Force mode - update all without prompting
|
|
423
|
+
if (force) {
|
|
424
|
+
const updateSpinner = p.spinner();
|
|
425
|
+
updateSpinner.start(`Updating ${updatableAgents.length} agents...`);
|
|
426
|
+
let successCount = 0;
|
|
427
|
+
let failCount = 0;
|
|
428
|
+
for (const agent of updatableAgents) {
|
|
429
|
+
const entry = bundledAgents[agent.id];
|
|
430
|
+
if (entry) {
|
|
431
|
+
try {
|
|
432
|
+
await updateAgent(agent.id, entry);
|
|
433
|
+
successCount++;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
failCount++;
|
|
437
|
+
logger.error(`Failed to update ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
updateSpinner.stop(`Updated ${successCount} agents`);
|
|
442
|
+
if (failCount > 0) {
|
|
443
|
+
p.log.warn(`${failCount} agents failed to update`);
|
|
444
|
+
}
|
|
445
|
+
p.outro(chalk.green('Sync complete!'));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
// Interactive mode - prompt for each
|
|
449
|
+
for (const agent of updatableAgents) {
|
|
450
|
+
const shouldUpdate = await p.confirm({
|
|
451
|
+
message: `Update ${chalk.cyan(agent.name)} (${agent.id})?`,
|
|
452
|
+
initialValue: true,
|
|
453
|
+
});
|
|
454
|
+
if (p.isCancel(shouldUpdate)) {
|
|
455
|
+
p.cancel('Sync cancelled');
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (shouldUpdate) {
|
|
459
|
+
const entry = bundledAgents[agent.id];
|
|
460
|
+
if (entry) {
|
|
461
|
+
try {
|
|
462
|
+
const updateSpinner = p.spinner();
|
|
463
|
+
updateSpinner.start(`Updating ${agent.id}...`);
|
|
464
|
+
await updateAgent(agent.id, entry);
|
|
465
|
+
updateSpinner.stop(`Updated ${agent.id}`);
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
p.log.error(`Failed to update ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
p.log.info(`Skipped ${agent.id}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
p.outro(chalk.green('Sync complete!'));
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
spinner.stop('Error');
|
|
480
|
+
p.log.error(`Failed to check agents: ${error instanceof Error ? error.message : String(error)}`);
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
@@ -25,9 +25,22 @@ interface InkCLIProps {
|
|
|
25
25
|
* - MouseProvider (only in alternate buffer mode)
|
|
26
26
|
*/
|
|
27
27
|
export declare function InkCLIRefactored({ agent, initialSessionId, startupInfo, soundService, }: InkCLIProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Options for starting the Ink CLI
|
|
30
|
+
*/
|
|
31
|
+
export interface InkCLIOptions {
|
|
32
|
+
/** Update info if a newer version is available */
|
|
33
|
+
updateInfo?: {
|
|
34
|
+
current: string;
|
|
35
|
+
latest: string;
|
|
36
|
+
updateCommand: string;
|
|
37
|
+
} | undefined;
|
|
38
|
+
/** True if installed agents differ from bundled and user should sync */
|
|
39
|
+
needsAgentSync?: boolean | undefined;
|
|
40
|
+
}
|
|
28
41
|
/**
|
|
29
42
|
* Start the modern Ink-based CLI
|
|
30
43
|
*/
|
|
31
|
-
export declare function startInkCliRefactored(agent: DextoAgent, initialSessionId: string | null): Promise<void>;
|
|
44
|
+
export declare function startInkCliRefactored(agent: DextoAgent, initialSessionId: string | null, options?: InkCLIOptions): Promise<void>;
|
|
32
45
|
export {};
|
|
33
46
|
//# sourceMappingURL=InkCLIRefactored.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkCLIRefactored.d.ts","sourceRoot":"","sources":["../../../src/cli/ink-cli/InkCLIRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAWpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAiB7E,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACjD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAY,GACf,EAAE,WAAW,2CAgBb;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"InkCLIRefactored.d.ts","sourceRoot":"","sources":["../../../src/cli/ink-cli/InkCLIRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAWpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAiB7E,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACjD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAY,GACf,EAAE,WAAW,2CAgBb;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,kDAAkD;IAClD,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpF,wEAAwE;IACxE,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAqEf"}
|
|
@@ -56,12 +56,17 @@ export function InkCLIRefactored({ agent, initialSessionId, startupInfo, soundSe
|
|
|
56
56
|
/**
|
|
57
57
|
* Start the modern Ink-based CLI
|
|
58
58
|
*/
|
|
59
|
-
export async function startInkCliRefactored(agent, initialSessionId) {
|
|
59
|
+
export async function startInkCliRefactored(agent, initialSessionId, options = {}) {
|
|
60
60
|
registerGracefulShutdown(() => agent, { inkMode: true });
|
|
61
61
|
// Enable bracketed paste mode so we can detect pasted text
|
|
62
62
|
// This wraps pastes with escape sequences that our KeypressContext handles
|
|
63
63
|
enableBracketedPaste();
|
|
64
|
-
const
|
|
64
|
+
const baseStartupInfo = await getStartupInfo(agent);
|
|
65
|
+
const startupInfo = {
|
|
66
|
+
...baseStartupInfo,
|
|
67
|
+
updateInfo: options.updateInfo,
|
|
68
|
+
needsAgentSync: options.needsAgentSync,
|
|
69
|
+
};
|
|
65
70
|
// Initialize sound service from preferences
|
|
66
71
|
const { SoundNotificationService } = await import('./utils/soundNotification.js');
|
|
67
72
|
const { globalPreferencesExist, loadGlobalPreferences } = await import('@dexto/agent-management');
|
|
@@ -17,6 +17,10 @@ interface StatusBarProps {
|
|
|
17
17
|
copyModeEnabled?: boolean;
|
|
18
18
|
/** Whether an approval prompt is currently shown */
|
|
19
19
|
isAwaitingApproval?: boolean;
|
|
20
|
+
/** Whether the todo list is expanded */
|
|
21
|
+
todoExpanded?: boolean;
|
|
22
|
+
/** Whether there are todos to display */
|
|
23
|
+
hasTodos?: boolean;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Status bar that shows processing state above input area
|
|
@@ -26,6 +30,6 @@ interface StatusBarProps {
|
|
|
26
30
|
* - Hide spinner during approval wait (user is reviewing, not waiting)
|
|
27
31
|
* - Only show elapsed time after 30s (avoid visual noise for fast operations)
|
|
28
32
|
*/
|
|
29
|
-
export declare function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled, isAwaitingApproval, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
33
|
+
export declare function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled, isAwaitingApproval, todoExpanded, hasTodos, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
30
34
|
export {};
|
|
31
35
|
//# sourceMappingURL=StatusBar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wCAAwC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,EAC1B,YAAmB,EACnB,QAAgB,GACnB,EAAE,cAAc,kDAuHhB"}
|
|
@@ -21,7 +21,7 @@ import { useTokenCounter } from '../hooks/useTokenCounter.js';
|
|
|
21
21
|
* - Hide spinner during approval wait (user is reviewing, not waiting)
|
|
22
22
|
* - Only show elapsed time after 30s (avoid visual noise for fast operations)
|
|
23
23
|
*/
|
|
24
|
-
export function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, }) {
|
|
24
|
+
export function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, todoExpanded = true, hasTodos = false, }) {
|
|
25
25
|
// Cycle through witty phrases while processing (not during compacting)
|
|
26
26
|
const { phrase } = usePhraseCycler({ isActive: isProcessing && !isCompacting });
|
|
27
27
|
// Track elapsed time during processing
|
|
@@ -42,14 +42,22 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
|
|
|
42
42
|
if (isAwaitingApproval) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
+
// Build the task toggle hint based on state
|
|
46
|
+
const todoHint = hasTodos
|
|
47
|
+
? todoExpanded
|
|
48
|
+
? 'ctrl+t to hide tasks'
|
|
49
|
+
: 'ctrl+t to show tasks'
|
|
50
|
+
: null;
|
|
45
51
|
// Show compacting state - yellow/orange color to indicate context management
|
|
46
52
|
if (isCompacting) {
|
|
47
53
|
const metaParts = [];
|
|
48
54
|
if (showTime)
|
|
49
55
|
metaParts.push(`(${elapsedTime})`);
|
|
50
56
|
metaParts.push('Esc to cancel');
|
|
57
|
+
if (todoHint)
|
|
58
|
+
metaParts.push(todoHint);
|
|
51
59
|
const metaContent = metaParts.join(' • ');
|
|
52
|
-
return (_jsxs(Box, { paddingX: 1, marginTop: 1,
|
|
60
|
+
return (_jsxs(Box, { paddingX: 1, marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { color: "yellow", children: " \uD83D\uDCE6 Compacting context..." })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: metaContent }) })] }));
|
|
53
61
|
}
|
|
54
62
|
// Show initial processing state (before streaming starts) - green/teal color
|
|
55
63
|
// TODO: Rename this event/state to "reasoning" and associate it with actual reasoning tokens
|
|
@@ -61,8 +69,10 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
|
|
|
61
69
|
if (tokenCount)
|
|
62
70
|
metaParts.push(tokenCount);
|
|
63
71
|
metaParts.push('Esc to cancel');
|
|
72
|
+
if (todoHint)
|
|
73
|
+
metaParts.push(todoHint);
|
|
64
74
|
const metaContent = metaParts.join(' • ');
|
|
65
|
-
return (_jsxs(Box, { paddingX: 1, marginTop: 1,
|
|
75
|
+
return (_jsxs(Box, { paddingX: 1, marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: "green", children: [" ", phrase] })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: metaContent }) })] }));
|
|
66
76
|
}
|
|
67
77
|
// Show active streaming state - green/teal color
|
|
68
78
|
// Always use 2-line layout: phrase on first line, meta on second
|
|
@@ -73,6 +83,8 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
|
|
|
73
83
|
if (tokenCount)
|
|
74
84
|
metaParts.push(tokenCount);
|
|
75
85
|
metaParts.push('Esc to cancel');
|
|
86
|
+
if (todoHint)
|
|
87
|
+
metaParts.push(todoHint);
|
|
76
88
|
const metaContent = metaParts.join(' • ');
|
|
77
|
-
return (_jsxs(Box, { paddingX: 1, marginTop: 1,
|
|
89
|
+
return (_jsxs(Box, { paddingX: 1, marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: "green", children: [" ", phrase] }), approvalQueueCount > 0 && (_jsxs(Text, { color: "yellowBright", children: [" \u2022 ", approvalQueueCount, " queued"] }))] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: metaContent }) })] }));
|
|
78
90
|
}
|
|
@@ -3,21 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Displays the current todo list for workflow tracking.
|
|
5
5
|
* Shows todos with their status indicators (pending, in progress, completed).
|
|
6
|
+
*
|
|
7
|
+
* Display modes:
|
|
8
|
+
* - Processing + Collapsed: Shows "Next:" with the next pending/in-progress task
|
|
9
|
+
* - Processing + Expanded: Shows simple checklist with ☐/☑ indicators below status bar
|
|
10
|
+
* - Idle + Expanded: Shows boxed format with header
|
|
11
|
+
* - Idle + Collapsed: Hidden
|
|
6
12
|
*/
|
|
7
13
|
import type { TodoItem } from '../state/types.js';
|
|
8
14
|
interface TodoPanelProps {
|
|
9
15
|
todos: TodoItem[];
|
|
16
|
+
/** Whether to show the full list or just the next task */
|
|
17
|
+
isExpanded: boolean;
|
|
18
|
+
/** Whether the agent is currently processing (affects display style) */
|
|
19
|
+
isProcessing?: boolean;
|
|
10
20
|
}
|
|
11
21
|
/**
|
|
12
22
|
* TodoPanel - Shows current todos for workflow tracking
|
|
13
|
-
*
|
|
14
|
-
* Design decisions:
|
|
15
|
-
* - Only shown when there are todos to display
|
|
16
|
-
* - Compact display that doesn't take too much vertical space
|
|
17
|
-
* - Status indicators: ○ pending, ● in progress, ✓ completed
|
|
18
|
-
* - In-progress items show activeForm (what's being worked on)
|
|
19
|
-
* - Completed items are dimmed with strikethrough
|
|
20
23
|
*/
|
|
21
|
-
export declare function TodoPanel({ todos }: TodoPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
24
|
+
export declare function TodoPanel({ todos, isExpanded, isProcessing }: TodoPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
22
25
|
export {};
|
|
23
26
|
//# sourceMappingURL=TodoPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TodoPanel.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/TodoPanel.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"TodoPanel.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/TodoPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAE9D,UAAU,cAAc;IACpB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,0DAA0D;IAC1D,UAAU,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,YAAoB,EAAE,EAAE,cAAc,kDAkHpF"}
|