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.
Files changed (62) hide show
  1. package/dist/agents/agent-template.yml +1 -1
  2. package/dist/agents/coding-agent/coding-agent.yml +2 -2
  3. package/dist/agents/podcast-agent/podcast-agent.yml +1 -1
  4. package/dist/cli/commands/index.d.ts +1 -0
  5. package/dist/cli/commands/index.d.ts.map +1 -1
  6. package/dist/cli/commands/index.js +1 -0
  7. package/dist/cli/commands/interactive-commands/commands.d.ts.map +1 -1
  8. package/dist/cli/commands/interactive-commands/commands.js +2 -0
  9. package/dist/cli/commands/interactive-commands/export/index.d.ts +13 -0
  10. package/dist/cli/commands/interactive-commands/export/index.d.ts.map +1 -0
  11. package/dist/cli/commands/interactive-commands/export/index.js +21 -0
  12. package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
  13. package/dist/cli/commands/interactive-commands/general-commands.js +1 -0
  14. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
  15. package/dist/cli/commands/interactive-commands/system/system-commands.js +2 -3
  16. package/dist/cli/commands/setup.js +102 -23
  17. package/dist/cli/commands/sync-agents.d.ts +44 -0
  18. package/dist/cli/commands/sync-agents.d.ts.map +1 -0
  19. package/dist/cli/commands/sync-agents.js +483 -0
  20. package/dist/cli/ink-cli/InkCLIRefactored.d.ts +14 -1
  21. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  22. package/dist/cli/ink-cli/InkCLIRefactored.js +7 -2
  23. package/dist/cli/ink-cli/components/StatusBar.d.ts +5 -1
  24. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  25. package/dist/cli/ink-cli/components/StatusBar.js +16 -4
  26. package/dist/cli/ink-cli/components/TodoPanel.d.ts +11 -8
  27. package/dist/cli/ink-cli/components/TodoPanel.d.ts.map +1 -1
  28. package/dist/cli/ink-cli/components/TodoPanel.js +38 -36
  29. package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/components/chat/Header.js +1 -1
  31. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
  32. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  33. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +13 -4
  34. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  35. package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
  36. package/dist/cli/ink-cli/components/overlays/ExportWizard.d.ts +22 -0
  37. package/dist/cli/ink-cli/components/overlays/ExportWizard.d.ts.map +1 -0
  38. package/dist/cli/ink-cli/components/overlays/ExportWizard.js +308 -0
  39. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  40. package/dist/cli/ink-cli/constants/tips.js +2 -2
  41. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  42. package/dist/cli/ink-cli/containers/InputContainer.js +1 -0
  43. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/containers/OverlayContainer.js +5 -1
  45. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  46. package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
  47. package/dist/cli/ink-cli/hooks/useInputOrchestrator.d.ts.map +1 -1
  48. package/dist/cli/ink-cli/hooks/useInputOrchestrator.js +5 -0
  49. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/state/initialState.js +1 -0
  51. package/dist/cli/ink-cli/state/types.d.ts +14 -1
  52. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
  54. package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
  55. package/dist/cli/ink-cli/utils/messageFormatting.js +2 -2
  56. package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
  57. package/dist/cli/utils/api-key-setup.js +13 -90
  58. package/dist/cli/utils/version-check.d.ts +45 -0
  59. package/dist/cli/utils/version-check.d.ts.map +1 -0
  60. package/dist/cli/utils/version-check.js +195 -0
  61. package/dist/index.js +60 -31
  62. 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,GAChC,OAAO,CAAC,IAAI,CAAC,CAgEf"}
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 startupInfo = await getStartupInfo(agent);
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;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,GAC7B,EAAE,cAAc,kDA6GhB"}
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, marginBottom: 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 }) })] }));
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, marginBottom: 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 }) })] }));
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, marginBottom: 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 }) })] }));
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;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAE9D,UAAU,cAAc;IACpB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACrB;AA2DD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,cAAc,kDAsClD"}
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"}