@pheem49/mint 1.5.5 → 1.6.1

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 (222) hide show
  1. package/.codex +0 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/.github/workflows/ci.yml +45 -0
  4. package/.github/workflows/release.yml +79 -0
  5. package/Cargo.lock +5792 -0
  6. package/Cargo.toml +32 -0
  7. package/README.md +387 -353
  8. package/assets/icon.png +0 -0
  9. package/bin/mint +0 -0
  10. package/crates/mint-cli/Cargo.toml +23 -0
  11. package/crates/mint-cli/src/agent.rs +851 -0
  12. package/crates/mint-cli/src/gmail.rs +216 -0
  13. package/crates/mint-cli/src/image.rs +142 -0
  14. package/crates/mint-cli/src/main.rs +2837 -0
  15. package/crates/mint-cli/src/mcp.rs +63 -0
  16. package/crates/mint-cli/src/onboard.rs +1149 -0
  17. package/crates/mint-cli/src/setup.rs +390 -0
  18. package/crates/mint-cli/src/skills.rs +8 -0
  19. package/crates/mint-cli/src/updater.rs +279 -0
  20. package/crates/mint-core/Cargo.toml +22 -0
  21. package/crates/mint-core/src/agent_loop.rs +94 -0
  22. package/crates/mint-core/src/api_server.rs +991 -0
  23. package/crates/mint-core/src/channels.rs +248 -0
  24. package/crates/mint-core/src/chat.rs +895 -0
  25. package/crates/mint-core/src/code_tools.rs +729 -0
  26. package/crates/mint-core/src/config.rs +368 -0
  27. package/crates/mint-core/src/files.rs +159 -0
  28. package/crates/mint-core/src/knowledge.rs +541 -0
  29. package/crates/mint-core/src/lib.rs +84 -0
  30. package/crates/mint-core/src/mcp.rs +273 -0
  31. package/crates/mint-core/src/memory.rs +673 -0
  32. package/crates/mint-core/src/orchestration.rs +2157 -0
  33. package/crates/mint-core/src/pictures.rs +314 -0
  34. package/crates/mint-core/src/plugins.rs +727 -0
  35. package/crates/mint-core/src/safety.rs +416 -0
  36. package/crates/mint-core/src/semantic.rs +254 -0
  37. package/crates/mint-core/src/shell.rs +317 -0
  38. package/crates/mint-core/src/skills.rs +71 -0
  39. package/crates/mint-core/src/symbols.rs +157 -0
  40. package/crates/mint-core/src/tasks.rs +308 -0
  41. package/crates/mint-core/src/tts.rs +92 -0
  42. package/crates/mint-core/src/weather.rs +93 -0
  43. package/crates/mint-core/src/web_search.rs +200 -0
  44. package/crates/mint-core/src/workflows.rs +81 -0
  45. package/crates/mint-core/tests/mcp_stdio.rs +45 -0
  46. package/crates/mint-core/tests/memory_persistence.rs +172 -0
  47. package/crates/mint-core/tests/pictures_storage.rs +14 -0
  48. package/crates/mint-core/tests/task_lifecycle.rs +87 -0
  49. package/package.json +35 -99
  50. package/src/bin/index.js +16 -0
  51. package/src/renderer/index-web.html +17 -0
  52. package/src/renderer/index.html +17 -0
  53. package/src/renderer/public/Live2DCubismCore.js +9 -0
  54. package/src/renderer/public/assets/icon.png +0 -0
  55. package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
  56. package/src/renderer/src/App.tsx +33 -0
  57. package/src/renderer/src/calculator.ts +47 -0
  58. package/src/renderer/src/components/ChatPanel.tsx +1598 -0
  59. package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
  60. package/src/renderer/src/components/Live2DStage.tsx +374 -0
  61. package/src/renderer/src/components/MintDashboard.tsx +950 -0
  62. package/src/renderer/src/components/ModelPanel.tsx +154 -0
  63. package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
  64. package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
  65. package/src/renderer/src/components/ScreenPicker.tsx +579 -0
  66. package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
  67. package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
  68. package/src/renderer/src/components/WidgetWindow.tsx +36 -0
  69. package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
  70. package/src/{UI → renderer/src/css}/settings.css +69 -16
  71. package/src/renderer/src/css/spotlight.css +113 -0
  72. package/src/renderer/src/css/styles.css +3722 -0
  73. package/src/renderer/src/css/widget.css +185 -0
  74. package/src/renderer/src/env.d.ts +116 -0
  75. package/src/renderer/src/index.css +379 -0
  76. package/src/renderer/src/main.tsx +13 -0
  77. package/src/renderer/src/tauri.ts +996 -0
  78. package/src/renderer/src-web/App.tsx +25 -0
  79. package/src/renderer/src-web/calculator.ts +47 -0
  80. package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
  81. package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
  82. package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
  83. package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
  84. package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
  85. package/src/renderer/src-web/css/settings.css +1100 -0
  86. package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
  87. package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
  88. package/src/{UI → renderer/src-web/css}/widget.css +2 -2
  89. package/src/renderer/src-web/env.d.ts +107 -0
  90. package/src/renderer/src-web/index.css +379 -0
  91. package/src/renderer/src-web/main.tsx +13 -0
  92. package/src/renderer/src-web/tauri.ts +983 -0
  93. package/tsconfig.json +30 -0
  94. package/vite.config.ts +33 -0
  95. package/vite.config.web.ts +51 -0
  96. package/GUIDE_TH.md +0 -125
  97. package/assets/Agent_Mint.png +0 -0
  98. package/assets/CLI_Screen.png +0 -0
  99. package/assets/Settings.png +0 -0
  100. package/benchmark_ai.js +0 -71
  101. package/install.ps1 +0 -64
  102. package/install.sh +0 -54
  103. package/main.js +0 -139
  104. package/mint-cli-logic.js +0 -3
  105. package/mint-cli.js +0 -410
  106. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
  107. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
  108. package/preload-picker.js +0 -11
  109. package/preload-settings.js +0 -11
  110. package/preload.js +0 -41
  111. package/scripts/install_linux_desktop_entry.js +0 -48
  112. package/src/AI_Brain/Gemini_API.js +0 -813
  113. package/src/AI_Brain/agent_orchestrator.js +0 -73
  114. package/src/AI_Brain/autonomous_brain.js +0 -179
  115. package/src/AI_Brain/behavior_memory.js +0 -135
  116. package/src/AI_Brain/headless_agent.js +0 -143
  117. package/src/AI_Brain/knowledge_base.js +0 -349
  118. package/src/AI_Brain/memory_store.js +0 -662
  119. package/src/AI_Brain/proactive_engine.js +0 -172
  120. package/src/AI_Brain/provider_adapter.js +0 -365
  121. package/src/Automation_Layer/browser_automation.js +0 -149
  122. package/src/Automation_Layer/file_operations.js +0 -286
  123. package/src/Automation_Layer/open_app.js +0 -85
  124. package/src/Automation_Layer/open_website.js +0 -38
  125. package/src/CLI/approval_handler.js +0 -47
  126. package/src/CLI/chat_router.js +0 -247
  127. package/src/CLI/chat_ui.js +0 -1159
  128. package/src/CLI/cli_colors.js +0 -115
  129. package/src/CLI/cli_formatters.js +0 -94
  130. package/src/CLI/code_agent.js +0 -1667
  131. package/src/CLI/code_session_memory.js +0 -62
  132. package/src/CLI/gmail_auth.js +0 -210
  133. package/src/CLI/image_input.js +0 -90
  134. package/src/CLI/intent_detectors.js +0 -181
  135. package/src/CLI/interactive_chat.js +0 -658
  136. package/src/CLI/list_features.js +0 -64
  137. package/src/CLI/onboarding.js +0 -416
  138. package/src/CLI/repo_summarizer.js +0 -282
  139. package/src/CLI/semantic_code_search.js +0 -312
  140. package/src/CLI/skill_manager.js +0 -41
  141. package/src/CLI/slash_command_handler.js +0 -418
  142. package/src/CLI/symbol_indexer.js +0 -231
  143. package/src/CLI/updater.js +0 -230
  144. package/src/CLI/workspace_manager.js +0 -90
  145. package/src/Channels/brave_search_bridge.js +0 -35
  146. package/src/Channels/discord_bridge.js +0 -66
  147. package/src/Channels/google_search_bridge.js +0 -38
  148. package/src/Channels/line_bridge.js +0 -60
  149. package/src/Channels/slack_bridge.js +0 -48
  150. package/src/Channels/telegram_bridge.js +0 -41
  151. package/src/Channels/whatsapp_bridge.js +0 -57
  152. package/src/Command_Parser/parser.js +0 -45
  153. package/src/Plugins/dev_tools.js +0 -41
  154. package/src/Plugins/discord.js +0 -20
  155. package/src/Plugins/docker.js +0 -47
  156. package/src/Plugins/gmail.js +0 -251
  157. package/src/Plugins/google_calendar.js +0 -252
  158. package/src/Plugins/mcp_manager.js +0 -95
  159. package/src/Plugins/notion.js +0 -256
  160. package/src/Plugins/obsidian.js +0 -54
  161. package/src/Plugins/plugin_manager.js +0 -81
  162. package/src/Plugins/spotify.js +0 -173
  163. package/src/Plugins/system_metrics.js +0 -31
  164. package/src/Plugins/system_monitor.js +0 -72
  165. package/src/System/action_executor.js +0 -178
  166. package/src/System/bridge_manager.js +0 -76
  167. package/src/System/chat_history_manager.js +0 -83
  168. package/src/System/config_manager.js +0 -194
  169. package/src/System/custom_workflows.js +0 -163
  170. package/src/System/daemon_manager.js +0 -67
  171. package/src/System/google_tts_urls.js +0 -51
  172. package/src/System/granular_automation.js +0 -157
  173. package/src/System/ipc_handlers.js +0 -332
  174. package/src/System/notifications.js +0 -23
  175. package/src/System/optional_require.js +0 -23
  176. package/src/System/picture_store.js +0 -109
  177. package/src/System/proactive_loop.js +0 -153
  178. package/src/System/safety_manager.js +0 -273
  179. package/src/System/sandbox_runner.js +0 -182
  180. package/src/System/screen_capture.js +0 -175
  181. package/src/System/smart_context.js +0 -227
  182. package/src/System/system_automation.js +0 -162
  183. package/src/System/system_events.js +0 -79
  184. package/src/System/system_info.js +0 -125
  185. package/src/System/task_manager.js +0 -222
  186. package/src/System/tool_registry.js +0 -293
  187. package/src/System/window_manager.js +0 -220
  188. package/src/UI/floating.css +0 -80
  189. package/src/UI/floating.html +0 -17
  190. package/src/UI/floating.js +0 -67
  191. package/src/UI/live2d_manager.js +0 -600
  192. package/src/UI/preload-floating.js +0 -7
  193. package/src/UI/preload-spotlight.js +0 -11
  194. package/src/UI/preload-widget.js +0 -5
  195. package/src/UI/proactive-glow.html +0 -42
  196. package/src/UI/renderer.js +0 -2127
  197. package/src/UI/screenPicker.html +0 -214
  198. package/src/UI/screenPicker.js +0 -262
  199. package/src/UI/settings.html +0 -577
  200. package/src/UI/settings.js +0 -770
  201. package/src/UI/spotlight.html +0 -23
  202. package/src/UI/spotlight.js +0 -185
  203. package/src/UI/widget.html +0 -29
  204. package/src/UI/widget.js +0 -10
  205. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  206. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
  207. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
  208. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
  209. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
  210. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
  211. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
  212. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
  213. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
  214. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
  215. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
  216. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
  217. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
  218. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
  219. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
  220. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
  221. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
  222. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
