dexto 1.5.0 → 1.5.2

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 (60) hide show
  1. package/dist/cli/commands/interactive-commands/command-parser.d.ts +1 -1
  2. package/dist/cli/commands/interactive-commands/command-parser.d.ts.map +1 -1
  3. package/dist/cli/commands/interactive-commands/command-parser.js +11 -1
  4. package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/interactive-commands/general-commands.js +63 -0
  6. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
  7. package/dist/cli/commands/interactive-commands/system/system-commands.js +3 -2
  8. package/dist/cli/commands/setup.d.ts.map +1 -1
  9. package/dist/cli/commands/setup.js +163 -28
  10. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  11. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +8 -2
  12. package/dist/cli/ink-cli/components/TextBufferInput.d.ts.map +1 -1
  13. package/dist/cli/ink-cli/components/TextBufferInput.js +24 -6
  14. package/dist/cli/ink-cli/components/chat/Header.js +1 -1
  15. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts +3 -1
  16. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  17. package/dist/cli/ink-cli/components/chat/MessageItem.js +37 -10
  18. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
  19. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
  20. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +3 -3
  21. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  22. package/dist/cli/ink-cli/components/modes/StaticCLI.js +10 -3
  23. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
  24. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +19 -8
  25. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  26. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +2 -1
  27. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  28. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +64 -2
  29. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +44 -1
  31. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts +6 -0
  32. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts.map +1 -1
  33. package/dist/cli/ink-cli/components/renderers/GenericRenderer.js +1 -1
  34. package/dist/cli/ink-cli/components/renderers/SearchRenderer.js +1 -1
  35. package/dist/cli/ink-cli/components/shared/MarkdownText.d.ts.map +1 -1
  36. package/dist/cli/ink-cli/components/shared/MarkdownText.js +4 -4
  37. package/dist/cli/ink-cli/constants/processingPhrases.d.ts.map +1 -1
  38. package/dist/cli/ink-cli/constants/processingPhrases.js +58 -48
  39. package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
  40. package/dist/cli/ink-cli/constants/tips.js +33 -32
  41. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  42. package/dist/cli/ink-cli/containers/InputContainer.js +18 -11
  43. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/containers/OverlayContainer.js +26 -3
  45. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts +8 -1
  46. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
  47. package/dist/cli/ink-cli/hooks/useAgentEvents.js +144 -6
  48. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  49. package/dist/cli/ink-cli/hooks/useCLIState.js +17 -1
  50. package/dist/cli/ink-cli/hooks/useTokenCounter.d.ts +11 -7
  51. package/dist/cli/ink-cli/hooks/useTokenCounter.d.ts.map +1 -1
  52. package/dist/cli/ink-cli/hooks/useTokenCounter.js +41 -18
  53. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  54. package/dist/cli/ink-cli/services/processStream.js +20 -8
  55. package/dist/cli/ink-cli/state/types.d.ts +2 -2
  56. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  57. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  58. package/dist/cli/ink-cli/utils/messageFormatting.js +92 -5
  59. package/dist/index.js +24 -3
  60. package/package.json +7 -7
@@ -31,7 +31,7 @@ export interface CommandDefinition {
31
31
  handler: (args: string[], agent: DextoAgent, ctx: CommandContext) => Promise<CommandHandlerResult>;
32
32
  }
33
33
  /**
34
- * Parses user input to determine if it's a slash command or a regular prompt
34
+ * Parses user input to determine if it's a slash command, shell command, or regular prompt
35
35
  */
36
36
  export declare function parseInput(input: string): CommandResult;
37
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"command-parser.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/command-parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAEhG,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,uDAAuD;IACvD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,OAAO,EAAE,CACL,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,cAAc,KAClB,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACtC;AA4CD;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAsBvD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAoB9F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,GAAE,OAAe,GAAG,MAAM,CAmB3F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAuDtE"}
1
+ {"version":3,"file":"command-parser.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/command-parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAEhG,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,uDAAuD;IACvD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,OAAO,EAAE,CACL,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,cAAc,KAClB,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACtC;AA4CD;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAiCvD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAoB9F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,GAAE,OAAe,GAAG,MAAM,CAmB3F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAuDtE"}
@@ -41,10 +41,20 @@ function parseQuotedArguments(input) {
41
41
  return args.filter((arg) => arg.length > 0);
42
42
  }
43
43
  /**
44
- * Parses user input to determine if it's a slash command or a regular prompt
44
+ * Parses user input to determine if it's a slash command, shell command, or regular prompt
45
45
  */
