@codemieai/code 0.0.3 → 0.0.5

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 (257) hide show
  1. package/README.md +375 -325
  2. package/bin/codemie-claude.js +23 -0
  3. package/bin/codemie-code.js +49 -11
  4. package/bin/codemie-codex.js +12 -13
  5. package/dist/agents/adapters/claude-code.d.ts +5 -0
  6. package/dist/agents/adapters/claude-code.d.ts.map +1 -1
  7. package/dist/agents/adapters/claude-code.js +76 -18
  8. package/dist/agents/adapters/claude-code.js.map +1 -1
  9. package/dist/agents/adapters/codex.d.ts +5 -0
  10. package/dist/agents/adapters/codex.d.ts.map +1 -1
  11. package/dist/agents/adapters/codex.js +75 -17
  12. package/dist/agents/adapters/codex.js.map +1 -1
  13. package/dist/agents/codemie-code/agent.d.ts.map +1 -1
  14. package/dist/agents/codemie-code/agent.js +187 -21
  15. package/dist/agents/codemie-code/agent.js.map +1 -1
  16. package/dist/agents/codemie-code/config.d.ts.map +1 -1
  17. package/dist/agents/codemie-code/config.js +29 -27
  18. package/dist/agents/codemie-code/config.js.map +1 -1
  19. package/dist/agents/codemie-code/index.d.ts +16 -2
  20. package/dist/agents/codemie-code/index.d.ts.map +1 -1
  21. package/dist/agents/codemie-code/index.js +74 -6
  22. package/dist/agents/codemie-code/index.js.map +1 -1
  23. package/dist/agents/codemie-code/modes/contextAwarePlanning.d.ts +87 -0
  24. package/dist/agents/codemie-code/modes/contextAwarePlanning.d.ts.map +1 -0
  25. package/dist/agents/codemie-code/modes/contextAwarePlanning.js +957 -0
  26. package/dist/agents/codemie-code/modes/contextAwarePlanning.js.map +1 -0
  27. package/dist/agents/codemie-code/modes/planMode.d.ts +116 -0
  28. package/dist/agents/codemie-code/modes/planMode.d.ts.map +1 -0
  29. package/dist/agents/codemie-code/modes/planMode.js +537 -0
  30. package/dist/agents/codemie-code/modes/planMode.js.map +1 -0
  31. package/dist/agents/codemie-code/prompts.d.ts +29 -0
  32. package/dist/agents/codemie-code/prompts.d.ts.map +1 -1
  33. package/dist/agents/codemie-code/prompts.js +129 -0
  34. package/dist/agents/codemie-code/prompts.js.map +1 -1
  35. package/dist/agents/codemie-code/storage/todoStorage.d.ts +78 -0
  36. package/dist/agents/codemie-code/storage/todoStorage.d.ts.map +1 -0
  37. package/dist/agents/codemie-code/storage/todoStorage.js +225 -0
  38. package/dist/agents/codemie-code/storage/todoStorage.js.map +1 -0
  39. package/dist/agents/codemie-code/tokenUtils.js +1 -1
  40. package/dist/agents/codemie-code/tokenUtils.js.map +1 -1
  41. package/dist/agents/codemie-code/tools/index.d.ts +26 -0
  42. package/dist/agents/codemie-code/tools/index.d.ts.map +1 -1
  43. package/dist/agents/codemie-code/tools/index.js +182 -14
  44. package/dist/agents/codemie-code/tools/index.js.map +1 -1
  45. package/dist/agents/codemie-code/tools/planning.d.ts +53 -0
  46. package/dist/agents/codemie-code/tools/planning.d.ts.map +1 -0
  47. package/dist/agents/codemie-code/tools/planning.js +224 -0
  48. package/dist/agents/codemie-code/tools/planning.js.map +1 -0
  49. package/dist/agents/codemie-code/types.d.ts +170 -6
  50. package/dist/agents/codemie-code/types.d.ts.map +1 -1
  51. package/dist/agents/codemie-code/types.js.map +1 -1
  52. package/dist/agents/codemie-code/ui/progressTracker.d.ts +125 -0
  53. package/dist/agents/codemie-code/ui/progressTracker.d.ts.map +1 -0
  54. package/dist/agents/codemie-code/ui/progressTracker.js +343 -0
  55. package/dist/agents/codemie-code/ui/progressTracker.js.map +1 -0
  56. package/dist/agents/codemie-code/ui/todoPanel.d.ts +112 -0
  57. package/dist/agents/codemie-code/ui/todoPanel.d.ts.map +1 -0
  58. package/dist/agents/codemie-code/ui/todoPanel.js +318 -0
  59. package/dist/agents/codemie-code/ui/todoPanel.js.map +1 -0
  60. package/dist/agents/codemie-code/ui.d.ts +106 -10
  61. package/dist/agents/codemie-code/ui.d.ts.map +1 -1
  62. package/dist/agents/codemie-code/ui.js +913 -129
  63. package/dist/agents/codemie-code/ui.js.map +1 -1
  64. package/dist/agents/codemie-code/utils/progressionEnforcer.d.ts +87 -0
  65. package/dist/agents/codemie-code/utils/progressionEnforcer.d.ts.map +1 -0
  66. package/dist/agents/codemie-code/utils/progressionEnforcer.js +293 -0
  67. package/dist/agents/codemie-code/utils/progressionEnforcer.js.map +1 -0
  68. package/dist/agents/codemie-code/utils/todoParser.d.ts +41 -0
  69. package/dist/agents/codemie-code/utils/todoParser.d.ts.map +1 -0
  70. package/dist/agents/codemie-code/utils/todoParser.js +305 -0
  71. package/dist/agents/codemie-code/utils/todoParser.js.map +1 -0
  72. package/dist/agents/codemie-code/utils/todoValidator.d.ts +65 -0
  73. package/dist/agents/codemie-code/utils/todoValidator.d.ts.map +1 -0
  74. package/dist/agents/codemie-code/utils/todoValidator.js +249 -0
  75. package/dist/agents/codemie-code/utils/todoValidator.js.map +1 -0
  76. package/dist/agents/codemie-code/validators/planValidator.d.ts +94 -0
  77. package/dist/agents/codemie-code/validators/planValidator.d.ts.map +1 -0
  78. package/dist/agents/codemie-code/validators/planValidator.js +281 -0
  79. package/dist/agents/codemie-code/validators/planValidator.js.map +1 -0
  80. package/dist/agents/registry.d.ts.map +1 -1
  81. package/dist/agents/registry.js +7 -5
  82. package/dist/agents/registry.js.map +1 -1
  83. package/dist/cli/commands/auth.d.ts +3 -0
  84. package/dist/cli/commands/auth.d.ts.map +1 -0
  85. package/dist/cli/commands/auth.js +170 -0
  86. package/dist/cli/commands/auth.js.map +1 -0
  87. package/dist/cli/commands/config.d.ts.map +1 -1
  88. package/dist/cli/commands/config.js +40 -13
  89. package/dist/cli/commands/config.js.map +1 -1
  90. package/dist/cli/commands/doctor.d.ts.map +1 -1
  91. package/dist/cli/commands/doctor.js +209 -16
  92. package/dist/cli/commands/doctor.js.map +1 -1
  93. package/dist/cli/commands/env.js +3 -3
  94. package/dist/cli/commands/env.js.map +1 -1
  95. package/dist/cli/commands/install.d.ts.map +1 -1
  96. package/dist/cli/commands/install.js +2 -1
  97. package/dist/cli/commands/install.js.map +1 -1
  98. package/dist/cli/commands/run.d.ts.map +1 -1
  99. package/dist/cli/commands/run.js +15 -9
  100. package/dist/cli/commands/run.js.map +1 -1
  101. package/dist/cli/commands/setup.d.ts.map +1 -1
  102. package/dist/cli/commands/setup.js +177 -11
  103. package/dist/cli/commands/setup.js.map +1 -1
  104. package/dist/cli/commands/tools.d.ts +6 -0
  105. package/dist/cli/commands/tools.d.ts.map +1 -0
  106. package/dist/cli/commands/tools.js +244 -0
  107. package/dist/cli/commands/tools.js.map +1 -0
  108. package/dist/cli/commands/version.js +1 -1
  109. package/dist/cli/commands/version.js.map +1 -1
  110. package/dist/cli/commands/workflow.d.ts +6 -0
  111. package/dist/cli/commands/workflow.d.ts.map +1 -0
  112. package/dist/cli/commands/workflow.js +424 -0
  113. package/dist/cli/commands/workflow.js.map +1 -0
  114. package/dist/cli/index.js +39 -5
  115. package/dist/cli/index.js.map +1 -1
  116. package/dist/clients/adapters/github.d.ts +17 -0
  117. package/dist/clients/adapters/github.d.ts.map +1 -0
  118. package/dist/clients/adapters/github.js +150 -0
  119. package/dist/clients/adapters/github.js.map +1 -0
  120. package/dist/clients/adapters/gitlab.d.ts +17 -0
  121. package/dist/clients/adapters/gitlab.d.ts.map +1 -0
  122. package/dist/clients/adapters/gitlab.js +147 -0
  123. package/dist/clients/adapters/gitlab.js.map +1 -0
  124. package/dist/clients/registry.d.ts +20 -0
  125. package/dist/clients/registry.d.ts.map +1 -0
  126. package/dist/clients/registry.js +27 -0
  127. package/dist/clients/registry.js.map +1 -0
  128. package/dist/tools/detector.d.ts +33 -0
  129. package/dist/tools/detector.d.ts.map +1 -0
  130. package/dist/tools/detector.js +145 -0
  131. package/dist/tools/detector.js.map +1 -0
  132. package/dist/tools/index.d.ts +8 -0
  133. package/dist/tools/index.d.ts.map +1 -0
  134. package/dist/tools/index.js +8 -0
  135. package/dist/tools/index.js.map +1 -0
  136. package/dist/tools/manager.d.ts +21 -0
  137. package/dist/tools/manager.d.ts.map +1 -0
  138. package/dist/tools/manager.js +104 -0
  139. package/dist/tools/manager.js.map +1 -0
  140. package/dist/tools/registry.d.ts +8 -0
  141. package/dist/tools/registry.d.ts.map +1 -0
  142. package/dist/tools/registry.js +36 -0
  143. package/dist/tools/registry.js.map +1 -0
  144. package/dist/tools/types.d.ts +41 -0
  145. package/dist/tools/types.d.ts.map +1 -0
  146. package/dist/tools/types.js +5 -0
  147. package/dist/tools/types.js.map +1 -0
  148. package/dist/types/sso.d.ts +42 -0
  149. package/dist/types/sso.d.ts.map +1 -0
  150. package/dist/types/sso.js +2 -0
  151. package/dist/types/sso.js.map +1 -0
  152. package/dist/utils/agent-compatibility.d.ts +32 -0
  153. package/dist/utils/agent-compatibility.d.ts.map +1 -0
  154. package/dist/utils/agent-compatibility.js +140 -0
  155. package/dist/utils/agent-compatibility.js.map +1 -0
  156. package/dist/utils/codemie-integration-validator.d.ts +17 -0
  157. package/dist/utils/codemie-integration-validator.d.ts.map +1 -0
  158. package/dist/utils/codemie-integration-validator.js +105 -0
  159. package/dist/utils/codemie-integration-validator.js.map +1 -0
  160. package/dist/utils/codemie-model-fetcher.d.ts +11 -0
  161. package/dist/utils/codemie-model-fetcher.d.ts.map +1 -0
  162. package/dist/utils/codemie-model-fetcher.js +242 -0
  163. package/dist/utils/codemie-model-fetcher.js.map +1 -0
  164. package/dist/utils/config-loader.d.ts +23 -1
  165. package/dist/utils/config-loader.d.ts.map +1 -1
  166. package/dist/utils/config-loader.js +73 -27
  167. package/dist/utils/config-loader.js.map +1 -1
  168. package/dist/utils/credential-store.d.ts +16 -0
  169. package/dist/utils/credential-store.d.ts.map +1 -0
  170. package/dist/utils/credential-store.js +109 -0
  171. package/dist/utils/credential-store.js.map +1 -0
  172. package/dist/utils/first-time.d.ts +1 -1
  173. package/dist/utils/first-time.d.ts.map +1 -1
  174. package/dist/utils/first-time.js +52 -71
  175. package/dist/utils/first-time.js.map +1 -1
  176. package/dist/utils/health-checker.d.ts.map +1 -1
  177. package/dist/utils/health-checker.js +5 -1
  178. package/dist/utils/health-checker.js.map +1 -1
  179. package/dist/utils/model-fetcher.d.ts.map +1 -1
  180. package/dist/utils/model-fetcher.js +15 -2
  181. package/dist/utils/model-fetcher.js.map +1 -1
  182. package/dist/utils/sso-auth.d.ts +15 -0
  183. package/dist/utils/sso-auth.d.ts.map +1 -0
  184. package/dist/utils/sso-auth.js +207 -0
  185. package/dist/utils/sso-auth.js.map +1 -0
  186. package/dist/utils/sso-gateway.d.ts +47 -0
  187. package/dist/utils/sso-gateway.d.ts.map +1 -0
  188. package/dist/utils/sso-gateway.js +298 -0
  189. package/dist/utils/sso-gateway.js.map +1 -0
  190. package/dist/workflows/detector.d.ts +37 -0
  191. package/dist/workflows/detector.d.ts.map +1 -0
  192. package/dist/workflows/detector.js +160 -0
  193. package/dist/workflows/detector.js.map +1 -0
  194. package/dist/workflows/index.d.ts +8 -0
  195. package/dist/workflows/index.d.ts.map +1 -0
  196. package/dist/workflows/index.js +8 -0
  197. package/dist/workflows/index.js.map +1 -0
  198. package/dist/workflows/installer.d.ts +24 -0
  199. package/dist/workflows/installer.d.ts.map +1 -0
  200. package/dist/workflows/installer.js +105 -0
  201. package/dist/workflows/installer.js.map +1 -0
  202. package/dist/workflows/registry.d.ts +29 -0
  203. package/dist/workflows/registry.d.ts.map +1 -0
  204. package/dist/workflows/registry.js +54 -0
  205. package/dist/workflows/registry.js.map +1 -0
  206. package/dist/workflows/templates/github/metadata.d.ts +6 -0
  207. package/dist/workflows/templates/github/metadata.d.ts.map +1 -0
  208. package/dist/workflows/templates/github/metadata.js +111 -0
  209. package/dist/workflows/templates/github/metadata.js.map +1 -0
  210. package/dist/workflows/templates/gitlab/metadata.d.ts +6 -0
  211. package/dist/workflows/templates/gitlab/metadata.d.ts.map +1 -0
  212. package/dist/workflows/templates/gitlab/metadata.js +14 -0
  213. package/dist/workflows/templates/gitlab/metadata.js.map +1 -0
  214. package/dist/workflows/types.d.ts +71 -0
  215. package/dist/workflows/types.d.ts.map +1 -0
  216. package/dist/workflows/types.js +5 -0
  217. package/dist/workflows/types.js.map +1 -0
  218. package/package.json +19 -6
  219. package/src/workflows/templates/github/code-ci.yml +529 -0
  220. package/src/workflows/templates/github/inline-fix.yml +665 -0
  221. package/src/workflows/templates/github/pr-review.yml +677 -0
  222. package/.claude/agents/README.md +0 -298
  223. package/.claude/agents/release-manager.md +0 -857
  224. package/.codemie/guides/git-workflow.md +0 -493
  225. package/CLAUDE.md +0 -225
  226. package/config.example.json +0 -10
  227. package/dist/agents/codemie-code/streaming/events.d.ts +0 -7
  228. package/dist/agents/codemie-code/streaming/events.d.ts.map +0 -1
  229. package/dist/agents/codemie-code/streaming/events.js +0 -7
  230. package/dist/agents/codemie-code/streaming/events.js.map +0 -1
  231. package/dist/agents/codemie-code/streaming/formatter.d.ts +0 -2
  232. package/dist/agents/codemie-code/streaming/formatter.d.ts.map +0 -1
  233. package/dist/agents/codemie-code/streaming/formatter.js +0 -2
  234. package/dist/agents/codemie-code/streaming/formatter.js.map +0 -1
  235. package/dist/agents/codemie-code/streaming/ui.d.ts +0 -2
  236. package/dist/agents/codemie-code/streaming/ui.d.ts.map +0 -1
  237. package/dist/agents/codemie-code/streaming/ui.js +0 -2
  238. package/dist/agents/codemie-code/streaming/ui.js.map +0 -1
  239. package/dist/agents/codemie-code/tools/command.d.ts +0 -2
  240. package/dist/agents/codemie-code/tools/command.d.ts.map +0 -1
  241. package/dist/agents/codemie-code/tools/command.js +0 -2
  242. package/dist/agents/codemie-code/tools/command.js.map +0 -1
  243. package/dist/agents/codemie-code/tools/filesystem.d.ts +0 -2
  244. package/dist/agents/codemie-code/tools/filesystem.d.ts.map +0 -1
  245. package/dist/agents/codemie-code/tools/filesystem.js +0 -2
  246. package/dist/agents/codemie-code/tools/filesystem.js.map +0 -1
  247. package/dist/agents/codemie-code/tools/git.d.ts +0 -2
  248. package/dist/agents/codemie-code/tools/git.d.ts.map +0 -1
  249. package/dist/agents/codemie-code/tools/git.js +0 -2
  250. package/dist/agents/codemie-code/tools/git.js.map +0 -1
  251. package/dist/agents/codemie-code/tools/security.d.ts +0 -2
  252. package/dist/agents/codemie-code/tools/security.d.ts.map +0 -1
  253. package/dist/agents/codemie-code/tools/security.js +0 -2
  254. package/dist/agents/codemie-code/tools/security.js.map +0 -1
  255. package/eslint.config.mjs +0 -43
  256. package/scripts/README.md +0 -80
  257. package/scripts/release.sh +0 -156