@@ -1,1159 +0,0 @@
1
- /**
2
- * Mint CLI - Ink-based UI (ESM-compatible Version)
3
- * A modern, React-based terminal UI for a better chat experience.
4
- * Uses dynamic imports to handle ESM dependencies (Ink).
5
- */
6
- const React = require('react');
7
- const path = require('path');
8
- const { readConfig } = require('../System/config_manager');
9
-
10
- // Helper to make element creation less verbose
11
- const h = React.createElement;
12
-
13
- const SLASH_COMMANDS = [
14
- { cmd: '/help', desc: 'Show available commands' },
15
- { cmd: '/image', desc: 'Attach an image from a file path' },
16
- { cmd: '/paste', desc: 'Attach an image from the clipboard' },
17
- { cmd: '/fast', desc: 'Toggle fast mode (hide thinking)' },
18
- { cmd: '/learn', desc: 'Remember a markdown skill file' },
19
- { cmd: '/code', desc: 'Force workspace Code Mode' },
20
- { cmd: '/cd', desc: 'Change current working directory' },
21
- { cmd: '/models', desc: 'List or switch Gemini models' },
22
- { cmd: '/memory', desc: 'List, search, clear, or export long-term memory' },
23
- { cmd: '/memory skills', desc: 'Show learned skill files' },
24
- { cmd: '/config', desc: 'Show current configuration' },
25
- { cmd: '/copy', desc: 'Copy last response to clipboard' },
26
- { cmd: '/clear', desc: 'Clear conversation history' },
27
- { cmd: '/reset', desc: 'Reset conversation history' },
28
- { cmd: '/agent', desc: 'Switch AI agents (e.g. /agent code)' },
29
- { cmd: '/workspace', desc: 'Manage registered workspaces' },
30
- { cmd: '/stats', desc: 'Show system statistics' },
31
- { cmd: '/review', desc: 'Request second-pass review' },
32
- { cmd: '/exit', desc: 'Exit Mint' }
33
- ];
34
-
35
- const MAX_BLANK_LINES = 1;
36
-
37
- function compactPathLabel(value) {
38
- const text = String(value || '').trim();
39
- if (!text) return '';
40
- return path.basename(text) || text;
41
- }
42
-
43
- function formatActivityStep(info = {}) {
44
- if (!info || typeof info !== 'object') return null;
45
-
46
- const { action, phase, target, message } = info;
47
- const rawText = String(target || message || '').trim();
48
- const kind = action || phase || 'activity';
49
- if (!rawText) return null;
50
-
51
- switch (kind) {
52
- case 'list_files':
53
- return { title: 'Explored', detail: `List ${rawText}` };
54
- case 'find_path':
55
- return { title: 'Explored', detail: `Find ${rawText}` };
56
- case 'read_file':
57
- return { title: 'Explored', detail: `Read ${compactPathLabel(rawText)}` };
58
- case 'search_code':
59
- return { title: 'Explored', detail: `Search ${rawText}` };
60
- case 'web_search':
61
- return { title: 'Searched', detail: rawText };
62
- case 'warn':
63
- return { title: '⚠ Notice', detail: rawText };
64
- case 'run_shell':
65
- return { title: 'Ran', detail: rawText };
66
- case 'plan':
67
- return { title: 'Plan', detail: rawText };
68
- case 'apply_patch':
69
- case 'write_file':
70
- return { title: 'Edited', detail: rawText };
71
- case 'evaluator':
72
- return { title: 'Checked', detail: rawText };
73
- case 'reviewer_start':
74
- return { title: 'Reviewing', detail: rawText };
75
- case 'ask_user':
76
- return { title: 'Ask User', detail: rawText };
77
- default:
78
- return { title: kind, detail: rawText };
79
- }
80
- }
81
-
82
- function stripInlineMarkdown(value) {
83
- return String(value || '')
84
- .replace(/\*\*([^*]+)\*\*/g, '$1')
85
- .replace(/\*([^*\n]+)\*/g, '$1')
86
- .replace(/__([^_]+)__/g, '$1')
87
- .replace(/`([^`\n]+)`/g, '$1');
88
- }
89
-
90
- function cleanDisplayText(text, role = 'assistant') {
91
- const raw = String(text || '').replace(/\r\n/g, '\n').trim();
92
- if (!raw) return '';
93
-
94
- const shouldPolishMarkdown = role === 'assistant' || role === 'system';
95
- const lines = raw.split('\n');
96
- const cleaned = [];
97
- let inCodeBlock = false;
98
- let blankCount = 0;
99
-
100
- for (const sourceLine of lines) {
101
- let line = sourceLine.replace(/\s+$/g, '');
102
- const fence = line.match(/^\s*```(.*)$/);
103
-
104
- if (fence) {
105
- inCodeBlock = !inCodeBlock;
106
- const label = fence[1] ? `code: ${fence[1].trim()}` : 'code';
107
- line = inCodeBlock ? label : '';
108
- } else if (inCodeBlock) {
109
- line = line ? ` ${line}` : '';
110
- } else if (shouldPolishMarkdown) {
111
- const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
112
- const bullet = line.match(/^(\s*)[-*]\s+(.+)$/);
113
- const numbered = line.match(/^(\s*)\d+[.)]\s+(.+)$/);
114
-
115
- if (heading) {
116
- if (cleaned.length > 0 && cleaned[cleaned.length - 1] !== '') cleaned.push('');
117
- line = stripInlineMarkdown(heading[1]).trim();
118
- } else if (bullet) {
119
- line = `${bullet[1]}• ${stripInlineMarkdown(bullet[2]).trim()}`;
120
- } else if (numbered) {
121
- line = `${numbered[1]}${stripInlineMarkdown(line).trim()}`;
122
- } else {
123
- line = stripInlineMarkdown(line);
124
- }
125
- }
126
-
127
- if (!line.trim()) {
128
- blankCount++;
129
- if (blankCount <= MAX_BLANK_LINES && cleaned.length > 0) cleaned.push('');
130
- continue;
131
- }
132
-
133
- blankCount = 0;
134
- cleaned.push(line);
135
- }
136
-
137
- while (cleaned[0] === '') cleaned.shift();
138
- while (cleaned[cleaned.length - 1] === '') cleaned.pop();
139
- return cleaned.join('\n');
140
- }
141
-
142
- function formatDuration(totalSeconds) {
143
- const seconds = Math.max(0, Math.floor(Number(totalSeconds) || 0));
144
- const minutes = Math.floor(seconds / 60);
145
- const remainingSeconds = seconds % 60;
146
-
147
- if (minutes <= 0) return `${remainingSeconds}s`;
148
- return `${minutes}m ${remainingSeconds}s`;
149
- }
150
-
151
- function splitDiffStatSegments(value) {
152
- const text = String(value || '');
153
- const match = text.match(/\(\+(\d+)\s+-(\d+)\)/);
154
- if (!match) return [{ text, color: 'cyanBright' }];
155
-
156
- return [
157
- { text: text.slice(0, match.index), color: 'cyanBright' },
158
- { text: '(', color: 'gray' },
159
- { text: `+${match[1]}`, color: 'greenBright' },
160
- { text: ' ', color: 'gray' },
161
- { text: `-${match[2]}`, color: 'redBright' },
162
- { text: ')', color: 'gray' },
163
- { text: text.slice(match.index + match[0].length), color: 'cyanBright' }
164
- ].filter(part => part.text);
165
- }
166
-
167
- const APPROVAL_CHOICES = ['approve', 'approve_session', 'deny'];
168
- const SUGGESTION_WINDOW_SIZE = 5;
169
-
170
- function getNextApprovalChoice(current, direction = 1) {
171
- const choices = APPROVAL_CHOICES;
172
- const index = choices.indexOf(current);
173
- const start = index === -1 ? 0 : index;
174
- return choices[(start + direction + choices.length) % choices.length];
175
- }
176
-
177
- function getVisibleSuggestions(suggestions, selectedIndex, limit = SUGGESTION_WINDOW_SIZE) {
178
- const items = Array.isArray(suggestions) ? suggestions : [];
179
- const safeLimit = Math.max(1, Number(limit) || SUGGESTION_WINDOW_SIZE);
180
- const safeSelected = Math.min(Math.max(0, Number(selectedIndex) || 0), Math.max(0, items.length - 1));
181
- const start = Math.min(
182
- Math.max(0, safeSelected - safeLimit + 1),
183
- Math.max(0, items.length - safeLimit)
184
- );
185
- const visible = items.slice(start, start + safeLimit);
186
-
187
- return {
188
- start,
189
- visible,
190
- current: items.length > 0 ? safeSelected + 1 : 0,
191
- total: items.length
192
- };
193
- }
194
-
195
- function parseUnifiedDiffPreview(preview) {
196
- const lines = String(preview || '').replace(/\r\n/g, '\n').split('\n');
197
- const files = [];
198
- let current = null;
199
-
200
- for (const line of lines) {
201
- if (line.startsWith('--- a/')) {
202
- current = {
203
- path: line.slice('--- a/'.length),
204
- additions: 0,
205
- deletions: 0,
206
- lines: []
207
- };
208
- files.push(current);
209
- continue;
210
- }
211
-
212
- if (!current) continue;
213
- if (line.startsWith('+++ b/')) {
214
- current.path = line.slice('+++ b/'.length) || current.path;
215
- continue;
216
- }
217
-
218
- if (line.startsWith('@@')) {
219
- current.lines.push({ type: 'hunk', text: line });
220
- continue;
221
- }
222
-
223
- if (line.startsWith('+')) {
224
- current.additions += 1;
225
- current.lines.push({ type: 'add', text: line });
226
- continue;
227
- }
228
-
229
- if (line.startsWith('-')) {
230
- current.deletions += 1;
231
- current.lines.push({ type: 'delete', text: line });
232
- continue;
233
- }
234
-
235
- current.lines.push({ type: 'context', text: line });
236
- }
237
-
238
- return files.filter(file => file.lines.length > 0 || file.additions > 0 || file.deletions > 0);
239
- }
240
-
241
- function isUnifiedDiffPreview(preview) {
242
- return parseUnifiedDiffPreview(preview).length > 0;
243
- }
244
-
245
- function getDiffLineStyle(line = {}) {
246
- if (line.type === 'add') return { color: 'greenBright' };
247
- if (line.type === 'delete') return { color: 'redBright' };
248
- if (line.type === 'hunk') return { color: 'cyanBright' };
249
- return { color: 'gray', dimColor: true };
250
- }
251
-
252
- function shouldAppendMessage(role, text) {
253
- if (role === 'assistant' || role === 'system') {
254
- return String(text || '').trim().length > 0;
255
- }
256
- return true;
257
- }
258
-
259
- function appendInlineImageToken(value, imageIndex) {
260
- const token = `[Image #${imageIndex}]`;
261
- const text = String(value || '').replace(/\s*[\r\n]+\s*/g, ' ').trimEnd();
262
- return text ? `${text} ${token}` : token;
263
- }
264
-
265
- function removeImageToken(value, imageIndex) {
266
- const tokenPattern = new RegExp(`\\s*\\[Image #${imageIndex}\\]`, 'g');
267
- return String(value || '').replace(tokenPattern, '').replace(/\s{2,}/g, ' ').trim();
268
- }
269
-
270
- function removeAllImageTokens(value) {
271
- return String(value || '').replace(/\s*\[Image #\d+\]/g, '').replace(/\s{2,}/g, ' ').trim();
272
- }
273
-
274
- /**
275
- * We wrap everything in an async function to load ESM modules
276
- */
277
- async function createChatUI(options) {
278
- // Dynamic imports for ESM modules
279
- const { render, Box, Text, useInput, useApp, Static } = await import('ink');
280
- const TextInput = (await import('ink-text-input')).default;
281
- const { useState, useImperativeHandle, forwardRef, createRef, useEffect, useMemo } = React;
282
-
283
- const App = forwardRef(({ onSubmit, onExit, onCancel, onPasteImage, initialHistory = [] }, ref) => {
284
- const config = readConfig();
285
- const { exit } = useApp();
286
- const [input, setInput] = useState('');
287
- const [history, setHistory] = useState(initialHistory);
288
- const [liveAssistant, setLiveAssistant] = useState(null);
289
- const [thinking, setThinking] = useState(false);
290
- const [workingSeconds, setWorkingSeconds] = useState(0);
291
- const [fastMode, setFastMode] = useState(false);
292
- const [mode, setMode] = useState('Agent');
293
- const [model, setModel] = useState('');
294
- const [workspace, setWorkspace] = useState(process.cwd());
295
- const [pendingImages, setPendingImages] = useState([]);
296
- const [pendingPaste, setPendingPaste] = useState(null);
297
- const [pendingPastePrefix, setPendingPastePrefix] = useState('');
298
- const [pendingApproval, setPendingApproval] = useState(null);
299
- const [approvalChoice, setApprovalChoice] = useState('approve');
300
- const [approvalSessionAutoApprove, setApprovalSessionAutoApprove] = useState(false);
301
- const [inputResetKey, setInputResetKey] = useState(0);
302
-
303
- // Suggestions State
304
- const [selectedIndex, setSelectedIndex] = useState(0);
305
- const inputRef = React.useRef(input);
306
- const pendingImagesRef = React.useRef(pendingImages);
307
- const pendingPasteRef = React.useRef(pendingPaste);
308
- const pendingPastePrefixRef = React.useRef(pendingPastePrefix);
309
- const liveAssistantRef = React.useRef(liveAssistant);
310
- const thinkingStartedAtRef = React.useRef(null);
311
- const fastModeRef = React.useRef(fastMode);
312
- const suppressPasteCharRef = React.useRef(false);
313
- const suppressPasteBurstRef = React.useRef(false);
314
- const selectedIndexRef = React.useRef(selectedIndex);
315
- const pendingApprovalRef = React.useRef(null);
316
- const approvalChoiceRef = React.useRef('approve');
317
- const approvalSessionAutoApproveRef = React.useRef(false);
318
-
319
- const removePasteArtifact = (value) => {
320
- const text = String(value || '');
321
- return text.replace(/[vV]$/, '');
322
- };
323
-
324
- const normalizeInputText = (value) => {
325
- return String(value || '').replace(/\s*[\r\n]+\s*/g, ' ');
326
- };
327
-
328
- const shouldStoreAsPastedContent = (value) => {
329
- const text = String(value || '');
330
- return text.length > 500 || /[\r\n]/.test(text);
331
- };
332
-
333
- const resetInputCursorToEnd = () => {
334
- setInputResetKey(key => key + 1);
335
- };
336
-
337
- useEffect(() => {
338
- inputRef.current = input;
339
- }, [input]);
340
-
341
- useEffect(() => {
342
- pendingImagesRef.current = pendingImages;
343
- }, [pendingImages]);
344
-
345
- useEffect(() => {
346
- pendingPasteRef.current = pendingPaste;
347
- }, [pendingPaste]);
348
-
349
- useEffect(() => {
350
- pendingPastePrefixRef.current = pendingPastePrefix;
351
- }, [pendingPastePrefix]);
352
-
353
- useEffect(() => {
354
- liveAssistantRef.current = liveAssistant;
355
- }, [liveAssistant]);
356
-
357
- useEffect(() => {
358
- if (!thinking) return undefined;
359
-
360
- const timer = setInterval(() => {
361
- if (!thinkingStartedAtRef.current) return;
362
- setWorkingSeconds(Math.floor((Date.now() - thinkingStartedAtRef.current) / 1000));
363
- }, 1000);
364
-
365
- return () => clearInterval(timer);
366
- }, [thinking]);
367
-
368
- useEffect(() => {
369
- fastModeRef.current = fastMode;
370
- }, [fastMode]);
371
-
372
- useEffect(() => {
373
- selectedIndexRef.current = selectedIndex;
374
- }, [selectedIndex]);
375
-
376
- useEffect(() => {
377
- pendingApprovalRef.current = pendingApproval;
378
- if (pendingApproval) {
379
- approvalChoiceRef.current = 'approve';
380
- setApprovalChoice('approve');
381
- }
382
- }, [pendingApproval]);
383
-
384
- useEffect(() => {
385
- approvalChoiceRef.current = approvalChoice;
386
- }, [approvalChoice]);
387
-
388
- useEffect(() => {
389
- approvalSessionAutoApproveRef.current = approvalSessionAutoApprove;
390
- }, [approvalSessionAutoApprove]);
391
-
392
- const showSuggestions = input.startsWith('/') && !input.includes(' ');
393
- const suggestions = useMemo(() => {
394
- if (!showSuggestions) return [];
395
- const query = input.toLowerCase();
396
- return SLASH_COMMANDS.filter(s => s.cmd.startsWith(query));
397
- }, [input, showSuggestions]);
398
- const visibleSuggestions = useMemo(
399
- () => getVisibleSuggestions(suggestions, selectedIndex),
400
- [suggestions, selectedIndex]
401
- );
402
-
403
- // Reset index when suggestions change
404
- useEffect(() => {
405
- setSelectedIndex(0);
406
- }, [suggestions.length]);
407
-
408
- const lastSystemMessage = React.useRef('');
409
-
410
- // Export methods to the outside world via ref
411
- useImperativeHandle(ref, () => ({
412
- appendMessage: (role, text, metadata = {}) => {
413
- if (!shouldAppendMessage(role, text)) return;
414
- setHistory(prev => [...prev, { role, text, time: new Date(), ...metadata }]);
415
- if (metadata.providerInfo) {
416
- const { provider, model } = metadata.providerInfo;
417
- setModel(model ? `${provider} • ${model}` : provider);
418
- }
419
- },
420
- beginAssistantStream: (metadata = {}) => {
421
- const msg = { role: 'assistant', text: '', time: new Date(), ...metadata };
422
- liveAssistantRef.current = msg;
423
- setLiveAssistant(msg);
424
- if (metadata.providerInfo) {
425
- const { provider, model } = metadata.providerInfo;
426
- setModel(model ? `${provider} • ${model}` : provider);
427
- }
428
- },
429
- appendAssistantStreamChunk: (chunk) => {
430
- if (!String(chunk || '').trim()) return;
431
- const current = liveAssistantRef.current || { role: 'assistant', text: '', time: new Date() };
432
- const next = { ...current, text: `${current.text || ''}${chunk}` };
433
- liveAssistantRef.current = next;
434
- setLiveAssistant(next);
435
- },
436
- finalizeAssistantStream: () => {
437
- const current = liveAssistantRef.current;
438
- liveAssistantRef.current = null;
439
- setLiveAssistant(null);
440
- if (current && String(current.text || '').trim()) {
441
- setHistory(prev => [...prev, current]);
442
- }
443
- },
444
- setThinking: (val, seconds = 0) => {
445
- if (val) {
446
- const elapsed = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
447
- if (!thinkingStartedAtRef.current) {
448
- thinkingStartedAtRef.current = Date.now() - (elapsed * 1000);
449
- }
450
- setWorkingSeconds(Math.floor((Date.now() - thinkingStartedAtRef.current) / 1000));
451
- setThinking(true);
452
- return;
453
- }
454
-
455
- thinkingStartedAtRef.current = null;
456
- setWorkingSeconds(0);
457
- setThinking(false);
458
- },
459
- setMode: (val) => setMode(val),
460
- setFastMode: (val) => {
461
- const next = Boolean(val);
462
- fastModeRef.current = next;
463
- setFastMode(next);
464
- return next;
465
- },
466
- toggleFastMode: () => {
467
- const next = !fastModeRef.current;
468
- fastModeRef.current = next;
469
- setFastMode(next);
470
- return next;
471
- },
472
- getFastMode: () => fastModeRef.current,
473
- setInputText: (val) => {
474
- const next = val || '';
475
- inputRef.current = next;
476
- setInput(next);
477
- resetInputCursorToEnd();
478
- },
479
- setPendingPasteText: (text) => {
480
- const normalized = normalizeInputText(text);
481
- setPendingPaste({ text: normalized, label: `[Pasted Content ${normalized.length} chars]` });
482
- setPendingPastePrefix('');
483
- setInput('');
484
- },
485
- updateStatusModel: (val) => setModel(val),
486
- updateWorkspace: (val) => setWorkspace(val),
487
- attachImage: (image) => {
488
- setPendingImages(prev => {
489
- const imageIndex = prev.length + 1;
490
- setInput(current => {
491
- const next = appendInlineImageToken(current, imageIndex);
492
- inputRef.current = next;
493
- resetInputCursorToEnd();
494
- return next;
495
- });
496
- return [...prev, image];
497
- });
498
- },
499
- appendCodeStep: (info) => {
500
- if (fastModeRef.current) {
501
- return;
502
- }
503
-
504
- let text = '';
505
- let label = 'System';
506
- let labelColor = 'blueBright';
507
- let isThought = false;
508
-
509
- if (typeof info === 'string') {
510
- text = info;
511
- } else {
512
- const { action, phase, target, message, thought } = info;
513
- if (action === 'memory_context' && process.env.MINT_SHOW_MEMORY_TRACE !== '1') {
514
- return;
515
- }
516
- if (phase === 'tool_call') {
517
- return;
518
- }
519
- if (thought) {
520
- if (process.env.MINT_HIDE_AGENT_NOTES === '1') {
521
- return;
522
- }
523
- text = thought;
524
- label = 'Working';
525
- labelColor = 'gray';
526
- isThought = true;
527
- } else if (action === 'thinking' || phase === 'thinking') {
528
- return;
529
- } else {
530
- const activity = formatActivityStep(info);
531
- if (activity) {
532
- const fullText = `[${activity.title}] ${activity.detail}`;
533
- if (fullText === lastSystemMessage.current) return;
534
- lastSystemMessage.current = fullText;
535
-
536
- setHistory(prev => [...prev, {
537
- role: 'system',
538
- label: activity.title,
539
- labelColor: 'blueBright',
540
- text: activity.detail,
541
- isActivity: true,
542
- activityTitle: activity.title,
543
- activityDetail: activity.detail,
544
- time: new Date()
545
- }]);
546
- return;
547
- }
548
-
549
- label = action || phase || 'Action';
550
- text = target || message || '';
551
- if (!text) return;
552
-
553
- // Color coding for specific actions
554
- if (label.includes('search')) labelColor = 'yellowBright';
555
- else if (label.includes('file') || label.includes('path')) labelColor = 'cyanBright';
556
- else if (label.includes('write') || label.includes('edit') || label.includes('patch')) labelColor = 'greenBright';
557
- else if (label.includes('shell') || label.includes('run')) labelColor = 'magentaBright';
558
- }
559
- }
560
-
561
- const fullText = `[${label}] ${text}`;
562
- if (fullText === lastSystemMessage.current) return;
563
- lastSystemMessage.current = fullText;
564
-
565
- setHistory(prev => [...prev, {
566
- role: 'system',
567
- label,
568
- labelColor,
569
- text,
570
- isThought,
571
- time: new Date()
572
- }]);
573
- },
574
- requestApproval: (request = {}) => {
575
- if (approvalSessionAutoApproveRef.current) {
576
- return Promise.resolve(true);
577
- }
578
-
579
- return new Promise((resolve) => {
580
- const approval = {
581
- type: request.type || 'action',
582
- label: request.label || 'Requested action',
583
- preview: request.preview || '',
584
- summary: request.summary || '',
585
- openPath: request.openPath || '',
586
- warnings: Array.isArray(request.warnings) ? request.warnings.filter(Boolean) : [],
587
- resolve
588
- };
589
- pendingApprovalRef.current = approval;
590
- setPendingApproval(approval);
591
- });
592
- }
593
- }));
594
-
595
- // Handle exiting and keyboard navigation
596
- useInput((inputStr, key) => {
597
- const approval = pendingApprovalRef.current;
598
- if (approval) {
599
- const resolveApproval = (approved, approveForSession = false) => {
600
- if (approveForSession) {
601
- approvalSessionAutoApproveRef.current = true;
602
- setApprovalSessionAutoApprove(true);
603
- }
604
- pendingApprovalRef.current = null;
605
- setPendingApproval(null);
606
- setHistory(prev => {
607
- if (approved && isUnifiedDiffPreview(approval.preview)) {
608
- return [...prev, {
609
- role: 'system',
610
- label: 'Edited',
611
- labelColor: 'greenBright',
612
- preview: approval.preview,
613
- isDiffPreview: true,
614
- time: new Date()
615
- }];
616
- }
617
-
618
- return [...prev, {
619
- role: 'system',
620
- label: 'Approval',
621
- labelColor: approved ? 'greenBright' : 'redBright',
622
- text: `${approveForSession ? 'Approved this session' : (approved ? 'Approved' : 'Denied')}: ${approval.label}`,
623
- time: new Date()
624
- }];
625
- });
626
- approval.resolve(approved);
627
- };
628
-
629
- if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.tab) {
630
- const next = getNextApprovalChoice(
631
- approvalChoiceRef.current,
632
- (key.upArrow || key.leftArrow) ? -1 : 1
633
- );
634
- approvalChoiceRef.current = next;
635
- setApprovalChoice(next);
636
- return;
637
- }
638
-
639
- const answer = String(inputStr || '').toLowerCase();
640
- if (key.return) {
641
- resolveApproval(approvalChoiceRef.current !== 'deny', approvalChoiceRef.current === 'approve_session');
642
- return;
643
- }
644
- if (answer === 'y') {
645
- resolveApproval(true);
646
- return;
647
- }
648
- if (answer === 'a') {
649
- resolveApproval(true, true);
650
- return;
651
- }
652
- if (answer === 'n' || key.escape || (key.ctrl && inputStr === 'c')) {
653
- resolveApproval(false);
654
- return;
655
- }
656
- return;
657
- }
658
-
659
- if (key.escape && pendingImagesRef.current.length > 0) {
660
- setPendingImages([]);
661
- pendingImagesRef.current = [];
662
- setInput(current => {
663
- const next = removeAllImageTokens(current);
664
- inputRef.current = next;
665
- resetInputCursorToEnd();
666
- return next;
667
- });
668
- return;
669
- }
670
-
671
- if (key.escape && pendingPasteRef.current) {
672
- setPendingPaste(null);
673
- pendingPasteRef.current = null;
674
- setPendingPastePrefix('');
675
- pendingPastePrefixRef.current = '';
676
- suppressPasteBurstRef.current = false;
677
- inputRef.current = '';
678
- setInput('');
679
- resetInputCursorToEnd();
680
- return;
681
- }
682
-
683
- if (key.ctrl && key.backspace && pendingImagesRef.current.length > 0) {
684
- const imageIndex = pendingImagesRef.current.length;
685
- const nextImages = pendingImagesRef.current.slice(0, -1);
686
- pendingImagesRef.current = nextImages;
687
- setPendingImages(nextImages);
688
- setInput(current => {
689
- const next = removeImageToken(current, imageIndex);
690
- inputRef.current = next;
691
- resetInputCursorToEnd();
692
- return next;
693
- });
694
- return;
695
- }
696
-
697
- if (key.escape) {
698
- if (thinking && typeof onCancel === 'function') {
699
- onCancel();
700
- return;
701
- }
702
- exit();
703
- onExit();
704
- return;
705
- }
706
-
707
- if (key.ctrl && inputStr === 'c') {
708
- exit();
709
- onExit();
710
- return;
711
- }
712
-
713
- const currentInput = inputRef.current;
714
- if (key.ctrl && inputStr === 'v') {
715
- suppressPasteCharRef.current = true;
716
- const inputBeforePaste = currentInput;
717
- setInput(prev => removePasteArtifact(prev));
718
- if (typeof onPasteImage === 'function') {
719
- Promise.resolve(onPasteImage())
720
- .then((image) => {
721
- if (image) {
722
- setPendingImages(prev => {
723
- const imageIndex = prev.length + 1;
724
- setInput(current => {
725
- const cleaned = removePasteArtifact(current);
726
- const next = appendInlineImageToken(cleaned || inputBeforePaste, imageIndex);
727
- inputRef.current = next;
728
- resetInputCursorToEnd();
729
- return next;
730
- });
731
- return [...prev, image];
732
- });
733
- }
734
- })
735
- .catch((err) => {
736
- setHistory(prev => [...prev, {
737
- role: 'error',
738
- text: err && err.message ? err.message : String(err || 'Unknown error'),
739
- time: new Date()
740
- }]);
741
- })
742
- .finally(() => {
743
- setInput(prev => {
744
- if (prev === `${inputBeforePaste}v` || prev === `${inputBeforePaste}V`) {
745
- return inputBeforePaste;
746
- }
747
- return removePasteArtifact(prev);
748
- });
749
- });
750
- }
751
- return;
752
- }
753
-
754
- const currentShowSuggestions = currentInput.startsWith('/') && !currentInput.includes(' ');
755
-
756
- if (currentShowSuggestions) {
757
- const query = currentInput.toLowerCase();
758
- const currentSuggestions = SLASH_COMMANDS.filter(s => s.cmd.startsWith(query));
759
-
760
- if (currentSuggestions.length > 0) {
761
- if (key.upArrow) {
762
- setSelectedIndex(prev => (prev > 0 ? prev - 1 : currentSuggestions.length - 1));
763
- } else if (key.downArrow) {
764
- setSelectedIndex(prev => (prev < currentSuggestions.length - 1 ? prev + 1 : 0));
765
- } else if (key.tab || (key.return && currentInput.startsWith('/'))) {
766
- const picked = currentSuggestions[selectedIndexRef.current];
767
- if (picked) {
768
- setInput(picked.cmd + ' ');
769
- }
770
- }
771
- }
772
- }
773
- });
774
-
775
- const handleSubmit = (value) => {
776
- const text = normalizeInputText(value).trim();
777
- const images = pendingImagesRef.current;
778
- const imageLabels = images.map((_, index) => `[Image #${index + 1}]`).join(' ');
779
- const pasted = pendingPasteRef.current;
780
- const pastePrefix = normalizeInputText(pendingPastePrefixRef.current).trim();
781
- const submittedText = pasted
782
- ? [pastePrefix, pasted.text, text].filter(Boolean).join('\n\n')
783
- : images.length > 0
784
- ? (text || imageLabels)
785
- : text;
786
- if (!submittedText && images.length === 0) return;
787
-
788
- if (!pasted && images.length === 0 && showSuggestions && suggestions.length > 0) {
789
- const picked = suggestions[selectedIndex];
790
- if (picked && text !== picked.cmd) {
791
- setInput(picked.cmd + ' ');
792
- return;
793
- }
794
- }
795
-
796
- setInput('');
797
- setPendingImages([]);
798
- setPendingPaste(null);
799
- setPendingPastePrefix('');
800
- pendingImagesRef.current = [];
801
- pendingPasteRef.current = null;
802
- pendingPastePrefixRef.current = '';
803
- suppressPasteBurstRef.current = false;
804
- onSubmit(submittedText, { images, pasted });
805
- };
806
-
807
- const handleInputChange = (value) => {
808
- if (suppressPasteBurstRef.current && pendingPasteRef.current) {
809
- inputRef.current = '';
810
- setInput('');
811
- resetInputCursorToEnd();
812
- return;
813
- }
814
-
815
- if (shouldStoreAsPastedContent(value)) {
816
- const normalized = normalizeInputText(value);
817
- const previous = normalizeInputText(inputRef.current).trim();
818
- const pasted = { text: normalized, label: `[Pasted Content ${normalized.length} chars]` };
819
- pendingPasteRef.current = pasted;
820
- pendingPastePrefixRef.current = previous;
821
- suppressPasteBurstRef.current = true;
822
- setPendingPaste(pasted);
823
- setPendingPastePrefix(previous);
824
- inputRef.current = '';
825
- setInput('');
826
- resetInputCursorToEnd();
827
- return;
828
- }
829
-
830
- const normalizedValue = normalizeInputText(value);
831
- if (suppressPasteCharRef.current) {
832
- suppressPasteCharRef.current = false;
833
- const previous = inputRef.current;
834
- if (normalizedValue === `${previous}v` || normalizedValue === `${previous}V`) {
835
- setInput(previous);
836
- return;
837
- }
838
- if (normalizedValue.length > previous.length && /^[vV]$/.test(normalizedValue.slice(previous.length))) {
839
- setInput(previous);
840
- return;
841
- }
842
- }
843
- inputRef.current = normalizedValue;
844
- setInput(normalizedValue);
845
- };
846
-
847
- const renderActivityDetail = (value) => {
848
- const segments = splitDiffStatSegments(value);
849
- return segments.map((segment, segmentIndex) =>
850
- h(Text, {
851
- key: `activity-detail-${segmentIndex}`,
852
- color: segment.color,
853
- wrap: 'wrap'
854
- }, segment.text)
855
- );
856
- };
857
-
858
- const renderDiffLine = (line, index) => {
859
- const style = getDiffLineStyle(line);
860
- return h(Text, {
861
- key: `diff-line-${index}`,
862
- ...style
863
- }, line.text || ' ');
864
- };
865
-
866
- const renderDiffPreview = (preview) => {
867
- const files = parseUnifiedDiffPreview(preview);
868
- if (files.length === 0) return null;
869
-
870
- return h(Box, { flexDirection: 'column', marginTop: 1 },
871
- ...files.map((file, fileIndex) =>
872
- h(Box, { key: `approval-diff-${fileIndex}`, flexDirection: 'column', marginBottom: 1 },
873
- h(Box, null,
874
- h(Text, { color: 'gray' }, '• '),
875
- h(Text, { bold: true, color: 'white' }, `Edited ${file.path} `),
876
- h(Text, { color: 'gray' }, '('),
877
- h(Text, { color: 'greenBright' }, `+${file.additions}`),
878
- h(Text, { color: 'gray' }, ' '),
879
- h(Text, { color: 'redBright' }, `-${file.deletions}`),
880
- h(Text, { color: 'gray' }, ')')
881
- ),
882
- h(Box, { flexDirection: 'column', paddingLeft: 2 },
883
- ...file.lines.slice(0, 120).map(renderDiffLine),
884
- file.lines.length > 120 && h(Text, { color: 'gray', dimColor: true }, `... ${file.lines.length - 120} more diff lines`)
885
- )
886
- )
887
- )
888
- );
889
- };
890
-
891
- const renderApprovalPreview = (approval) => {
892
- const preview = approval && approval.preview ? approval.preview : '';
893
- if (approval && approval.type === 'plan') {
894
- return h(Box, { flexDirection: 'column', marginTop: 1 },
895
- h(Text, { color: 'gray' }, approval.summary || 'Mint prepared a plan for this task.'),
896
- approval.openPath && h(Text, { color: 'gray', dimColor: true }, `Details: ${approval.label}`)
897
- );
898
- }
899
-
900
- const diffPreview = renderDiffPreview(preview);
901
- if (!diffPreview) {
902
- return preview && preview !== approval.label
903
- ? h(Box, null, h(Text, { color: 'gray', dimColor: true }, preview))
904
- : null;
905
- }
906
-
907
- return diffPreview;
908
- };
909
-
910
- const renderMessage = (msg, index, keyPrefix = 'msg') => {
911
- if (msg.isThought) {
912
- return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'row', marginBottom: 0, paddingLeft: 2 },
913
- h(Text, { color: 'gray', dimColor: true }, `Thinking: ${msg.text}`)
914
- );
915
- }
916
-
917
- if (msg.isActivity) {
918
- return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
919
- h(Box, null,
920
- h(Text, { color: 'greenBright' }, '• '),
921
- h(Text, { bold: true, color: msg.labelColor || 'blueBright' }, msg.activityTitle || msg.label || 'Activity')
922
- ),
923
- h(Box, { paddingLeft: 2, marginBottom: 1 },
924
- h(Text, { color: 'gray' }, '└ '),
925
- ...renderActivityDetail(msg.activityDetail || msg.text)
926
- )
927
- );
928
- }
929
-
930
- if (msg.isDiffPreview) {
931
- return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
932
- renderDiffPreview(msg.preview || '')
933
- );
934
- }
935
-
936
- let name = 'Mint';
937
- let nameColor = 'greenBright';
938
-
939
- if (msg.role === 'user') {
940
- name = 'You';
941
- nameColor = 'cyanBright';
942
- } else if (msg.role === 'error') {
943
- name = 'Error';
944
- nameColor = 'redBright';
945
- } else if (msg.role === 'system') {
946
- name = msg.label || 'System';
947
- nameColor = msg.labelColor || 'blueBright';
948
- }
949
-
950
- return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
951
- h(Box, null,
952
- h(Text, { bold: true, color: nameColor }, name),
953
- h(Text, { color: 'gray' }, ` ${msg.time instanceof Date ? msg.time.toLocaleTimeString() : ''}`)
954
- ),
955
- h(Box, { paddingLeft: 2, marginBottom: 1 },
956
- h(Text, { wrap: 'wrap' }, cleanDisplayText(msg.text, msg.role))
957
- )
958
- );
959
- };
960
-
961
- return h(Box, { flexDirection: 'column', paddingX: 1, width: '100%' },
962
- // Static History: Messages
963
- h(Static, { items: history }, (msg, index) => renderMessage(msg, index, 'history')),
964
- liveAssistant && renderMessage(liveAssistant, 'live', 'live'),
965
-
966
- // Floating (Persistent) UI part
967
- h(Box, { flexDirection: 'column' },
968
- thinking && h(Box, { flexDirection: 'column', marginBottom: 1 },
969
- h(Text, { color: 'gray', dimColor: true }, `─ Working for ${formatDuration(workingSeconds)} ─────────────────────────────────────────────────────────`),
970
- h(Box, { flexDirection: 'row', justifyContent: 'space-between' },
971
- h(Text, { color: 'yellow' }, '● Mint is thinking...'),
972
- h(Text, { color: 'gray', dimColor: true }, 'Press Esc to cancel')
973
- )
974
- ),
975
-
976
- pendingApproval && h(Box, {
977
- flexDirection: 'column',
978
- borderStyle: 'single',
979
- borderColor: 'cyanBright',
980
- paddingX: 1,
981
- marginBottom: 0
982
- },
983
- h(Box, null,
984
- pendingApproval.type === 'plan'
985
- ? h(Text, { bold: true, color: 'greenBright' }, 'Plan')
986
- : [
987
- h(Text, { key: 'approval-title', bold: true, color: 'greenBright' }, 'Approval '),
988
- h(Text, { key: 'approval-type', color: 'cyanBright' }, `[${pendingApproval.type}] `),
989
- h(Text, { key: 'approval-label', color: 'white' }, pendingApproval.label)
990
- ]
991
- ),
992
- pendingApproval.warnings && pendingApproval.warnings.length > 0 && h(Box, { flexDirection: 'column', marginTop: 1, marginBottom: 1 },
993
- ...pendingApproval.warnings.map((warning, index) =>
994
- h(Box, { key: `approval-warning-${index}` },
995
- h(Text, { color: 'yellowBright' }, 'Warning: '),
996
- h(Text, { color: 'yellowBright' }, warning)
997
- )
998
- )
999
- ),
1000
- renderApprovalPreview(pendingApproval)
1001
- ),
1002
-
1003
- pendingApproval && h(Box, {
1004
- flexDirection: 'column',
1005
- borderStyle: 'single',
1006
- borderColor: approvalChoice === 'deny' ? 'redBright' : 'greenBright',
1007
- paddingX: 1,
1008
- marginBottom: 0
1009
- },
1010
- h(Box, null,
1011
- h(Text, {
1012
- color: approvalChoice === 'approve' ? 'black' : 'greenBright',
1013
- backgroundColor: approvalChoice === 'approve' ? 'greenBright' : undefined,
1014
- bold: true
1015
- }, approvalChoice === 'approve' ? '▸ Approve' : ' Approve')
1016
- ),
1017
- h(Box, null,
1018
- h(Text, {
1019
- color: approvalChoice === 'approve_session' ? 'black' : 'cyanBright',
1020
- backgroundColor: approvalChoice === 'approve_session' ? 'cyanBright' : undefined,
1021
- bold: true
1022
- }, approvalChoice === 'approve_session' ? '▸ Approve Session' : ' Approve Session')
1023
- ),
1024
- h(Box, null,
1025
- h(Text, {
1026
- color: approvalChoice === 'deny' ? 'white' : 'redBright',
1027
- backgroundColor: approvalChoice === 'deny' ? 'redBright' : undefined,
1028
- bold: true
1029
- }, approvalChoice === 'deny' ? '▸ Deny' : ' Deny')
1030
- ),
1031
- h(Box, null,
1032
- h(Text, { color: 'gray', dimColor: true }, ' ↑/↓ Enter y/a/n')
1033
- )
1034
- ),
1035
-
1036
- // Compact Input Area
1037
- h(Box, { borderStyle: 'round', borderColor: pendingApproval ? 'gray' : 'greenBright', paddingX: 1, flexDirection: 'column' },
1038
- pendingImages.length > 0 && h(Box, null,
1039
- h(Text, { color: 'greenBright' }, `${pendingImages.length} image${pendingImages.length === 1 ? '' : 's'} attached `),
1040
- h(Text, { color: 'gray' }, 'Enter to send, Ctrl+Backspace remove, Esc clear')
1041
- ),
1042
- pendingPaste && h(Box, null,
1043
- pendingPastePrefix && h(Text, { color: 'cyanBright' }, '[Text before] '),
1044
- h(Text, { color: 'yellowBright' }, pendingPaste.label),
1045
- h(Text, { color: 'gray' }, ' Enter to send, Esc clear')
1046
- ),
1047
- h(Box, { flexDirection: 'row' },
1048
- h(Text, { bold: true, color: 'greenBright' }, '› '),
1049
- h(TextInput, {
1050
- key: `input-${inputResetKey}`,
1051
- value: input,
1052
- onChange: pendingApproval ? () => {} : handleInputChange,
1053
- onSubmit: pendingApproval ? () => {} : handleSubmit,
1054
- placeholder: pendingApproval ? 'Approval pending...' : (thinking ? 'Agent is working... Press Esc to cancel' : 'Ask anything...')
1055
- })
1056
- )
1057
- ),
1058
-
1059
- // Suggestions Menu
1060
- showSuggestions && suggestions.length > 0 && h(Box, {
1061
- flexDirection: 'column',
1062
- borderStyle: 'single',
1063
- borderColor: 'gray',
1064
- paddingX: 1,
1065
- marginBottom: 0
1066
- },
1067
- h(Box, { justifyContent: 'space-between' },
1068
- h(Text, { color: 'gray', dimColor: true }, 'Commands'),
1069
- h(Text, { color: 'gray', dimColor: true }, `${visibleSuggestions.current}/${visibleSuggestions.total}`)
1070
- ),
1071
- visibleSuggestions.visible.map((s, i) => {
1072
- const actualIndex = visibleSuggestions.start + i;
1073
- return h(Box, { key: s.cmd, flexDirection: 'row' },
1074
- h(Text, {
1075
- backgroundColor: actualIndex === selectedIndex ? 'green' : undefined,
1076
- color: actualIndex === selectedIndex ? 'white' : 'greenBright'
1077
- }, s.cmd.padEnd(12)),
1078
- h(Text, { color: 'gray' }, ` ${s.desc}`)
1079
- );
1080
- })
1081
- ),
1082
-
1083
- // Status Bar
1084
- h(Box, { justifyContent: 'space-between' },
1085
- h(Box, null,
1086
- h(Text, { color: 'cyan' }, `[${fastMode ? 'Fast' : mode}] `),
1087
- h(Text, { color: 'magentaBright' }, (model || config.geminiModel || 'gemini').slice(0, 46)),
1088
- approvalSessionAutoApprove && h(Text, { color: 'greenBright' }, ' approvals:session')
1089
- ),
1090
- h(Box, null,
1091
- h(Text, { color: 'gray' }, `path: ...${workspace.slice(-20)}`)
1092
- )
1093
- )
1094
- )
1095
- );
1096
- });
1097
-
1098
- // Print banner once before rendering the main app-
1099
- console.log(`\x1b[38;5;121m\x1b[1m __ __ _ _ ___ _ ___ \x1b[0m`);
1100
- console.log(`\x1b[38;5;121m\x1b[1m| \\/ (_)_ __ | |_ / __| | |_ _|\x1b[0m`);
1101
- console.log(`\x1b[38;5;121m\x1b[1m| |\\/| | | '_ \\| _| (__| |__ | | \x1b[0m`);
1102
- console.log(`\x1b[38;5;121m\x1b[1m|_| |_|_|_| |_|\\__|\\___|____|___|\x1b[0m`);
1103
- console.log(`\x1b[90mType naturally to chat. Esc to exit.\x1b[0m\n`);
1104
-
1105
- const ref = createRef();
1106
- const instance = render(h(App, { ref, ...options }), { exitOnCtrlC: false });
1107
-
1108
- return {
1109
- unmount: () => instance.unmount(),
1110
- appendMessage: (role, text, metadata) => ref.current?.appendMessage(role, text, metadata),
1111
- setThinking: (val, seconds) => ref.current?.setThinking(val, seconds),
1112
- setMode: (val) => ref.current?.setMode(val),
1113
- setFastMode: (val) => ref.current?.setFastMode(val),
1114
- toggleFastMode: () => ref.current?.toggleFastMode(),
1115
- getFastMode: () => ref.current?.getFastMode(),
1116
- setInputText: (val) => ref.current?.setInputText(val),
1117
- setPendingPasteText: (text) => ref.current?.setPendingPasteText(text),
1118
- updateStatusModel: (val) => ref.current?.updateStatusModel(val),
1119
- updateWorkspace: (val) => ref.current?.updateWorkspace(val),
1120
- attachImage: (image) => ref.current?.attachImage(image),
1121
- appendCodeStep: (info) => ref.current?.appendCodeStep(info),
1122
- streamMessage: (metadata = {}) => {
1123
- ref.current?.beginAssistantStream(metadata);
1124
- return {
1125
- appendChunk: (chunk) => {
1126
- if (!String(chunk || '').trim()) return;
1127
- ref.current?.appendAssistantStreamChunk(chunk);
1128
- },
1129
- finalize: () => {
1130
- ref.current?.finalizeAssistantStream();
1131
- }
1132
- };
1133
- },
1134
- copyLastResponse: () => false,
1135
- requestApproval: (request) => ref.current?.requestApproval(request) || Promise.resolve(false),
1136
- askUser: () => Promise.resolve('')
1137
- };
1138
- }
1139
-
1140
- module.exports = {
1141
- createChatUI,
1142
- _helpers: {
1143
- cleanDisplayText,
1144
- stripInlineMarkdown,
1145
- compactPathLabel,
1146
- formatActivityStep,
1147
- formatDuration,
1148
- splitDiffStatSegments,
1149
- getNextApprovalChoice,
1150
- getVisibleSuggestions,
1151
- parseUnifiedDiffPreview,
1152
- isUnifiedDiffPreview,
1153
- getDiffLineStyle,
1154
- shouldAppendMessage,
1155
- appendInlineImageToken,
1156
- removeImageToken,
1157
- removeAllImageTokens
1158
- }
1159
- };