46
46
  export function parseInput(input) {
47
47
  const trimmed = input.trim();
48
+ // Check if it's a shell command (! prefix)
49
+ if (trimmed.startsWith('!')) {
50
+ const shellCommand = trimmed.slice(1).trim();
51
+ return {
52
+ type: 'command',
53
+ command: 'shell',
54
+ args: [shellCommand],
55
+ rawInput: trimmed,
56
+ };
57
+ }
48
58
  // Check if it's a slash command
49
59
  if (trimmed.startsWith('/')) {
50
60
  const args = parseQuotedArguments(trimmed.slice(1));
@@ -1 +1 @@
1
- {"version":3,"file":"general-commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/general-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,qBAAqB,CAAC;AAMnG;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,iBAAiB,EAAE,GAAG,iBAAiB,CAgC9F;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,iBAAiB,EAiM9C,CAAC"}
1
+ {"version":3,"file":"general-commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/general-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,qBAAqB,CAAC;AAiDnG;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,iBAAiB,EAAE,GAAG,iBAAiB,CAgC9F;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,iBAAiB,EAyO9C,CAAC"}
@@ -10,9 +10,42 @@
10
10
  * - /clear, /reset - Clear conversation history
11
11
  */
12
12
  import chalk from 'chalk';
13
+ import { spawn } from 'child_process';
13
14
  import { formatForInkCli } from './utils/format-output.js';
14
15
  import { CommandOutputHelper } from './utils/command-output.js';
15
16
  import { writeToClipboard } from '../../ink-cli/utils/clipboardUtils.js';
17
+ /**
18
+ * Execute a shell command and return the output
19
+ */
20
+ async function executeShellCommand(command, cwd, timeoutMs = 30000) {
21
+ return new Promise((resolve) => {
22
+ const child = spawn(command, [], {
23
+ cwd,
24
+ shell: true,
25
+ stdio: ['ignore', 'pipe', 'pipe'],
26
+ });
27
+ let stdout = '';
28
+ let stderr = '';
29
+ const timer = setTimeout(() => {
30
+ child.kill();
31
+ resolve({ stdout, stderr: `Command timed out after ${timeoutMs}ms`, exitCode: -1 });
32
+ }, timeoutMs);
33
+ child.stdout.on('data', (data) => {
34
+ stdout += data.toString();
35
+ });
36
+ child.stderr.on('data', (data) => {
37
+ stderr += data.toString();
38
+ });
39
+ child.on('error', (error) => {
40
+ clearTimeout(timer);
41
+ resolve({ stdout, stderr: error.message, exitCode: -1 });
42
+ });
43
+ child.on('close', (code) => {
44
+ clearTimeout(timer);
45
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
46
+ });
47
+ });
48
+ }
16
49
  /**
17
50
  * Creates the help command with access to all commands for display
18
51
  */
@@ -47,6 +80,36 @@ export function createHelpCommand(getAllCommands) {
47
80
  * Note: The help command is created separately to avoid circular dependencies
48
81
  */
49
82
  export const generalCommands = [
83
+ {
84
+ name: 'shell',
85
+ description: 'Execute shell command directly (use !command as shortcut)',
86
+ usage: '!<command> or /shell <command>',
87
+ category: 'General',
88
+ handler: async (args, agent, _ctx) => {
89
+ const command = args.join(' ').trim();
90
+ if (!command) {
91
+ return formatForInkCli('❌ No command provided. Usage: !<command>');
92
+ }
93
+ const cwd = process.cwd();
94
+ agent.logger.debug(`Executing shell command: ${command}`);
95
+ const { stdout, stderr, exitCode } = await executeShellCommand(command, cwd);
96
+ // Build output
97
+ const lines = [];
98
+ if (stdout.trim()) {
99
+ lines.push(stdout.trim());
100
+ }
101
+ if (stderr.trim()) {
102
+ lines.push(chalk.yellow(stderr.trim()));
103
+ }
104
+ if (exitCode !== 0) {
105
+ lines.push(chalk.red(`Exit code: ${exitCode}`));
106
+ }
107
+ if (lines.length === 0) {
108
+ return formatForInkCli(chalk.gray('(no output)'));
109
+ }
110
+ return formatForInkCli(lines.join('\n'));
111
+ },
112
+ },
50
113
  {
51
114
  name: 'exit',
52
115
  description: 'Exit the CLI',
@@ -1 +1 @@
1
- {"version":3,"file":"system-commands.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/system/system-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,sBAAsB,CAAC;AAKpG;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,iBAAiB,EAwO7C,CAAC"}
1
+ {"version":3,"file":"system-commands.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/system/system-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,sBAAsB,CAAC;AAKpG;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,iBAAiB,EAyO7C,CAAC"}
@@ -33,7 +33,7 @@ export const systemCommands = [
33
33
  const logFilePath = logger.getLogFilePath();
34
34
  console.log(chalk.bold.blue('\n📊 Logging Configuration:\n'));
35
35
  console.log(` Current level: ${chalk.green.bold(currentLevel)}`);
36
- if (logFilePath) {
36
+ if (logFilePath && process.env.DEXTO_PRIVACY_MODE !== 'true') {
37
37
  console.log(` Log file: ${chalk.cyan(logFilePath)}`);
38
38
  }
39
39
  console.log(chalk.gray('\n Available levels (from least to most verbose):'));
@@ -44,10 +44,11 @@ export const systemCommands = [
44
44
  console.log(` ${marker} ${levelText}`);
45
45
  });
46
46
  console.log(chalk.gray('\n 💡 Use /log <level> to change level (e.g., /log debug)\n'));
47
+ const isPrivacyMode = process.env.DEXTO_PRIVACY_MODE === 'true';
47
48
  const output = [
48
49
  '\n📊 Logging Configuration:',
49
50
  `Current level: ${currentLevel}`,
50
- logFilePath ? `Log file: ${logFilePath}` : '',
51
+ logFilePath && !isPrivacyMode ? `Log file: ${logFilePath}` : '',
51
52
  '\nAvailable levels: error, warn, info, http, verbose, debug, silly',
52
53
  '💡 Use /log <level> to change level',
53
54
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA0CxB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0ClB,CAAC;AAEP,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAClE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAiGtE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9F"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2CxB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0ClB,CAAC;AAEP,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAClE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAsHtE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9F"}
@@ -1,7 +1,7 @@
1
1
  // packages/cli/src/cli/commands/setup.ts
2
2
  import chalk from 'chalk';
3
3
  import { z } from 'zod';
4
- import { getDefaultModelForProvider, LLM_PROVIDERS, LLM_REGISTRY, isValidProviderModel, getSupportedModels, acceptsAnyModel, supportsCustomModels, requiresApiKey, } from '@dexto/core';
4
+ import { getDefaultModelForProvider, LLM_PROVIDERS, LLM_REGISTRY, isValidProviderModel, getSupportedModels, acceptsAnyModel, supportsCustomModels, requiresApiKey, isReasoningCapableModel, } from '@dexto/core';
5
5
  import { resolveApiKeyForProvider } from '@dexto/core';
6
6
  import { createInitialPreferences, saveGlobalPreferences, loadGlobalPreferences, getGlobalPreferencesPath, updateGlobalPreferences, setActiveModel, } from '@dexto/agent-management';
7
7
  import { interactiveApiKeySetup, hasApiKeyConfigured } from '../utils/api-key-setup.js';
@@ -56,34 +56,44 @@ const SetupCommandSchema = z
56
56
  }
57
57
  });
58
58
  /**
59
- * Get the steps to display for the current provider.
60
- * Local/Ollama providers skip the API Key step.
59
+ * Get the steps to display for the current provider and model.
60
+ * - Local/Ollama providers skip the API Key step.
61
+ * - Reasoning-capable models (o1, o3, codex, gpt-5.x) show the Reasoning step.
61
62
  */
62
- function getWizardSteps(provider) {
63
+ function getWizardSteps(provider, model) {
63
64
  const isLocalProvider = provider === 'local' || provider === 'ollama';
65
+ const showReasoningStep = model && isReasoningCapableModel(model);
64
66
  if (isLocalProvider) {
65
- return [
67
+ const steps = [
66
68
  { key: 'provider', label: 'Provider' },
67
69
  { key: 'model', label: 'Model' },
68
- { key: 'mode', label: 'Mode' },
69
70
  ];
71
+ if (showReasoningStep) {
72
+ steps.push({ key: 'reasoningEffort', label: 'Reasoning' });
73
+ }
74
+ steps.push({ key: 'mode', label: 'Mode' });
75
+ return steps;
70
76
  }
71
- return [
77
+ const steps = [
72
78
  { key: 'provider', label: 'Provider' },
73
79
  { key: 'model', label: 'Model' },
74
- { key: 'apiKey', label: 'API Key' },
75
- { key: 'mode', label: 'Mode' },
76
80
  ];
81
+ if (showReasoningStep) {
82
+ steps.push({ key: 'reasoningEffort', label: 'Reasoning' });
83
+ }
84
+ steps.push({ key: 'apiKey', label: 'API Key' });
85
+ steps.push({ key: 'mode', label: 'Mode' });
86
+ return steps;
77
87
  }
78
88
  /**
79
89
  * Display step progress indicator for the setup wizard
80
90
  */
81
- function showStepProgress(currentStep, provider) {
91
+ function showStepProgress(currentStep, provider, model) {
82
92
  // Don't show progress for setupType (it's the entry point)
83
93
  if (currentStep === 'setupType' || currentStep === 'complete') {
84
94
  return;
85
95
  }
86
- const steps = getWizardSteps(provider);
96
+ const steps = getWizardSteps(provider, model);
87
97
  const currentIndex = steps.findIndex((s) => s.key === currentStep);
88
98
  if (currentIndex === -1) {
89
99
  return;
@@ -226,6 +236,9 @@ async function handleInteractiveSetup(_options) {
226
236
  case 'model':
227
237
  state = await wizardStepModel(state);
228
238
  break;
239
+ case 'reasoningEffort':
240
+ state = await wizardStepReasoningEffort(state);
241
+ break;
229
242
  case 'apiKey':
230
243
  state = await wizardStepApiKey(state);
231
244
  break;
@@ -299,8 +312,10 @@ async function wizardStepModel(state) {
299
312
  if (!hasSelectedModel(localResult)) {
300
313
  return { ...state, step: 'provider', model: undefined };
301
314
  }
302
- // Local providers skip apiKey step
303
- return { ...state, step: 'mode', model: getModelFromResult(localResult) };
315
+ const model = getModelFromResult(localResult);
316
+ // Check if model supports reasoning effort
317
+ const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'mode';
318
+ return { ...state, step: nextStep, model };
304
319
  }
305
320
  if (provider === 'ollama') {
306
321
  const ollamaResult = await setupOllamaModels();
@@ -308,8 +323,10 @@ async function wizardStepModel(state) {
308
323
  if (!hasSelectedModel(ollamaResult)) {
309
324
  return { ...state, step: 'provider', model: undefined };
310
325
  }
311
- // Ollama skips apiKey step
312
- return { ...state, step: 'mode', model: getModelFromResult(ollamaResult) };
326
+ const model = getModelFromResult(ollamaResult);
327
+ // Check if model supports reasoning effort
328
+ const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'mode';
329
+ return { ...state, step: nextStep, model };
313
330
  }
314
331
  // Handle baseURL for providers that need it
315
332
  let baseURL;
@@ -326,7 +343,52 @@ async function wizardStepModel(state) {
326
343
  if (model === '_back') {
327
344
  return { ...state, step: 'provider', model: undefined, baseURL: undefined };
328
345
  }
329
- return { ...state, step: 'apiKey', model, baseURL };
346
+ // Check if model supports reasoning effort
347
+ const nextStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'apiKey';
348
+ return { ...state, step: nextStep, model, baseURL };
349
+ }
350
+ /**
351
+ * Wizard Step: Reasoning Effort Selection (for OpenAI reasoning models)
352
+ */
353
+ async function wizardStepReasoningEffort(state) {
354
+ const provider = state.provider;
355
+ const model = state.model;
356
+ const isLocalProvider = provider === 'local' || provider === 'ollama';
357
+ showStepProgress('reasoningEffort', provider, model);
358
+ const result = await p.select({
359
+ message: 'Select reasoning effort level',
360
+ options: [
361
+ {
362
+ value: 'medium',
363
+ label: 'Medium (Recommended)',
364
+ hint: 'Balanced reasoning for most tasks',
365
+ },
366
+ { value: 'low', label: 'Low', hint: 'Light reasoning, fast responses' },
367
+ {
368
+ value: 'minimal',
369
+ label: 'Minimal',
370
+ hint: 'Barely any reasoning, very fast',
371
+ },
372
+ { value: 'high', label: 'High', hint: 'More thorough reasoning' },
373
+ {
374
+ value: 'xhigh',
375
+ label: 'Extra High',
376
+ hint: 'Maximum reasoning, slower responses',
377
+ },
378
+ { value: 'none', label: 'None', hint: 'Disable reasoning' },
379
+ { value: '_back', label: chalk.gray('← Back'), hint: 'Change model' },
380
+ ],
381
+ });
382
+ if (p.isCancel(result)) {
383
+ p.cancel('Setup cancelled');
384
+ process.exit(0);
385
+ }
386
+ if (result === '_back') {
387
+ return { ...state, step: 'model', reasoningEffort: undefined };
388
+ }
389
+ // Determine next step based on provider type
390
+ const nextStep = isLocalProvider ? 'mode' : 'apiKey';
391
+ return { ...state, step: nextStep, reasoningEffort: result };
330
392
  }
331
393
  /**
332
394
  * Wizard Step: API Key Configuration
@@ -334,7 +396,7 @@ async function wizardStepModel(state) {
334
396
  async function wizardStepApiKey(state) {
335
397
  const provider = state.provider;
336
398
  const model = state.model;
337
- showStepProgress('apiKey', provider);
399
+ showStepProgress('apiKey', provider, model);
338
400
  const hasKey = hasApiKeyConfigured(provider);
339
401
  const needsApiKey = requiresApiKey(provider);
340
402
  if (needsApiKey && !hasKey) {
@@ -343,8 +405,9 @@ async function wizardStepApiKey(state) {
343
405
  model,
344
406
  });
345
407
  if (result.cancelled) {
346
- // Go back to model selection
347
- return { ...state, step: 'model', apiKeySkipped: undefined };
408
+ // Go back to reasoning effort if model supports it, otherwise model selection
409
+ const prevStep = isReasoningCapableModel(model) ? 'reasoningEffort' : 'model';
410
+ return { ...state, step: prevStep, apiKeySkipped: undefined };
348
411
  }
349
412
  const apiKeySkipped = result.skipped || !result.success;
350
413
  return { ...state, step: 'mode', apiKeySkipped };
@@ -362,14 +425,22 @@ async function wizardStepApiKey(state) {
362
425
  */
363
426
  async function wizardStepMode(state) {
364
427
  const provider = state.provider;
428
+ const model = state.model;
365
429
  const isLocalProvider = provider === 'local' || provider === 'ollama';
366
- showStepProgress('mode', provider);
430
+ const hasReasoningStep = isReasoningCapableModel(model);
431
+ showStepProgress('mode', provider, model);
367
432
  const mode = await selectDefaultModeWithBack();
368
433
  if (mode === '_back') {
369
- // Go back to previous step (apiKey for cloud, model for local)
434
+ // Go back to previous step based on provider type and model capabilities
370
435
  if (isLocalProvider) {
371
- return { ...state, step: 'model', defaultMode: undefined };
436
+ // Local: reasoning effort -> model
437
+ return {
438
+ ...state,
439
+ step: hasReasoningStep ? 'reasoningEffort' : 'model',
440
+ defaultMode: undefined,
441
+ };
372
442
  }
443
+ // Cloud: always go back to apiKey (reasoning effort comes before apiKey)
373
444
  return { ...state, step: 'apiKey', defaultMode: undefined };
374
445
  }
375
446
  return { ...state, step: 'complete', defaultMode: mode };
@@ -472,6 +543,9 @@ async function saveWizardPreferences(state) {
472
543
  if (state.baseURL) {
473
544
  preferencesOptions.baseURL = state.baseURL;
474
545
  }
546
+ if (state.reasoningEffort) {
547
+ preferencesOptions.reasoningEffort = state.reasoningEffort;
548
+ }
475
549
  const preferences = createInitialPreferences(preferencesOptions);
476
550
  await saveGlobalPreferences(preferences);
477
551
  // Analytics
@@ -544,6 +618,9 @@ async function showSettingsMenu() {
544
618
  ...(currentPrefs.llm.baseURL
545
619
  ? [`Base URL: ${chalk.cyan(currentPrefs.llm.baseURL)}`]
546
620
  : []),
621
+ ...(currentPrefs.llm.reasoningEffort
622
+ ? [`Reasoning Effort: ${chalk.cyan(currentPrefs.llm.reasoningEffort)}`]
623
+ : []),
547
624
  ].join('\n');
548
625
  p.note(currentConfig, 'Current Configuration');
549
626
  }
@@ -634,9 +711,18 @@ async function changeModel(currentProvider) {
634
711
  return;
635
712
  }
636
713
  const model = getModelFromResult(localResult);
637
- await updateGlobalPreferences({
638
- llm: { provider, model },
639
- });
714
+ const llmUpdate = {
715
+ provider,
716
+ model,
717
+ };
718
+ // Ask for reasoning effort if applicable
719
+ if (isReasoningCapableModel(model)) {
720
+ const reasoningEffort = await selectReasoningEffort();
721
+ if (reasoningEffort !== null) {
722
+ llmUpdate.reasoningEffort = reasoningEffort;
723
+ }
724
+ }
725
+ await updateGlobalPreferences({ llm: llmUpdate });
640
726
  p.log.success(`Model changed to ${model}`);
641
727
  return;
642
728
  }
@@ -648,9 +734,18 @@ async function changeModel(currentProvider) {
648
734
  return;
649
735
  }
650
736
  const model = getModelFromResult(ollamaResult);
651
- await updateGlobalPreferences({
652
- llm: { provider, model },
653
- });
737
+ const llmUpdate = {
738
+ provider,
739
+ model,
740
+ };
741
+ // Ask for reasoning effort if applicable
742
+ if (isReasoningCapableModel(model)) {
743
+ const reasoningEffort = await selectReasoningEffort();
744
+ if (reasoningEffort !== null) {
745
+ llmUpdate.reasoningEffort = reasoningEffort;
746
+ }
747
+ }
748
+ await updateGlobalPreferences({ llm: llmUpdate });
654
749
  p.log.success(`Model changed to ${model}`);
655
750
  return;
656
751
  }
@@ -671,6 +766,13 @@ async function changeModel(currentProvider) {
671
766
  if (needsApiKey) {
672
767
  llmUpdate.apiKey = `$${apiKeyVar}`;
673
768
  }
769
+ // Ask for reasoning effort if applicable
770
+ if (isReasoningCapableModel(model)) {
771
+ const reasoningEffort = await selectReasoningEffort();
772
+ if (reasoningEffort !== null) {
773
+ llmUpdate.reasoningEffort = reasoningEffort;
774
+ }
775
+ }
674
776
  await updateGlobalPreferences({ llm: llmUpdate });
675
777
  p.log.success(`Model changed to ${model}`);
676
778
  }
@@ -810,6 +912,39 @@ async function selectDefaultMode() {
810
912
  }
811
913
  return mode;
812
914
  }
915
+ /**
916
+ * Select reasoning effort level for reasoning-capable models
917
+ * Used in settings menu when changing to a reasoning-capable model
918
+ */
919
+ async function selectReasoningEffort() {
920
+ const effort = await p.select({
921
+ message: 'Select reasoning effort level',
922
+ options: [
923
+ {
924
+ value: 'medium',
925
+ label: 'Medium (Recommended)',
926
+ hint: 'Balanced reasoning for most tasks',
927
+ },
928
+ { value: 'low', label: 'Low', hint: 'Light reasoning, fast responses' },
929
+ {
930
+ value: 'minimal',
931
+ label: 'Minimal',
932
+ hint: 'Barely any reasoning, very fast',
933
+ },
934
+ { value: 'high', label: 'High', hint: 'More thorough reasoning' },
935
+ {
936
+ value: 'xhigh',
937
+ label: 'Extra High',
938
+ hint: 'Maximum reasoning, slower responses',
939
+ },
940
+ { value: 'none', label: 'None', hint: 'Disable reasoning' },
941
+ ],
942
+ });
943
+ if (p.isCancel(effort)) {
944
+ return null;
945
+ }
946
+ return effort;
947
+ }
813
948
  /**
814
949
  * Select model interactively
815
950
  * Returns null if user cancels
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceAutocomplete.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ResourceAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,0BAA0B;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,yBAAyB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AA6ED;;GAEG;AACH,QAAA,MAAM,yBAAyB,8GAgR9B,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAErB,OAAO,yBAAyB,CAAC;AAEtC,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"ResourceAutocomplete.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ResourceAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,0BAA0B;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,yBAAyB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AA6ED;;GAEG;AACH,QAAA,MAAM,yBAAyB,8GAoQ9B,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAErB,OAAO,yBAAyB,CAAC;AAEtC,eAAe,oBAAoB,CAAC"}
@@ -74,7 +74,7 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
74
74
  // Combined state to guarantee single render on navigation
75
75
  const [selection, setSelection] = useState({ index: 0, offset: 0 });
76
76
  const selectedIndexRef = useRef(0);
77
- const MAX_VISIBLE_ITEMS = 8;
77
+ const MAX_VISIBLE_ITEMS = 5;
78
78
  // Update selection AND scroll offset in a single state update
79
79
  // This guarantees exactly one render per navigation action
80
80
  const updateSelection = useCallback((indexUpdater) => {
@@ -122,6 +122,10 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
122
122
  cancelled = true;
123
123
  };
124
124
  }, [isVisible, agent]);
125
+ // NOTE: Auto-close logic is handled synchronously in TextBufferInput.tsx
126
+ // (on backspace deleting @ and on space after @). We don't use useEffect here
127
+ // because React batches state updates, causing race conditions where isVisible
128
+ // and searchQuery update at different times.
125
129
  // Extract query from @mention (everything after @)
126
130
  const mentionQuery = useMemo(() => {
127
131
  // Find the last @ that's at start or after space
@@ -250,7 +254,9 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
250
254
  const uriParts = resource.uri.split('/');
251
255
  const displayName = resource.name || uriParts[uriParts.length - 1] || resource.uri;
252
256
  const isImage = (resource.mimeType || '').startsWith('image/');
253
- return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [isImage && (_jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: "\uD83D\uDDBC\uFE0F " })), _jsx(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: displayName }), resource.serverName && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: isSelected ? 'white' : 'gray', children: ["[", resource.serverName, "]"] }) }))] }), _jsx(Box, { marginLeft: isImage ? 3 : 0, children: _jsx(Text, { color: isSelected ? 'white' : 'gray', children: resource.uri }) }), resource.description && (_jsx(Box, { marginLeft: isImage ? 3 : 0, children: _jsx(Text, { color: isSelected ? 'white' : 'gray', children: resource.description }) }))] }) }, resource.uri));
257
+ // Truncate URI for display (show last 40 chars with ellipsis)
258
+ const truncatedUri = resource.uri.length > 50 ? '…' + resource.uri.slice(-49) : resource.uri;
259
+ return (_jsxs(Box, { children: [isImage && _jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: "\uD83D\uDDBC\uFE0F " }), _jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: displayName }), resource.serverName && (_jsxs(Text, { color: "gray", children: [" [", resource.serverName, "]"] })), _jsxs(Text, { color: "gray", children: [" ", truncatedUri] })] }, resource.uri));
254
260
  })] }));
255
261
  });
256
262
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TextBufferInput.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/TextBufferInput.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGnE,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG,uBAAuB,GAAG,OAAO,CAAC;AActF,UAAU,oBAAoB;IAC1B,oCAAoC;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACrE,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACnE,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACpE,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iDAAiD;IACjD,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC3D,iEAAiE;IACjE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IACpC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACxD,wDAAwD;IACxD,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACzC,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC1D,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC5F,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7D,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAuCD,wBAAgB,eAAe,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,WAAW,EACX,UAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAqB,EACrB,QAAQ,EACR,gBAAgB,EAChB,UAAc,EACd,YAAY,EACZ,MAAW,EACX,aAAa,EACb,YAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,GACjB,EAAE,oBAAoB,2CAsgBtB"}
1
+ {"version":3,"file":"TextBufferInput.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/TextBufferInput.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGnE,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG,uBAAuB,GAAG,OAAO,CAAC;AActF,UAAU,oBAAoB;IAC1B,oCAAoC;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACrE,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACnE,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACpE,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iDAAiD;IACjD,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC3D,iEAAiE;IACjE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IACpC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACxD,wDAAwD;IACxD,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACzC,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC1D,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC5F,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7D,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAuCD,wBAAgB,eAAe,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,WAAW,EACX,UAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAqB,EACrB,QAAQ,EACR,gBAAgB,EAChB,UAAc,EACd,YAAY,EACZ,MAAW,EACX,aAAa,EACb,YAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,GACjB,EAAE,oBAAoB,2CA0hBtB"}
@@ -250,14 +250,23 @@ export function TextBufferInput({ buffer, onSubmit, placeholder, isDisabled = fa
250
250
  buffer.backspace();
251
251
  checkRemovedImages();
252
252
  checkRemovedPasteBlocks();
253
+ // Check if we should close overlay after backspace
254
+ // NOTE: buffer.text is memoized and won't update until next render,
255
+ // so we calculate the expected new text ourselves
253
256
  if (onTriggerOverlay && cursorPos > 0) {
254
257
  const deletedChar = prevText[cursorPos - 1];
255
- const newText = buffer.text;
258
+ // Calculate what the text will be after backspace
259
+ const expectedNewText = prevText.slice(0, cursorPos - 1) + prevText.slice(cursorPos);
256
260
  if (deletedChar === '/' && cursorPos === 1) {
257
261
  onTriggerOverlay('close');
258
262
  }
259
- else if (deletedChar === '@' && !newText.includes('@')) {
260
- onTriggerOverlay('close');
263
+ else if (deletedChar === '@') {
264
+ // Close if no valid @ mention remains
265
+ // A valid @ is at start of text or after whitespace
266
+ const hasValidAt = /(^|[\s])@/.test(expectedNewText);
267
+ if (!hasValidAt) {
268
+ onTriggerOverlay('close');
269
+ }
261
270
  }
262
271
  }
263
272
  return;
@@ -388,6 +397,11 @@ export function TextBufferInput({ buffer, onSubmit, placeholder, isDisabled = fa
388
397
  else if (key.sequence === '@') {
389
398
  onTriggerOverlay('resource-autocomplete');
390
399
  }
400
+ else if (/\s/.test(key.sequence)) {
401
+ // Close resource autocomplete when user types whitespace
402
+ // Whitespace means user is done with the mention (either selected or abandoned)
403
+ onTriggerOverlay('close');
404
+ }
391
405
  }
392
406
  }
393
407
  }, [
@@ -415,6 +429,10 @@ export function TextBufferInput({ buffer, onSubmit, placeholder, isDisabled = fa
415
429
  const cursorVisualCol = visualCursor[1];
416
430
  const separator = '─'.repeat(terminalWidth);
417
431
  const totalLines = visualLines.length;
432
+ // Detect shell command mode (input starts with "!")
433
+ const isShellMode = bufferText.startsWith('!');
434
+ const promptPrefix = isShellMode ? '$ ' : '> ';
435
+ const promptColor = isShellMode ? 'yellow' : 'green';
418
436
  // Calculate visible window
419
437
  let startLine = 0;
420
438
  let endLine = totalLines;
@@ -434,15 +452,15 @@ export function TextBufferInput({ buffer, onSubmit, placeholder, isDisabled = fa
434
452
  return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [_jsx(Text, { color: "gray", children: separator }), startLine > 0 && (_jsxs(Text, { color: "gray", children: [' ', "\u2191 ", startLine, " more line", startLine > 1 ? 's' : '', " above (", KEY_LABELS.altUp, " to jump)"] })), visibleLines.map((line, idx) => {
435
453
  const absoluteRow = startLine + idx;
436
454
  const isFirst = absoluteRow === 0;
437
- const prefix = isFirst ? '> ' : ' ';
455
+ const prefix = isFirst ? promptPrefix : ' ';
438
456
  const isCursorLine = absoluteRow === cursorVisualRow;
439
457
  if (!isCursorLine) {
440
- return (_jsxs(Box, { width: terminalWidth, children: [_jsx(Text, { color: "green", bold: isFirst, children: prefix }), _jsx(HighlightedText, { text: line, query: highlightQuery }), _jsx(Text, { children: ' '.repeat(Math.max(0, terminalWidth - prefix.length - line.length)) })] }, absoluteRow));
458
+ return (_jsxs(Box, { width: terminalWidth, children: [_jsx(Text, { color: promptColor, bold: isFirst, children: prefix }), _jsx(HighlightedText, { text: line, query: highlightQuery }), _jsx(Text, { children: ' '.repeat(Math.max(0, terminalWidth - prefix.length - line.length)) })] }, absoluteRow));
441
459
  }
442
460
  const before = line.slice(0, cursorVisualCol);
443
461
  const atCursor = line.charAt(cursorVisualCol) || ' ';
444
462
  const after = line.slice(cursorVisualCol + 1);
445
- return (_jsxs(Box, { width: terminalWidth, children: [_jsx(Text, { color: "green", bold: isFirst, children: prefix }), _jsx(HighlightedText, { text: before, query: highlightQuery }), _jsx(Text, { inverse: true, children: atCursor }), _jsx(HighlightedText, { text: after, query: highlightQuery }), _jsx(Text, { children: ' '.repeat(Math.max(0, terminalWidth - prefix.length - before.length - 1 - after.length)) })] }, absoluteRow));
463
+ return (_jsxs(Box, { width: terminalWidth, children: [_jsx(Text, { color: promptColor, bold: isFirst, children: prefix }), _jsx(HighlightedText, { text: before, query: highlightQuery }), _jsx(Text, { inverse: true, children: atCursor }), _jsx(HighlightedText, { text: after, query: highlightQuery }), _jsx(Text, { children: ' '.repeat(Math.max(0, terminalWidth - prefix.length - before.length - 1 - after.length)) })] }, absoluteRow));
446
464
  }), endLine < totalLines && (_jsxs(Text, { color: "gray", children: [' ', "\u2193 ", totalLines - endLine, " more line", totalLines - endLine > 1 ? 's' : '', ' ', "below (", KEY_LABELS.altDown, " to jump)"] })), pastedBlocks.length > 0 && (_jsx(PasteBlockHint, { pastedBlocks: pastedBlocks, expandedBlock: findExpandedBlock(), cursorOnCollapsed: findCollapsedBlockAtCursor() })), _jsx(Text, { color: "gray", children: separator })] }));
447
465
  }
448
466
  /** Hint component for paste blocks */
@@ -12,5 +12,5 @@ export function Header({ modelName, sessionId, hasActiveSession, startupInfo })
12
12
  ██║ ██║█████╗ ╚███╔╝ ██║ ██║ ██║
13
13
  ██║ ██║██╔══╝ ██╔██╗ ██║ ██║ ██║
14
14
  ██████╔╝███████╗██╔╝ ██╗ ██║ ╚██████╔╝
15
- ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝` }) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "gray", children: "Model: " }), _jsx(Text, { color: "white", children: modelName }), hasActiveSession && sessionId && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: " \u2022 Session: " }), _jsx(Text, { color: "white", children: sessionId.slice(0, 8) })] }))] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: "Servers: " }), _jsx(Text, { color: "white", children: startupInfo.connectedServers.count }), _jsx(Text, { color: "gray", children: " \u2022 Tools: " }), _jsx(Text, { color: "white", children: startupInfo.toolCount })] }), startupInfo.failedConnections.length > 0 && (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: "yellowBright", children: ["\u26A0\uFE0F Failed: ", startupInfo.failedConnections.join(', ')] }) })), startupInfo.logFile && (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: "gray", children: ["Logs: ", startupInfo.logFile] }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: " " }) })] }));
15
+ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝` }) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "gray", children: "Model: " }), _jsx(Text, { color: "white", children: modelName }), hasActiveSession && sessionId && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: " \u2022 Session: " }), _jsx(Text, { color: "white", children: sessionId.slice(0, 8) })] }))] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: "Servers: " }), _jsx(Text, { color: "white", children: startupInfo.connectedServers.count }), _jsx(Text, { color: "gray", children: " \u2022 Tools: " }), _jsx(Text, { color: "white", children: startupInfo.toolCount })] }), startupInfo.failedConnections.length > 0 && (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: "yellowBright", children: ["\u26A0\uFE0F Failed: ", startupInfo.failedConnections.join(', ')] }) })), startupInfo.logFile && process.env.DEXTO_PRIVACY_MODE !== 'true' && (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: "gray", children: ["Logs: ", startupInfo.logFile] }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: " " }) })] }));
16
16
  }