@@ -3,14 +3,32 @@ import chalk from 'chalk';
3
3
  import { formatToolMetadata } from './toolMetadata.js';
4
4
  import { formatCost, formatTokens, formatTokenUsageSummary } from './tokenUtils.js';
5
5
  import { hasClipboardImage, getClipboardImage } from '../../utils/clipboard.js';
6
+ import { TodoPanel } from './ui/todoPanel.js';
7
+ import { getProgressTracker } from './ui/progressTracker.js';
8
+ import { TodoStateManager } from './tools/planning.js';
6
9
  /**
7
10
  * Terminal UI interface for CodeMie Agent using Clack
8
11
  */
9
12
  export class CodeMieTerminalUI {
10
13
  agent;
11
14
  currentSpinner;
15
+ todoPanel;
16
+ progressTracker;
17
+ planMode = false;
18
+ activePlanningPhase = null;
12
19
  constructor(agent) {
13
20
  this.agent = agent;
21
+ this.todoPanel = new TodoPanel({
22
+ showProgress: true,
23
+ compact: false
24
+ });
25
+ this.progressTracker = getProgressTracker({
26
+ realTimeUpdates: true,
27
+ showCelebrations: true,
28
+ compact: true
29
+ });
30
+ // Register for todo update events
31
+ TodoStateManager.addEventCallback(this.handleTodoUpdate.bind(this));
14
32
  }
15
33
  /**
16
34
  * Start interactive terminal session
@@ -20,13 +38,19 @@ export class CodeMieTerminalUI {
20
38
  intro(chalk.cyan('šŸ¤– CodeMie Native Agent'));
21
39
  const config = this.agent.getConfig();
22
40
  if (config) {
23
- note(`Provider: ${chalk.yellow(config.provider)}\n` +
24
- `Model: ${chalk.cyan(config.model)}\n` +
25
- `Working Directory: ${chalk.dim(config.workingDirectory)}`, 'Configuration');
41
+ // Use displayProvider for user-facing output, or fall back to normalized provider
42
+ const displayProvider = config.displayProvider || config.provider;
43
+ console.log(chalk.cyan('ā—‡ Configuration'));
44
+ console.log(` Provider: ${chalk.yellow(displayProvider)}`);
45
+ console.log(` Model: ${chalk.cyan(config.model)}`);
46
+ console.log(` Working Directory: ${chalk.dim(config.workingDirectory)}`);
47
+ console.log(` Mode: ${this.planMode ? chalk.green('Plan Mode') : chalk.yellow('Direct Mode')}`);
48
+ console.log('');
26
49
  }
27
50
  console.log(chalk.dim('Type /help for commands, /exit to quit'));
28
51
  console.log(chalk.dim('Enter = send, Shift+Enter = new line, Cmd+V = paste text'));
29
- console.log(chalk.dim('šŸ“ø Tab = insert clipboard image • Multiple images supported\n'));
52
+ console.log(chalk.dim('šŸ“ø Tab = insert clipboard image • Multiple images supported'));
53
+ console.log(chalk.dim('šŸ’” Press Ctrl+H for hotkeys, Ctrl+P to toggle plan mode\n'));
30
54
  // Main interaction loop
31
55
  while (true) {
32
56
  const input = await this.getMultilineInput();
@@ -44,8 +68,8 @@ export class CodeMieTerminalUI {
44
68
  }
45
69
  if (trimmed === '')
46
70
  continue;
47
- // Execute the task with streaming UI
48
- await this.executeTaskWithUI(trimmed, input.images);
71
+ // Execute the task with streaming UI (respecting current mode)
72
+ await this.executeTaskWithCurrentMode(trimmed, input.images);
49
73
  }
50
74
  }
51
75
  /**
@@ -55,127 +79,220 @@ export class CodeMieTerminalUI {
55
79
  return new Promise((resolve) => {
56
80
  if (!process.stdin.setRawMode) {
57
81
  // Fallback for environments without raw mode
58
- resolve(this.getFallbackInput());
82
+ this.getFallbackInput().then(resolve).catch(() => resolve(null));
59
83
  return;
60
84
  }
61
- process.stdin.setRawMode(true);
62
- process.stdin.resume();
63
- process.stdin.setEncoding('utf8');
64
- let lines = [];
65
- let currentLine = '';
66
- let isFirstLine = true;
67
- let escapeSequence = '';
68
- let images = [];
69
- let imageCounter = 0;
70
- const writePrompt = () => {
71
- const prompt = isFirstLine ? '> ' : '... ';
72
- process.stdout.write(prompt);
73
- };
74
- const cleanup = () => {
75
- process.stdin.setRawMode(false);
76
- process.stdin.pause();
77
- process.stdin.removeAllListeners('data');
78
- };
79
- writePrompt();
80
- process.stdin.on('data', (key) => {
81
- const data = key.toString('utf8');
82
- // Handle escape sequences
83
- if (data.startsWith('\x1b')) {
84
- escapeSequence += data;
85
- // Wait for complete escape sequence (timeout after short delay)
86
- setTimeout(() => {
87
- escapeSequence = '';
88
- }, 10);
85
+ let cleanupDone = false;
86
+ const performCleanup = () => {
87
+ if (cleanupDone)
89
88
  return;
89
+ cleanupDone = true;
90
+ try {
91
+ if (process.stdin.setRawMode) {
92
+ process.stdin.setRawMode(false);
93
+ }
94
+ process.stdin.pause();
95
+ process.stdin.removeAllListeners('data');
96
+ process.stdin.removeAllListeners('error');
90
97
  }
91
- // Check for Shift+Enter patterns on macOS
92
- // Shift+Enter in macOS Terminal typically sends: \r\n or \n\r
93
- if (data === '\r\n' || data === '\n\r' || (escapeSequence && data === '\r')) {
94
- // This is Shift+Enter - add line and continue
95
- lines.push(currentLine);
96
- currentLine = '';
97
- isFirstLine = false;
98
- process.stdout.write('\n');
99
- writePrompt();
100
- escapeSequence = '';
101
- return;
98
+ catch {
99
+ // Ignore cleanup errors
102
100
  }
103
- // Regular Enter - send message
104
- if (data === '\r' || data === '\n') {
105
- if (currentLine.trim() === '' && lines.length === 0) {
106
- // Empty input, continue asking
107
- writePrompt();
108
- return;
101
+ };
102
+ // Set up error handling first
103
+ process.stdin.once('error', (error) => {
104
+ performCleanup();
105
+ const config = this.agent.getConfig();
106
+ if (config?.debug) {
107
+ console.error('[DEBUG] Stdin error:', error);
108
+ }
109
+ resolve(null);
110
+ });
111
+ try {
112
+ process.stdin.setRawMode(true);
113
+ process.stdin.resume();
114
+ process.stdin.setEncoding('utf8');
115
+ let lines = [];
116
+ let currentLine = '';
117
+ let isFirstLine = true;
118
+ let escapeSequence = '';
119
+ let images = [];
120
+ let imageCounter = 0;
121
+ const writePrompt = () => {
122
+ try {
123
+ const prompt = isFirstLine ? '> ' : '... ';
124
+ process.stdout.write(prompt);
109
125
  }
110
- // Send the message
111
- if (currentLine.trim() !== '') {
112
- lines.push(currentLine);
126
+ catch {
127
+ // Ignore prompt write errors
113
128
  }
114
- process.stdout.write('\n');
115
- cleanup();
116
- resolve({
117
- text: lines.join('\n'),
118
- images: images
119
- });
120
- return;
121
- }
122
- // Ctrl+C
123
- if (data === '\u0003') {
124
- cleanup();
129
+ };
130
+ // Add timeout to prevent hanging indefinitely
131
+ const inputTimeout = setTimeout(() => {
132
+ performCleanup();
125
133
  resolve(null);
126
- return;
127
- }
128
- // Ctrl+I - Insert image from clipboard
129
- if (data === '\u0009') {
130
- // Check if there's an image in clipboard
131
- hasClipboardImage().then(hasImage => {
132
- if (hasImage) {
133
- getClipboardImage().then(clipboardImage => {
134
- if (clipboardImage) {
135
- imageCounter++;
136
- images.push(clipboardImage);
137
- // Insert visual indicator in current line
138
- const imageIndicator = chalk.blue(`[Image #${imageCounter}]`);
139
- currentLine += imageIndicator;
140
- process.stdout.write(imageIndicator);
141
- console.log(chalk.green(`\nšŸ“ø Image #${imageCounter} added from clipboard (${clipboardImage.mimeType})`));
134
+ }, 30000); // 30 second timeout
135
+ writePrompt();
136
+ process.stdin.on('data', (key) => {
137
+ if (cleanupDone)
138
+ return; // Prevent processing after cleanup
139
+ try {
140
+ const data = key.toString('utf8');
141
+ // Handle escape sequences
142
+ if (data.startsWith('\x1b')) {
143
+ escapeSequence += data;
144
+ // Wait for complete escape sequence (timeout after short delay)
145
+ setTimeout(() => {
146
+ escapeSequence = '';
147
+ }, 10);
148
+ return;
149
+ }
150
+ // Check for Shift+Enter patterns on macOS
151
+ // Shift+Enter in macOS Terminal typically sends: \r\n or \n\r
152
+ if (data === '\r\n' || data === '\n\r' || (escapeSequence && data === '\r')) {
153
+ // This is Shift+Enter - add line and continue
154
+ lines.push(currentLine);
155
+ currentLine = '';
156
+ isFirstLine = false;
157
+ process.stdout.write('\n');
158
+ writePrompt();
159
+ escapeSequence = '';
160
+ return;
161
+ }
162
+ // Regular Enter - send message
163
+ if (data === '\r' || data === '\n') {
164
+ if (currentLine.trim() === '' && lines.length === 0) {
165
+ // Empty input, continue asking
166
+ writePrompt();
167
+ return;
168
+ }
169
+ // Send the message
170
+ if (currentLine.trim() !== '') {
171
+ lines.push(currentLine);
172
+ }
173
+ process.stdout.write('\n');
174
+ clearTimeout(inputTimeout);
175
+ performCleanup();
176
+ resolve({
177
+ text: lines.join('\n'),
178
+ images: images
179
+ });
180
+ return;
181
+ }
182
+ // Ctrl+C
183
+ if (data === '\u0003') {
184
+ clearTimeout(inputTimeout);
185
+ performCleanup();
186
+ resolve(null);
187
+ return;
188
+ }
189
+ // Hotkeys for mode switching
190
+ // Ctrl+P - Toggle plan mode
191
+ if (data === '\u0010') {
192
+ this.handleHotkey('toggle-plan-mode');
193
+ // Mode change notification is handled in showModeChangeNotification()
194
+ writePrompt();
195
+ process.stdout.write(currentLine);
196
+ return;
197
+ }
198
+ // Ctrl+H - Show hotkey help
199
+ if (data === '\u0008') {
200
+ this.showHotkeyHelp();
201
+ writePrompt();
202
+ process.stdout.write(currentLine);
203
+ return;
204
+ }
205
+ // Ctrl+T - Show current todos
206
+ if (data === '\u0014') {
207
+ this.handleHotkey('show-todos');
208
+ writePrompt();
209
+ process.stdout.write(currentLine);
210
+ return;
211
+ }
212
+ // Ctrl+S - Show current mode status (changed from Ctrl+M to avoid Enter conflict)
213
+ if (data === '\u0013') {
214
+ this.showModeStatus();
215
+ writePrompt();
216
+ process.stdout.write(currentLine);
217
+ return;
218
+ }
219
+ // Alt+M - Show mode status (Alt key sequences start with \x1b)
220
+ if (data === 'm' && escapeSequence.includes('\x1b')) {
221
+ this.showModeStatus();
222
+ writePrompt();
223
+ process.stdout.write(currentLine);
224
+ escapeSequence = '';
225
+ return;
226
+ }
227
+ // Ctrl+I - Insert image from clipboard (Tab key)
228
+ if (data === '\u0009') {
229
+ // Check if there's an image in clipboard
230
+ hasClipboardImage().then(hasImage => {
231
+ if (hasImage) {
232
+ getClipboardImage().then(clipboardImage => {
233
+ if (clipboardImage) {
234
+ imageCounter++;
235
+ images.push(clipboardImage);
236
+ // Insert visual indicator in current line
237
+ const imageIndicator = chalk.blueBright(`[Image #${imageCounter}]`);
238
+ currentLine += imageIndicator;
239
+ process.stdout.write(imageIndicator);
240
+ console.log(chalk.green(`\nšŸ“ø Image #${imageCounter} added from clipboard (${clipboardImage.mimeType})`));
241
+ writePrompt();
242
+ process.stdout.write(currentLine);
243
+ }
244
+ });
245
+ }
246
+ else {
247
+ console.log(chalk.yellow('\nāš ļø No image found in clipboard'));
142
248
  writePrompt();
143
249
  process.stdout.write(currentLine);
144
250
  }
251
+ }).catch(() => {
252
+ console.log(chalk.rgb(255, 120, 120)('\nāŒ Error accessing clipboard'));
253
+ writePrompt();
254
+ process.stdout.write(currentLine);
145
255
  });
256
+ return;
146
257
  }
147
- else {
148
- console.log(chalk.yellow('\nāš ļø No image found in clipboard'));
149
- writePrompt();
150
- process.stdout.write(currentLine);
258
+ // Backspace
259
+ if (data === '\u007F' || data === '\b') {
260
+ if (currentLine.length > 0) {
261
+ currentLine = currentLine.slice(0, -1);
262
+ process.stdout.write('\b \b');
263
+ }
264
+ return;
151
265
  }
152
- }).catch(() => {
153
- console.log(chalk.red('\nāŒ Error accessing clipboard'));
154
- writePrompt();
155
- process.stdout.write(currentLine);
156
- });
157
- return;
158
- }
159
- // Backspace
160
- if (data === '\u007F' || data === '\b') {
161
- if (currentLine.length > 0) {
162
- currentLine = currentLine.slice(0, -1);
163
- process.stdout.write('\b \b');
266
+ // Handle clipboard paste (Cmd+V on macOS, Ctrl+V on Windows/Linux)
267
+ // Pasted content can be multiple characters, so handle any printable text
268
+ if (data.length > 1 || (data.length === 1 && (data.charCodeAt(0) >= 32 || data === '\t'))) {
269
+ // Filter out non-printable characters except tabs
270
+ const printableData = data.split('').filter(char => char.charCodeAt(0) >= 32 || char === '\t').join('');
271
+ if (printableData.length > 0) {
272
+ currentLine += printableData;
273
+ process.stdout.write(printableData);
274
+ }
275
+ }
276
+ escapeSequence = '';
164
277
  }
165
- return;
166
- }
167
- // Handle clipboard paste (Cmd+V on macOS, Ctrl+V on Windows/Linux)
168
- // Pasted content can be multiple characters, so handle any printable text
169
- if (data.length > 1 || (data.length === 1 && (data.charCodeAt(0) >= 32 || data === '\t'))) {
170
- // Filter out non-printable characters except tabs
171
- const printableData = data.split('').filter(char => char.charCodeAt(0) >= 32 || char === '\t').join('');
172
- if (printableData.length > 0) {
173
- currentLine += printableData;
174
- process.stdout.write(printableData);
278
+ catch (error) {
279
+ // Handle data processing errors gracefully
280
+ const config = this.agent.getConfig();
281
+ if (config?.debug) {
282
+ console.error('[DEBUG] Input processing error:', error);
283
+ }
284
+ // Continue processing - don't crash on single key errors
175
285
  }
286
+ });
287
+ }
288
+ catch (error) {
289
+ performCleanup();
290
+ const config = this.agent.getConfig();
291
+ if (config?.debug) {
292
+ console.error('[DEBUG] Input setup error:', error);
176
293
  }
177
- escapeSequence = '';
178
- });
294
+ resolve(null);
295
+ }
179
296
  });
180
297
  }
181
298
  /**
@@ -207,9 +324,15 @@ export class CodeMieTerminalUI {
207
324
  note(`${chalk.cyan('/help')} - Show this help message\n` +
208
325
  `${chalk.cyan('/clear')} - Clear conversation history\n` +
209
326
  `${chalk.cyan('/stats')} - Show agent statistics\n` +
327
+ `${chalk.cyan('/todos')} - Show current todo list and progress\n` +
210
328
  `${chalk.cyan('/config')} - Show configuration\n` +
211
329
  `${chalk.cyan('/health')} - Run health check\n` +
212
330
  `${chalk.cyan('/exit')} - Exit the agent\n\n` +
331
+ `${chalk.yellow('Hotkeys:')}\n` +
332
+ `- ${chalk.cyan('Ctrl+P')} - Toggle plan mode on/off\n` +
333
+ `- ${chalk.cyan('Ctrl+H')} - Show detailed hotkey help\n` +
334
+ `- ${chalk.cyan('Ctrl+T')} - Show current todo list\n` +
335
+ `- ${chalk.cyan('Ctrl+S')} - Show current mode status\n\n` +
213
336
  `${chalk.yellow('Input Controls:')}\n` +
214
337
  `- ${chalk.cyan('Enter')} - Send message\n` +
215
338
  `- ${chalk.cyan('Shift+Enter')} - New line (multiline input)\n` +
@@ -218,7 +341,7 @@ export class CodeMieTerminalUI {
218
341
  `- ${chalk.cyan('Ctrl+C')} - Cancel current input\n\n` +
219
342
  `${chalk.yellow('Image Support:')}\n` +
220
343
  `- Copy image/screenshot to clipboard\n` +
221
- `- Press ${chalk.cyan('Tab')} to insert as ${chalk.blue('[Image #N]')}\n` +
344
+ `- Press ${chalk.cyan('Tab')} to insert as ${chalk.blueBright('[Image #N]')}\n` +
222
345
  `- Multiple images supported per message\n` +
223
346
  `- AI analyzes both text and all images`, 'Available Commands');
224
347
  break;
@@ -229,6 +352,9 @@ export class CodeMieTerminalUI {
229
352
  case 'stats':
230
353
  await this.showStats();
231
354
  break;
355
+ case 'todos':
356
+ await this.showTodos();
357
+ break;
232
358
  case 'config':
233
359
  await this.showConfig();
234
360
  break;
@@ -239,7 +365,7 @@ export class CodeMieTerminalUI {
239
365
  outro(chalk.dim('Goodbye! šŸ‘‹'));
240
366
  return 'exit';
241
367
  default:
242
- note(chalk.red(`Unknown command: ${command}\nType /help for available commands`), 'Error');
368
+ note(chalk.rgb(255, 120, 120)(`Unknown command: ${command}\nType /help for available commands`), 'Error');
243
369
  break;
244
370
  }
245
371
  }
@@ -284,6 +410,17 @@ export class CodeMieTerminalUI {
284
410
  }
285
411
  this.currentSpinner.start(chalk.yellow(`Using ${event.toolName}...`));
286
412
  break;
413
+ case 'tool_call_progress':
414
+ if (this.currentSpinner && event.toolProgress) {
415
+ const { percentage, operation, details } = event.toolProgress;
416
+ const progressBar = this.createToolProgressBar(percentage);
417
+ let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
418
+ if (details) {
419
+ message += ` (${details})`;
420
+ }
421
+ this.currentSpinner.message(chalk.cyan(message));
422
+ }
423
+ break;
287
424
  case 'tool_call_result':
288
425
  if (this.currentSpinner) {
289
426
  // Use enhanced metadata if available, otherwise fall back to basic message
@@ -368,8 +505,10 @@ export class CodeMieTerminalUI {
368
505
  note('No configuration available', 'Config');
369
506
  return;
370
507
  }
508
+ // Use displayProvider for user-facing output, or fall back to normalized provider
509
+ const displayProvider = config.displayProvider || config.provider;
371
510
  const configText = [
372
- `Provider: ${chalk.yellow(config.provider)}`,
511
+ `Provider: ${chalk.yellow(displayProvider)}`,
373
512
  `Model: ${chalk.cyan(config.model)}`,
374
513
  `Base URL: ${chalk.dim(config.baseUrl)}`,
375
514
  `Working Directory: ${chalk.dim(config.workingDirectory)}`,
@@ -450,6 +589,17 @@ export class CodeMieTerminalUI {
450
589
  toolCallCount++;
451
590
  taskSpinner.message(chalk.yellow(`Using ${event.toolName}...`));
452
591
  break;
592
+ case 'tool_call_progress':
593
+ if (event.toolProgress) {
594
+ const { percentage, operation, details } = event.toolProgress;
595
+ const progressBar = this.createToolProgressBar(percentage);
596
+ let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
597
+ if (details) {
598
+ message += ` (${details})`;
599
+ }
600
+ taskSpinner.message(chalk.cyan(message));
601
+ }
602
+ break;
453
603
  case 'tool_call_result':
454
604
  // Show enhanced tool info in single task mode too
455
605
  if (event.toolMetadata) {
@@ -491,23 +641,98 @@ export class CodeMieTerminalUI {
491
641
  }
492
642
  }
493
643
  /**
494
- * Show a welcome message for single task execution
495
- */
496
- showTaskWelcome(task) {
497
- intro(chalk.cyan('šŸ¤– CodeMie Native Agent'));
498
- note(chalk.dim(`Task: ${task}`), 'Executing');
499
- }
500
- /**
501
- * Show task completion message
644
+ * Execute a task with planning mode and UI streaming (for plan mode)
502
645
  */
503
- showTaskComplete() {
504
- outro(chalk.green('Task completed successfully'));
505
- }
506
- /**
507
- * Show error message
508
- */
509
- showError(error) {
510
- outro(chalk.red(`Error: ${error}`));
646
+ async executePlanningTask(task, _images = [], planOnly = false) {
647
+ const planSpinner = spinner();
648
+ this.currentSpinner = planSpinner; // Store reference to stop when progress starts
649
+ planSpinner.start(chalk.blueBright('šŸ“‹ Starting planning phase...'));
650
+ try {
651
+ // Import PlanMode
652
+ const { PlanMode } = await import('./modes/planMode.js');
653
+ const planMode = new PlanMode(this.agent, {
654
+ requirePlanning: true,
655
+ enforceSequential: true,
656
+ showPlanningFeedback: true
657
+ });
658
+ // Create a UI-connected event callback that handles both planning and streaming events
659
+ const uiEventCallback = (event) => {
660
+ // Handle planning-specific events
661
+ this.handleStreamingEvent(event);
662
+ // Handle regular streaming events too
663
+ switch (event.type) {
664
+ case 'thinking_start':
665
+ planSpinner.message(chalk.dim('Thinking...'));
666
+ break;
667
+ case 'content_chunk':
668
+ // For planning phase, we might not want to show content chunks immediately
669
+ break;
670
+ case 'tool_call_start':
671
+ planSpinner.message(chalk.yellow(`Using ${event.toolName}...`));
672
+ break;
673
+ case 'tool_call_progress':
674
+ if (event.toolProgress) {
675
+ const { percentage, operation, details } = event.toolProgress;
676
+ const progressBar = this.createToolProgressBar(percentage);
677
+ let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
678
+ if (details) {
679
+ message += ` (${details})`;
680
+ }
681
+ planSpinner.message(chalk.cyan(message));
682
+ }
683
+ break;
684
+ case 'tool_call_result':
685
+ if (event.toolMetadata) {
686
+ const message = formatToolMetadata(event.toolName || 'tool', event.toolMetadata);
687
+ planSpinner.message(chalk.green(message));
688
+ }
689
+ else {
690
+ planSpinner.message(chalk.dim('Processing...'));
691
+ }
692
+ break;
693
+ case 'complete':
694
+ planSpinner.stop();
695
+ break;
696
+ case 'error':
697
+ planSpinner.stop(chalk.red(`Error: ${event.error}`));
698
+ break;
699
+ }
700
+ };
701
+ if (planOnly) {
702
+ // Only generate plan, don't execute
703
+ const planningResult = await planMode.planningPhase(task, uiEventCallback);
704
+ planSpinner.stop();
705
+ if (!planningResult.success) {
706
+ throw new Error(`Planning failed: ${planningResult.error}`);
707
+ }
708
+ return `šŸ“‹ Plan generated successfully with ${planningResult.todos.length} steps:\n\n` +
709
+ planningResult.todos.map((todo, i) => `${i + 1}. ${todo.content}`).join('\n') +
710
+ `\n\nQuality Score: ${planningResult.qualityScore}/100\n` +
711
+ (planningResult.suggestions.length > 0 ?
712
+ `\nSuggestions:\n${planningResult.suggestions.map((s) => `• ${s}`).join('\n')}` : '') +
713
+ `\n\nšŸŽÆ **Plan-only mode**: Plan created. Use --plan flag (without --plan-only) to execute this plan.`;
714
+ }
715
+ // Full planning + execution
716
+ const result = await planMode.executePlannedTask(task, uiEventCallback);
717
+ planSpinner.stop();
718
+ // Show success message with token usage
719
+ const stats = this.agent.getStats();
720
+ const summaryParts = [];
721
+ if (stats.totalTokens > 0) {
722
+ summaryParts.push(`${formatTokens(stats.totalTokens)} tokens`);
723
+ }
724
+ if (stats.estimatedTotalCost > 0) {
725
+ summaryParts.push(`${formatCost(stats.estimatedTotalCost)}`);
726
+ }
727
+ if (summaryParts.length > 0) {
728
+ console.log(chalk.dim(`${summaryParts.join(' • ')}\n`));
729
+ }
730
+ return result;
731
+ }
732
+ catch (error) {
733
+ planSpinner.stop(chalk.red('Planning failed'));
734
+ throw error;
735
+ }
511
736
  }
512
737
  /**
513
738
  * Check if we should show detailed information for a tool
@@ -611,6 +836,562 @@ export class CodeMieTerminalUI {
611
836
  }
612
837
  note(stepLines.join('\n'), 'Step Details');
613
838
  }
839
+ /**
840
+ * Handle todo update events
841
+ */
842
+ handleTodoUpdate(event) {
843
+ // Update internal state
844
+ this.todoPanel.update(event.todos);
845
+ // Only show progress tracker updates when NOT in active planning phase
846
+ // This prevents duplicate progress displays during context-aware planning
847
+ if (!this.activePlanningPhase) {
848
+ this.progressTracker.updateTodos(event.todos, event);
849
+ // Show visual feedback based on change type
850
+ if (event.changeType === 'create' && event.todos.length > 0) {
851
+ this.progressTracker.showPlanningComplete(event.todos.length);
852
+ }
853
+ }
854
+ else {
855
+ // During planning phase, update internal state but suppress visual feedback
856
+ this.progressTracker.updateTodos(event.todos, event, true);
857
+ }
858
+ }
859
+ /**
860
+ * Enable plan mode for structured planning
861
+ */
862
+ enablePlanMode() {
863
+ this.planMode = true;
864
+ // Remove redundant messages - UI will show plan mode status in the configuration display
865
+ }
866
+ /**
867
+ * Disable plan mode
868
+ */
869
+ disablePlanMode() {
870
+ this.planMode = false;
871
+ this.progressTracker.stop();
872
+ }
873
+ /**
874
+ * Show current todo status
875
+ */
876
+ async showTodos() {
877
+ const _todos = this.todoPanel.getTodos();
878
+ if (_todos.length === 0) {
879
+ note('No todos found. Use a planning task to create todos automatically.', 'šŸ“‹ Todo Status');
880
+ return;
881
+ }
882
+ const todoDisplay = this.todoPanel.render();
883
+ note(todoDisplay, 'šŸ“‹ Current Todo List');
884
+ }
885
+ /**
886
+ * Show planning phase welcome
887
+ */
888
+ showPlanningWelcome() {
889
+ if (this.planMode) {
890
+ console.log(chalk.cyan('šŸ’” Plan mode features: structured planning, progress tracking, sequential execution'));
891
+ console.log('');
892
+ }
893
+ }
894
+ /**
895
+ * Show task welcome with planning context
896
+ */
897
+ showTaskWelcome(task) {
898
+ intro(chalk.cyan('šŸ¤– CodeMie Native Agent'));
899
+ if (this.planMode) {
900
+ console.log(chalk.cyan('ā—‡ Task Execution'));
901
+ console.log(` Task: ${chalk.yellow(task)}`);
902
+ console.log(` Mode: ${chalk.cyan('Plan Mode')} - Structured planning enabled`);
903
+ console.log('');
904
+ this.showPlanningWelcome();
905
+ }
906
+ else {
907
+ console.log(chalk.cyan('ā—‡ Task Execution'));
908
+ console.log(` Task: ${chalk.yellow(task)}`);
909
+ console.log('');
910
+ }
911
+ }
912
+ /**
913
+ * Show task completion with todo summary
914
+ */
915
+ showTaskComplete() {
916
+ const _todos = this.todoPanel.getTodos();
917
+ const progressInfo = this.todoPanel.getProgressInfo();
918
+ if (progressInfo && progressInfo.total > 0) {
919
+ const stats = {
920
+ tasksCompleted: progressInfo.completed,
921
+ totalTime: undefined // Could track this if needed
922
+ };
923
+ this.progressTracker.showOverallCompletion(stats);
924
+ // Show final todo status if relevant
925
+ if (progressInfo.completed === progressInfo.total) {
926
+ note('All planned tasks completed successfully! šŸŽ‰', 'āœ… Task Complete');
927
+ }
928
+ else if (progressInfo.completed > 0) {
929
+ note(`Completed ${progressInfo.completed}/${progressInfo.total} planned tasks`, 'šŸ“Š Progress Summary');
930
+ }
931
+ }
932
+ else {
933
+ outro(chalk.green('āœ… Task completed!'));
934
+ }
935
+ }
936
+ /**
937
+ * Show error with todo context
938
+ */
939
+ showError(error) {
940
+ const _todos = this.todoPanel.getTodos();
941
+ const progressInfo = this.todoPanel.getProgressInfo();
942
+ let contextInfo = '';
943
+ if (progressInfo?.currentTodo) {
944
+ contextInfo = `\nšŸ“ Error occurred while working on: ${progressInfo.currentTodo.content}`;
945
+ }
946
+ note(chalk.rgb(255, 120, 120)(`āŒ ${error}${contextInfo}`), 'Error');
947
+ }
948
+ /**
949
+ * Execute task respecting current mode settings
950
+ */
951
+ async executeTaskWithCurrentMode(task, images = []) {
952
+ if (this.planMode) {
953
+ // Use plan mode execution with confirmation
954
+ try {
955
+ const { PlanMode } = await import('./modes/planMode.js');
956
+ const planMode = new PlanMode(this.agent, {
957
+ requirePlanning: true,
958
+ enforceSequential: true,
959
+ showPlanningFeedback: true
960
+ });
961
+ // First, create the plan only - using UI-integrated planning
962
+ const planningResult = await planMode.planningPhase(task, (event) => {
963
+ // Handle planning-specific events with UI integration
964
+ this.handleStreamingEvent(event);
965
+ // Handle regular streaming events for spinner updates
966
+ switch (event.type) {
967
+ case 'thinking_start':
968
+ // Already handled in handleStreamingEvent
969
+ break;
970
+ case 'tool_call_start':
971
+ // Already handled in handleStreamingEvent
972
+ break;
973
+ case 'tool_call_result':
974
+ // Already handled in handleStreamingEvent
975
+ break;
976
+ }
977
+ });
978
+ if (!planningResult.success) {
979
+ this.showError(`Planning failed: ${planningResult.error}`);
980
+ return;
981
+ }
982
+ // Show the plan to the user with proper formatting
983
+ const formattedPlan = this.formatPlanForDisplay(planningResult.todos, planningResult.qualityScore);
984
+ note(formattedPlan, 'Planning Complete');
985
+ // Ask for confirmation
986
+ const shouldExecute = await text({
987
+ message: 'Execute this plan?',
988
+ placeholder: 'Type "yes" to execute, or "no" to cancel',
989
+ validate: (value) => {
990
+ const normalized = value.toLowerCase().trim();
991
+ if (!['yes', 'y', 'no', 'n'].includes(normalized)) {
992
+ return 'Please type "yes" or "no"';
993
+ }
994
+ }
995
+ });
996
+ if (isCancel(shouldExecute)) {
997
+ note('Plan execution cancelled by user', 'Cancelled');
998
+ return;
999
+ }
1000
+ const response = shouldExecute.toLowerCase().trim();
1001
+ if (['yes', 'y'].includes(response)) {
1002
+ // Execute the plan
1003
+ await planMode.executePlannedTask(task, (event) => {
1004
+ this.handleStreamingEvent(event);
1005
+ });
1006
+ }
1007
+ else {
1008
+ note('Plan execution cancelled by user', 'Cancelled');
1009
+ }
1010
+ }
1011
+ catch (error) {
1012
+ this.showError(error instanceof Error ? error.message : String(error));
1013
+ }
1014
+ }
1015
+ else {
1016
+ // Use direct execution
1017
+ await this.executeTaskWithUI(task, images);
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Handle streaming events from plan mode execution
1022
+ */
1023
+ handleStreamingEvent(event) {
1024
+ switch (event.type) {
1025
+ case 'planning_start':
1026
+ this.activePlanningPhase = event.planningInfo?.phase || 'planning';
1027
+ // Don't duplicate the planning start message - handled by spinner
1028
+ break;
1029
+ case 'planning_complete':
1030
+ this.activePlanningPhase = null;
1031
+ console.log(chalk.green(`šŸ“‹ Plan created with ${event.planningInfo?.totalSteps || 0} steps`));
1032
+ break;
1033
+ case 'planning_progress':
1034
+ this.handlePlanningProgress(event.planningProgress);
1035
+ break;
1036
+ case 'planning_tool_call':
1037
+ this.handlePlanningToolCall(event.planningToolCall);
1038
+ break;
1039
+ case 'content_chunk':
1040
+ if (event.content) {
1041
+ process.stdout.write(event.content);
1042
+ }
1043
+ break;
1044
+ case 'todo_update':
1045
+ // Todo updates are handled automatically by the TodoStateManager
1046
+ // During planning phase, we suppress duplicate visual feedback
1047
+ break;
1048
+ case 'error':
1049
+ this.activePlanningPhase = null;
1050
+ this.showError(event.error || 'Unknown error');
1051
+ break;
1052
+ default:
1053
+ // Handle other event types silently
1054
+ break;
1055
+ }
1056
+ }
1057
+ /**
1058
+ * Handle planning progress streaming events
1059
+ */
1060
+ currentPhase = '';
1061
+ currentTool = '';
1062
+ currentToolCall = '';
1063
+ handlePlanningProgress(progressInfo) {
1064
+ if (!progressInfo)
1065
+ return;
1066
+ // Keep the spinner running and use it to display progress updates
1067
+ const phaseNames = {
1068
+ 'context_gathering': 'Discovery',
1069
+ 'task_analysis': 'Analysis',
1070
+ 'plan_generation': 'Planning',
1071
+ 'plan_validation': 'Validation'
1072
+ };
1073
+ this.currentPhase = phaseNames[progressInfo.phase] || 'Planning';
1074
+ this.updateGlobalProgress(progressInfo.overallProgress || 0);
1075
+ }
1076
+ /**
1077
+ * Handle planning tool call events
1078
+ */
1079
+ handlePlanningToolCall(toolInfo) {
1080
+ if (!toolInfo)
1081
+ return;
1082
+ const toolNames = {
1083
+ 'list_directory': 'Exploring',
1084
+ 'read_file': 'Reading',
1085
+ 'execute_command': 'Executing',
1086
+ 'llm_analysis': 'Analyzing',
1087
+ 'llm_plan_generation': 'Generating Plan',
1088
+ 'plan_validation': 'Validating',
1089
+ 'analyze_dependencies': 'Dependencies'
1090
+ };
1091
+ this.currentTool = toolNames[toolInfo.toolName] || 'Tool';
1092
+ // Format tool call details with arguments
1093
+ this.currentToolCall = this.formatToolCall(toolInfo.toolName, toolInfo.args);
1094
+ // Update progress with current tool info
1095
+ this.updateGlobalProgress();
1096
+ }
1097
+ /**
1098
+ * Format tool call with arguments for display
1099
+ */
1100
+ formatToolCall(toolName, args) {
1101
+ if (!args || Object.keys(args).length === 0) {
1102
+ return toolName;
1103
+ }
1104
+ // Extract key arguments for concise display
1105
+ const formatArg = (key, value) => {
1106
+ if (typeof value === 'string') {
1107
+ // Truncate long strings and show just the relevant part
1108
+ if (key === 'path' || key === 'directory') {
1109
+ // Show just the last part of paths
1110
+ const parts = value.split('/');
1111
+ return parts[parts.length - 1] || value;
1112
+ }
1113
+ if (key === 'command') {
1114
+ // Show first word of commands
1115
+ return value.split(' ')[0];
1116
+ }
1117
+ if (value.length > 20) {
1118
+ return value.substring(0, 17) + '...';
1119
+ }
1120
+ return value;
1121
+ }
1122
+ if (Array.isArray(value)) {
1123
+ return `[${value.length} items]`;
1124
+ }
1125
+ if (typeof value === 'object') {
1126
+ return '{...}';
1127
+ }
1128
+ return String(value);
1129
+ };
1130
+ // Pick the most relevant arguments to show
1131
+ const relevantKeys = ['path', 'directory', 'file', 'command', 'query', 'pattern'];
1132
+ const argsToShow = [];
1133
+ for (const key of relevantKeys) {
1134
+ if (key in args) {
1135
+ argsToShow.push(formatArg(key, args[key]));
1136
+ if (argsToShow.length >= 2)
1137
+ break; // Show max 2 args
1138
+ }
1139
+ }
1140
+ // If no relevant keys found, show first few keys
1141
+ if (argsToShow.length === 0) {
1142
+ const keys = Object.keys(args).slice(0, 2);
1143
+ for (const key of keys) {
1144
+ argsToShow.push(formatArg(key, args[key]));
1145
+ }
1146
+ }
1147
+ return argsToShow.length > 0 ? `${toolName} (${argsToShow.join(', ')})` : toolName;
1148
+ }
1149
+ /**
1150
+ * Update global progress display
1151
+ */
1152
+ currentProgress = 0;
1153
+ updateGlobalProgress(progress) {
1154
+ if (progress !== undefined) {
1155
+ this.currentProgress = progress;
1156
+ }
1157
+ const progressBar = this.createProgressBar(this.currentProgress);
1158
+ let displayText = `Progress ${Math.round(this.currentProgress)}% ${progressBar}`;
1159
+ // Show tool call details if available, otherwise fall back to tool name or phase
1160
+ // This provides detailed info like "(Discovery, calling list_dir (src))"
1161
+ if (this.currentToolCall) {
1162
+ displayText += ` (${this.currentPhase}, calling ${this.currentToolCall})`;
1163
+ }
1164
+ else if (this.currentTool) {
1165
+ displayText += ` (${this.currentTool})`;
1166
+ }
1167
+ else if (this.currentPhase) {
1168
+ displayText += ` (${this.currentPhase})`;
1169
+ }
1170
+ // Use clack-style spinner update for reliable real-time display
1171
+ if (this.currentSpinner) {
1172
+ this.currentSpinner.message(displayText);
1173
+ }
1174
+ else {
1175
+ // Fallback to direct output if no spinner
1176
+ process.stdout.write('\r' + displayText);
1177
+ }
1178
+ // Only add newline when planning is completely finished (100%)
1179
+ if (this.currentProgress >= 100) {
1180
+ if (this.currentSpinner) {
1181
+ this.currentSpinner.stop(displayText);
1182
+ this.currentSpinner = undefined;
1183
+ }
1184
+ else {
1185
+ process.stdout.write('\n');
1186
+ }
1187
+ this.currentPhase = '';
1188
+ this.currentTool = '';
1189
+ this.currentToolCall = '';
1190
+ }
1191
+ }
1192
+ /**
1193
+ * Create a simple progress bar
1194
+ */
1195
+ createProgressBar(percentage, width = 20) {
1196
+ const filled = Math.round((percentage / 100) * width);
1197
+ const empty = width - filled;
1198
+ const filledBar = chalk.cyan('ā–ˆ'.repeat(filled));
1199
+ const emptyBar = chalk.dim('ā–‘'.repeat(empty));
1200
+ return `[${filledBar}${emptyBar}]`;
1201
+ }
1202
+ /**
1203
+ * Create a tool progress bar (smaller for inline use)
1204
+ */
1205
+ createToolProgressBar(percentage, width = 12) {
1206
+ const filled = Math.round((percentage / 100) * width);
1207
+ const empty = width - filled;
1208
+ const filledBar = chalk.green('ā–ˆ'.repeat(filled));
1209
+ const emptyBar = chalk.dim('ā–‘'.repeat(empty));
1210
+ return `[${filledBar}${emptyBar}]`;
1211
+ }
1212
+ /**
1213
+ * Handle hotkey actions
1214
+ */
1215
+ handleHotkey(action) {
1216
+ switch (action) {
1217
+ case 'toggle-plan-mode':
1218
+ this.togglePlanMode();
1219
+ break;
1220
+ case 'show-todos':
1221
+ this.showTodosHotkey();
1222
+ break;
1223
+ case 'show-help':
1224
+ this.showHotkeyHelp();
1225
+ break;
1226
+ case 'show-status':
1227
+ this.showModeStatus();
1228
+ break;
1229
+ default:
1230
+ console.log(chalk.red(`\nāŒ Unknown hotkey action: ${action}`));
1231
+ break;
1232
+ }
1233
+ }
1234
+ /**
1235
+ * Toggle plan mode on/off
1236
+ */
1237
+ togglePlanMode() {
1238
+ const wasEnabled = this.planMode;
1239
+ if (this.planMode) {
1240
+ this.disablePlanMode();
1241
+ }
1242
+ else {
1243
+ this.enablePlanMode();
1244
+ }
1245
+ // Show enhanced visual feedback
1246
+ this.showModeChangeNotification(wasEnabled, this.planMode);
1247
+ }
1248
+ /**
1249
+ * Show enhanced visual feedback for mode changes
1250
+ */
1251
+ showModeChangeNotification(wasEnabled, nowEnabled) {
1252
+ const modeIcon = nowEnabled ? 'šŸ“‹' : '⚔';
1253
+ const statusText = nowEnabled ? 'enabled' : 'disabled';
1254
+ // Simple, concise mode change message
1255
+ console.log(chalk.blueBright(`\n${modeIcon} Plan mode ${statusText}`));
1256
+ // Start progress tracking when plan mode is enabled (but don't show 0/0 progress yet)
1257
+ if (nowEnabled) {
1258
+ this.progressTracker.start();
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Show todos via hotkey (non-blocking)
1263
+ */
1264
+ showTodosHotkey() {
1265
+ const _todos = this.todoPanel.getTodos();
1266
+ if (_todos.length === 0) {
1267
+ console.log(chalk.dim('\nšŸ“‹ No todos found'));
1268
+ }
1269
+ else {
1270
+ const todoDisplay = this.todoPanel.render();
1271
+ console.log(`\n${todoDisplay}`);
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Show hotkey help
1276
+ */
1277
+ showHotkeyHelp() {
1278
+ const helpText = `
1279
+ ${chalk.bold.cyan('šŸ”„ Interactive Mode Hotkeys')}
1280
+
1281
+ Mode Control:
1282
+ ${chalk.yellow('Ctrl+P')} Toggle plan mode on/off
1283
+ ${chalk.yellow('Ctrl+S')} Show current mode status
1284
+
1285
+ Todo Management:
1286
+ ${chalk.yellow('Ctrl+T')} Show current todo list and progress
1287
+
1288
+ General:
1289
+ ${chalk.yellow('Ctrl+H')} Show this help
1290
+ ${chalk.yellow('Tab')} Insert image from clipboard
1291
+ ${chalk.yellow('Ctrl+C')} Cancel input / Exit
1292
+
1293
+ Input Controls:
1294
+ ${chalk.yellow('Enter')} Send message
1295
+ ${chalk.yellow('Shift+Enter')} New line (multiline input)
1296
+
1297
+ Chat Commands:
1298
+ ${chalk.yellow('/help')} Show chat commands
1299
+ ${chalk.yellow('/todos')} Show detailed todo information
1300
+ ${chalk.yellow('/stats')} Show agent statistics
1301
+ ${chalk.yellow('/exit')} Exit the session
1302
+ `;
1303
+ console.log(helpText);
1304
+ }
1305
+ /**
1306
+ * Show current mode status
1307
+ */
1308
+ showModeStatus() {
1309
+ const _todos = this.todoPanel.getTodos();
1310
+ const progressInfo = this.todoPanel.getProgressInfo();
1311
+ let statusText = chalk.bold.blueBright('\nšŸ“Š Current Status\n');
1312
+ // Mode information
1313
+ statusText += `Mode: ${this.planMode ?
1314
+ chalk.green('Plan Mode (structured todos)') :
1315
+ chalk.yellow('Direct Mode (immediate execution)')}\n`;
1316
+ // Todo information
1317
+ if (progressInfo && progressInfo.total > 0) {
1318
+ statusText += `Todos: ${progressInfo.completed}/${progressInfo.total} completed (${progressInfo.percentage}%)\n`;
1319
+ if (progressInfo.currentTodo) {
1320
+ statusText += `Current: ${chalk.cyan(progressInfo.currentTodo.content)}\n`;
1321
+ }
1322
+ }
1323
+ else {
1324
+ statusText += 'Todos: None active\n';
1325
+ }
1326
+ // Agent status
1327
+ const agentStats = this.agent.getStats();
1328
+ if (agentStats) {
1329
+ statusText += `Session: ${agentStats.toolCalls} tool calls, ${agentStats.llmCalls} LLM calls\n`;
1330
+ }
1331
+ console.log(statusText);
1332
+ }
1333
+ /**
1334
+ * Format plan for display with proper text wrapping
1335
+ */
1336
+ formatPlanForDisplay(todos, qualityScore) {
1337
+ const maxWidth = Math.min(process.stdout.columns - 10, 100); // Leave some margin
1338
+ let formatted = `šŸ“‹ **Plan Created** (${todos.length} steps):\n\n`;
1339
+ todos.forEach((todo, index) => {
1340
+ const stepNumber = `${index + 1}. `;
1341
+ const content = todo.content;
1342
+ // Wrap long lines properly
1343
+ const wrappedContent = this.wrapText(content, maxWidth - stepNumber.length);
1344
+ const lines = wrappedContent.split('\n');
1345
+ // First line with step number
1346
+ formatted += stepNumber + lines[0] + '\n';
1347
+ // Subsequent lines indented to align with content
1348
+ for (let i = 1; i < lines.length; i++) {
1349
+ formatted += ' '.repeat(stepNumber.length) + lines[i] + '\n';
1350
+ }
1351
+ // Add spacing between steps
1352
+ if (index < todos.length - 1) {
1353
+ formatted += '\n';
1354
+ }
1355
+ });
1356
+ formatted += `\n\nQuality Score: ${qualityScore}/100`;
1357
+ return formatted;
1358
+ }
1359
+ /**
1360
+ * Wrap text to specified width
1361
+ */
1362
+ wrapText(text, maxWidth) {
1363
+ if (text.length <= maxWidth) {
1364
+ return text;
1365
+ }
1366
+ const words = text.split(' ');
1367
+ const lines = [];
1368
+ let currentLine = '';
1369
+ for (const word of words) {
1370
+ // If adding this word would exceed the width
1371
+ if (currentLine.length + word.length + 1 > maxWidth) {
1372
+ if (currentLine) {
1373
+ lines.push(currentLine.trim());
1374
+ currentLine = word;
1375
+ }
1376
+ else {
1377
+ // Single word is longer than maxWidth, force break
1378
+ lines.push(word);
1379
+ }
1380
+ }
1381
+ else {
1382
+ if (currentLine) {
1383
+ currentLine += ' ' + word;
1384
+ }
1385
+ else {
1386
+ currentLine = word;
1387
+ }
1388
+ }
1389
+ }
1390
+ if (currentLine) {
1391
+ lines.push(currentLine.trim());
1392
+ }
1393
+ return lines.join('\n');
1394
+ }
614
1395
  /**
615
1396
  * Cleanup resources
616
1397
  */
@@ -619,6 +1400,9 @@ export class CodeMieTerminalUI {
619
1400
  this.currentSpinner.stop();
620
1401
  this.currentSpinner = undefined;
621
1402
  }
1403
+ // Clean up todo tracking
1404
+ this.progressTracker.stop();
1405
+ TodoStateManager.removeEventCallback(this.handleTodoUpdate.bind(this));
622
1406
  }
623
1407
  }
624
1408
  //# sourceMappingURL=ui.js.map