@@ -6,6 +6,8 @@
6
6
  import type { Message } from '../../state/types.js';
7
7
  interface MessageItemProps {
8
8
  message: Message;
9
+ /** Terminal width for proper text wrapping calculations */
10
+ terminalWidth?: number;
9
11
  }
10
12
  /**
11
13
  * Pure presentational component for a single message
@@ -14,6 +16,6 @@ interface MessageItemProps {
14
16
  * Memoization with custom comparator prevents re-renders when message array changes
15
17
  * but individual message content hasn't changed.
16
18
  */
17
- export declare const MessageItem: import("react").MemoExoticComponent<({ message }: MessageItemProps) => import("react/jsx-runtime").JSX.Element>;
19
+ export declare const MessageItem: import("react").MemoExoticComponent<({ message, terminalWidth }: MessageItemProps) => import("react/jsx-runtime").JSX.Element>;
18
20
  export {};
19
21
  //# sourceMappingURL=MessageItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/chat/MessageItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EACR,OAAO,EAUV,MAAM,sBAAsB,CAAC;AAuC9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,oDACN,gBAAgB,6CAwJjC,CAAC"}
1
+ {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/chat/MessageItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EACR,OAAO,EAUV,MAAM,sBAAsB,CAAC;AAuC9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mEACc,gBAAgB,6CA+LrD,CAAC"}