@pheem49/mint 1.8.2 → 1.10.0

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 (106) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +297 -107
  4. package/bin/mint +0 -0
  5. package/crates/mint-cli/Cargo.toml +1 -0
  6. package/crates/mint-cli/src/actions.rs +233 -0
  7. package/crates/mint-cli/src/agent.rs +382 -127
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/image.rs +8 -8
  10. package/crates/mint-cli/src/interactive.rs +2888 -0
  11. package/crates/mint-cli/src/main.rs +391 -2506
  12. package/crates/mint-cli/src/markdown.rs +294 -0
  13. package/crates/mint-cli/src/onboard.rs +152 -109
  14. package/crates/mint-cli/src/setup.rs +143 -143
  15. package/crates/mint-cli/src/updater.rs +48 -6
  16. package/crates/mint-core/Cargo.toml +1 -0
  17. package/crates/mint-core/src/agent_loop.rs +1 -0
  18. package/crates/mint-core/src/api_server.rs +431 -65
  19. package/crates/mint-core/src/browser.rs +630 -102
  20. package/crates/mint-core/src/channels.rs +105 -6
  21. package/crates/mint-core/src/chat.rs +174 -11
  22. package/crates/mint-core/src/code_tools.rs +47 -31
  23. package/crates/mint-core/src/config.rs +302 -17
  24. package/crates/mint-core/src/image_gen.rs +299 -38
  25. package/crates/mint-core/src/lib.rs +10 -7
  26. package/crates/mint-core/src/mcp.rs +119 -6
  27. package/crates/mint-core/src/memory.rs +6 -1
  28. package/crates/mint-core/src/orchestration.rs +237 -96
  29. package/crates/mint-core/src/pictures.rs +67 -5
  30. package/crates/mint-core/src/semantic.rs +1 -1
  31. package/crates/mint-core/src/shell.rs +1 -1
  32. package/crates/mint-core/src/skills.rs +128 -25
  33. package/crates/mint-core/src/video_gen.rs +235 -0
  34. package/package.json +8 -2
  35. package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
  36. package/shared-modules/linux-audio/fluidsynth2.json +8 -2
  37. package/shared-modules/linux-audio/lilv.json +26 -19
  38. package/shared-modules/linux-audio/lv2.json +0 -1
  39. package/shared-modules/luajit/luajit.json +2 -2
  40. package/skills-lock.json +41 -0
  41. package/src/renderer/index-web.html +1 -1
  42. package/src/renderer/index.html +1 -1
  43. package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
  44. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  45. package/src/renderer/shared/platform.ts +3 -0
  46. package/src/renderer/shared/types.ts +4 -0
  47. package/src/renderer/shared/utils/approval.tsx +86 -7
  48. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  49. package/src/renderer/shared/utils/markdown.tsx +217 -88
  50. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  51. package/src/renderer/src/components/ChatPanel.tsx +486 -69
  52. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  53. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  54. package/src/renderer/src/components/MintDashboard.tsx +195 -7
  55. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  56. package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
  57. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  58. package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
  59. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  60. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  61. package/src/renderer/src/components/SettingsWindow.tsx +106 -3
  62. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  63. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  64. package/src/renderer/src/css/image-studio.css +106 -0
  65. package/src/renderer/src/css/settings.css +158 -0
  66. package/src/renderer/src/css/styles.css +368 -1
  67. package/src/renderer/src/css/veo-studio.css +626 -0
  68. package/src/renderer/src/index.css +1 -0
  69. package/src/renderer/src/tauri.ts +144 -17
  70. package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
  71. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  72. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  73. package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
  74. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  75. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
  76. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  77. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
  78. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  79. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  80. package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
  81. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  82. package/src/renderer/src-web/css/image-studio.css +106 -0
  83. package/src/renderer/src-web/css/settings.css +158 -0
  84. package/src/renderer/src-web/css/styles.css +369 -1
  85. package/src/renderer/src-web/css/veo-studio.css +650 -0
  86. package/src/renderer/src-web/index.css +1 -0
  87. package/src/renderer/src-web/tauri.ts +135 -16
  88. package/src-tauri/src/desktop.rs +18 -16
  89. package/src-tauri/src/discord_rpc.rs +1 -1
  90. package/src-tauri/src/lib.rs +155 -25
  91. package/src-tauri/src/webhooks.rs +45 -31
  92. package/src-tauri/src/workflows.rs +1 -0
  93. package/src-tauri/tauri.conf.json +1 -1
  94. package/temp_gridgeist/AGENTS.md +8 -0
  95. package/temp_gridgeist/CHANGELOG.md +130 -0
  96. package/temp_gridgeist/LICENSE +21 -0
  97. package/temp_gridgeist/README.md +323 -0
  98. package/temp_gridgeist/README.th.md +325 -0
  99. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  100. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  101. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  102. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  103. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  104. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  105. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  106. package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
@@ -0,0 +1,294 @@
1
+ pub fn is_table_line(line: &str) -> bool {
2
+ let trimmed = line.trim();
3
+ trimmed.starts_with('|') && trimmed.ends_with('|') && trimmed.len() > 1
4
+ }
5
+
6
+ pub fn process_inline_bold(s: &str) -> String {
7
+ let mut result = String::new();
8
+ let parts = s.split("**");
9
+ let mut is_bold = false;
10
+ for part in parts {
11
+ if is_bold {
12
+ result.push_str("\x1b[1m");
13
+ result.push_str(part);
14
+ result.push_str("\x1b[0m");
15
+ } else {
16
+ result.push_str(part);
17
+ }
18
+ is_bold = !is_bold;
19
+ }
20
+ result
21
+ }
22
+
23
+ pub fn format_line(line: &str) -> String {
24
+ let mut formatted = line.to_string();
25
+ let trimmed = line.trim_start();
26
+ if (trimmed.starts_with("- ") || trimmed.starts_with("* ") || trimmed.starts_with("+ "))
27
+ && trimmed.len() >= 2
28
+ {
29
+ let leading_len = line.len() - trimmed.len();
30
+ let leading_spaces = &line[..leading_len];
31
+ formatted = format!("{}•{}", leading_spaces, &trimmed[1..]);
32
+ }
33
+ process_inline_bold(&formatted)
34
+ }
35
+
36
+ pub fn unicode_width(s: &str) -> usize {
37
+ let mut count = 0;
38
+ for c in s.chars() {
39
+ let val = c as u32;
40
+ if (0x0E31..=0x0E31).contains(&val)
41
+ || (0x0E34..=0x0E3A).contains(&val)
42
+ || (0x0E47..=0x0E4E).contains(&val)
43
+ {
44
+ continue;
45
+ }
46
+ if is_cjk(c) {
47
+ count += 2;
48
+ } else {
49
+ count += 1;
50
+ }
51
+ }
52
+ count
53
+ }
54
+
55
+ pub fn is_cjk(c: char) -> bool {
56
+ let val = c as u32;
57
+ (0x4E00..=0x9FFF).contains(&val)
58
+ || (0x3400..=0x4DBF).contains(&val)
59
+ || (0x20000..=0x2A6DF).contains(&val)
60
+ || (0xAC00..=0xD7AF).contains(&val)
61
+ || (0x3040..=0x309F).contains(&val)
62
+ || (0x30A0..=0x30FF).contains(&val)
63
+ }
64
+
65
+ pub fn render_markdown_table(table_lines: &[String]) -> String {
66
+ let mut rows: Vec<Vec<String>> = Vec::new();
67
+ for line in table_lines {
68
+ let trimmed = line.trim();
69
+ if trimmed.is_empty() {
70
+ continue;
71
+ }
72
+ let content = &trimmed[1..trimmed.len() - 1];
73
+ let content_escaped = content.replace("\\|", "\u{0000}");
74
+ let cols: Vec<String> = content_escaped
75
+ .split('|')
76
+ .map(|s| s.replace("\u{0000}", "|").trim().to_string())
77
+ .collect();
78
+ rows.push(cols);
79
+ }
80
+
81
+ if rows.is_empty() {
82
+ return String::new();
83
+ }
84
+
85
+ let mut has_separator = false;
86
+ let mut separator_idx = None;
87
+ for (i, row) in rows.iter().enumerate() {
88
+ if row.iter().all(|col| {
89
+ col.chars()
90
+ .all(|c| c == '-' || c == ':' || c == ' ' || c == '\t')
91
+ }) && !row.is_empty()
92
+ {
93
+ has_separator = true;
94
+ separator_idx = Some(i);
95
+ break;
96
+ }
97
+ }
98
+
99
+ let mut data_rows: Vec<Vec<String>> = Vec::new();
100
+ let mut header_row: Option<Vec<String>> = None;
101
+
102
+ if has_separator {
103
+ let sep_idx = separator_idx.unwrap();
104
+ if sep_idx > 0 {
105
+ header_row = Some(rows[sep_idx - 1].clone());
106
+ for (i, row) in rows.iter().enumerate() {
107
+ if i != sep_idx && i != sep_idx - 1 {
108
+ data_rows.push(row.clone());
109
+ }
110
+ }
111
+ } else {
112
+ for (i, row) in rows.iter().enumerate() {
113
+ if i != sep_idx {
114
+ data_rows.push(row.clone());
115
+ }
116
+ }
117
+ }
118
+ } else {
119
+ header_row = Some(rows[0].clone());
120
+ data_rows = rows[1..].to_vec();
121
+ }
122
+
123
+ let num_cols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
124
+ let mut col_widths = vec![0; num_cols];
125
+
126
+ if let Some(ref header) = header_row {
127
+ for (i, col) in header.iter().enumerate() {
128
+ if i < num_cols {
129
+ col_widths[i] = col_widths[i].max(unicode_width(col));
130
+ }
131
+ }
132
+ }
133
+ for row in &data_rows {
134
+ for (i, col) in row.iter().enumerate() {
135
+ if i < num_cols {
136
+ col_widths[i] = col_widths[i].max(unicode_width(col));
137
+ }
138
+ }
139
+ }
140
+
141
+ let mut rendered = String::new();
142
+
143
+ let draw_border =
144
+ |left: &str, mid: &str, right: &str, fill: &str, widths: &[usize]| -> String {
145
+ let mut s = left.to_string();
146
+ for (i, &w) in widths.iter().enumerate() {
147
+ s.push_str(&fill.repeat(w + 2));
148
+ if i < widths.len() - 1 {
149
+ s.push_str(mid);
150
+ }
151
+ }
152
+ s.push_str(right);
153
+ s.push('\n');
154
+ s
155
+ };
156
+
157
+ rendered.push_str(&draw_border("┌", "┬", "┐", "─", &col_widths));
158
+
159
+ if let Some(ref header) = header_row {
160
+ rendered.push('│');
161
+ for (i, col) in header.iter().enumerate() {
162
+ let width = unicode_width(col);
163
+ let padding = col_widths[i] - width;
164
+ rendered.push_str(&format!(
165
+ " \x1b[1;36m{}\x1b[0m{}",
166
+ col,
167
+ " ".repeat(padding + 1)
168
+ ));
169
+ rendered.push('│');
170
+ }
171
+ rendered.push('\n');
172
+ rendered.push_str(&draw_border("├", "┼", "┤", "─", &col_widths));
173
+ }
174
+
175
+ for (r_idx, row) in data_rows.iter().enumerate() {
176
+ rendered.push('│');
177
+ for i in 0..num_cols {
178
+ let col_val = row.get(i).cloned().unwrap_or_default();
179
+ let width = unicode_width(&col_val);
180
+ let padding = col_widths[i] - width;
181
+ rendered.push_str(&format!(" {}{}", col_val, " ".repeat(padding + 1)));
182
+ rendered.push('│');
183
+ }
184
+ rendered.push('\n');
185
+
186
+ if r_idx < data_rows.len() - 1 {
187
+ rendered.push_str(&draw_border("├", "┼", "┤", "─", &col_widths));
188
+ }
189
+ }
190
+
191
+ rendered.push_str(draw_border("└", "┴", "┘", "─", &col_widths).trim_end());
192
+ rendered
193
+ }
194
+
195
+ pub struct CliStreamFormatter {
196
+ line_buffer: String,
197
+ table_buffer: Vec<String>,
198
+ in_code_block: bool,
199
+ }
200
+
201
+ impl CliStreamFormatter {
202
+ pub fn new() -> Self {
203
+ Self {
204
+ line_buffer: String::new(),
205
+ table_buffer: Vec::new(),
206
+ in_code_block: false,
207
+ }
208
+ }
209
+
210
+ pub fn process_char(&mut self, c: char, mut print_fn: impl FnMut(char)) {
211
+ self.line_buffer.push(c);
212
+ if c == '\n' {
213
+ let mut line = self.line_buffer.clone();
214
+ self.line_buffer.clear();
215
+ if line.ends_with('\n') {
216
+ line.pop();
217
+ }
218
+
219
+ let trimmed = line.trim();
220
+ if trimmed.starts_with("```") {
221
+ self.in_code_block = !self.in_code_block;
222
+ }
223
+
224
+ if self.in_code_block {
225
+ if !self.table_buffer.is_empty() {
226
+ self.flush_table(&mut print_fn);
227
+ }
228
+ for ch in line.chars() {
229
+ print_fn(ch);
230
+ }
231
+ print_fn('\n');
232
+ for ch in " ".chars() {
233
+ print_fn(ch);
234
+ }
235
+ } else {
236
+ if is_table_line(&line) {
237
+ self.table_buffer.push(line);
238
+ } else {
239
+ if !self.table_buffer.is_empty() {
240
+ self.flush_table(&mut print_fn);
241
+ }
242
+ let formatted = format_line(&line);
243
+ for ch in formatted.chars() {
244
+ print_fn(ch);
245
+ }
246
+ print_fn('\n');
247
+ for ch in " ".chars() {
248
+ print_fn(ch);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ pub fn finalize(&mut self, mut print_fn: impl FnMut(char)) {
256
+ if !self.line_buffer.is_empty() {
257
+ let mut line = self.line_buffer.clone();
258
+ self.line_buffer.clear();
259
+ if line.ends_with('\n') {
260
+ line.pop();
261
+ }
262
+ if self.in_code_block {
263
+ if !self.table_buffer.is_empty() {
264
+ self.flush_table(&mut print_fn);
265
+ }
266
+ for ch in line.chars() {
267
+ print_fn(ch);
268
+ }
269
+ } else if is_table_line(&line) {
270
+ self.table_buffer.push(line);
271
+ } else {
272
+ if !self.table_buffer.is_empty() {
273
+ self.flush_table(&mut print_fn);
274
+ }
275
+ let formatted = format_line(&line);
276
+ for ch in formatted.chars() {
277
+ print_fn(ch);
278
+ }
279
+ }
280
+ }
281
+ if !self.table_buffer.is_empty() {
282
+ self.flush_table(&mut print_fn);
283
+ }
284
+ }
285
+
286
+ fn flush_table<F: FnMut(char)>(&mut self, mut print_fn: F) {
287
+ let rendered = render_markdown_table(&self.table_buffer);
288
+ for ch in rendered.chars() {
289
+ print_fn(ch);
290
+ }
291
+ print_fn('\n');
292
+ self.table_buffer.clear();
293
+ }
294
+ }
@@ -75,9 +75,11 @@ const REPLICATE_MODEL_PRESETS: &[&str] = &[
75
75
  "black-forest-labs/flux-1.1-pro",
76
76
  "black-forest-labs/flux-schnell",
77
77
  "stability-ai/sdxl",
78
- "bytedance/sdxl-lightning-4step",
79
78
  ];
80
79
 
80
+ // ── Video Generation Providers ──────────────────────────────────────────────
81
+ const VEO_VIDEO_MODEL_PRESETS: &[&str] = &["veo-2.0-generate-001", "veo-2.0-flash-001"];
82
+
81
83
  pub async fn run() -> Result<()> {
82
84
  let mut config = load_config()?;
83
85
 
@@ -280,6 +282,19 @@ pub async fn run() -> Result<()> {
280
282
  key: "img_replicate",
281
283
  enabled: !config.replicate_api_key.is_empty(),
282
284
  },
285
+ // ── Video Generation ─────────────────────────────────────────────────
286
+ OnboardService {
287
+ category: "Video Generation",
288
+ name: "Google Veo (Gemini Videos) [uses Gemini key]",
289
+ key: "vid_veo",
290
+ enabled: !config.api_key.is_empty()
291
+ && config
292
+ .extra
293
+ .get("veoModel")
294
+ .and_then(|v| v.as_str())
295
+ .map(|s| !s.is_empty())
296
+ .unwrap_or(false),
297
+ },
283
298
  ];
284
299
 
285
300
  let mut cursor = 0;
@@ -296,71 +311,71 @@ pub async fn run() -> Result<()> {
296
311
  loop {
297
312
  match event::poll(std::time::Duration::from_millis(100)) {
298
313
  Ok(true) => {
299
- if let Event::Key(key_event) = event::read()? {
300
- if key_event.kind == event::KeyEventKind::Press {
301
- let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
302
- && key_event
303
- .modifiers
304
- .contains(crossterm::event::KeyModifiers::CONTROL);
305
- if is_ctrl_c {
306
- disable_raw_mode()?;
307
- println!("\n\x1b[31mOnboarding cancelled.\x1b[0m");
308
- return Ok(());
309
- }
314
+ if let Event::Key(key_event) = event::read()?
315
+ && key_event.kind == event::KeyEventKind::Press
316
+ {
317
+ let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
318
+ && key_event
319
+ .modifiers
320
+ .contains(crossterm::event::KeyModifiers::CONTROL);
321
+ if is_ctrl_c {
322
+ disable_raw_mode()?;
323
+ println!("\n\x1b[31mOnboarding cancelled.\x1b[0m");
324
+ return Ok(());
325
+ }
310
326
 
311
- match key_event.code {
312
- KeyCode::Up => {
313
- if cursor > 0 {
314
- cursor -= 1;
315
- } else {
316
- cursor = services.len() - 1;
317
- }
318
- disable_raw_mode()?;
319
- print!("\x1b[{}A\x1b[J", service_display_lines(&services));
320
- print_services(&services, cursor);
321
- enable_raw_mode()?;
322
- }
323
- KeyCode::Down => {
324
- if cursor < services.len() - 1 {
325
- cursor += 1;
326
- } else {
327
- cursor = 0;
328
- }
329
- disable_raw_mode()?;
330
- print!("\x1b[{}A\x1b[J", service_display_lines(&services));
331
- print_services(&services, cursor);
332
- enable_raw_mode()?;
333
- }
334
- KeyCode::Char(' ') => {
335
- services[cursor].enabled = !services[cursor].enabled;
336
- disable_raw_mode()?;
337
- print!("\x1b[{}A\x1b[J", service_display_lines(&services));
338
- print_services(&services, cursor);
339
- enable_raw_mode()?;
327
+ match key_event.code {
328
+ KeyCode::Up => {
329
+ if cursor > 0 {
330
+ cursor -= 1;
331
+ } else {
332
+ cursor = services.len() - 1;
340
333
  }
341
- KeyCode::Char('a') => {
342
- for svc in &mut services {
343
- svc.enabled = true;
344
- }
345
- disable_raw_mode()?;
346
- print!("\x1b[{}A\x1b[J", service_display_lines(&services));
347
- print_services(&services, cursor);
348
- enable_raw_mode()?;
334
+ disable_raw_mode()?;
335
+ print!("\x1b[{}A\x1b[J", service_display_lines(&services));
336
+ print_services(&services, cursor);
337
+ enable_raw_mode()?;
338
+ }
339
+ KeyCode::Down => {
340
+ if cursor < services.len() - 1 {
341
+ cursor += 1;
342
+ } else {
343
+ cursor = 0;
349
344
  }
350
- KeyCode::Char('i') => {
351
- for svc in &mut services {
352
- svc.enabled = !svc.enabled;
353
- }
354
- disable_raw_mode()?;
355
- print!("\x1b[{}A\x1b[J", service_display_lines(&services));
356
- print_services(&services, cursor);
357
- enable_raw_mode()?;
345
+ disable_raw_mode()?;
346
+ print!("\x1b[{}A\x1b[J", service_display_lines(&services));
347
+ print_services(&services, cursor);
348
+ enable_raw_mode()?;
349
+ }
350
+ KeyCode::Char(' ') => {
351
+ services[cursor].enabled = !services[cursor].enabled;
352
+ disable_raw_mode()?;
353
+ print!("\x1b[{}A\x1b[J", service_display_lines(&services));
354
+ print_services(&services, cursor);
355
+ enable_raw_mode()?;
356
+ }
357
+ KeyCode::Char('a') => {
358
+ for svc in &mut services {
359
+ svc.enabled = true;
358
360
  }
359
- KeyCode::Enter => {
360
- break;
361
+ disable_raw_mode()?;
362
+ print!("\x1b[{}A\x1b[J", service_display_lines(&services));
363
+ print_services(&services, cursor);
364
+ enable_raw_mode()?;
365
+ }
366
+ KeyCode::Char('i') => {
367
+ for svc in &mut services {
368
+ svc.enabled = !svc.enabled;
361
369
  }
362
- _ => {}
370
+ disable_raw_mode()?;
371
+ print!("\x1b[{}A\x1b[J", service_display_lines(&services));
372
+ print_services(&services, cursor);
373
+ enable_raw_mode()?;
374
+ }
375
+ KeyCode::Enter => {
376
+ break;
363
377
  }
378
+ _ => {}
364
379
  }
365
380
  }
366
381
  }
@@ -976,6 +991,34 @@ pub async fn run() -> Result<()> {
976
991
  config.replicate_api_key = String::new();
977
992
  }
978
993
 
994
+ // Google Veo (Gemini Videos)
995
+ if is_selected("vid_veo", &services) {
996
+ println!("\n\x1b[36m--- Google Veo (Gemini Videos) ---\x1b[0m");
997
+ println!(
998
+ "\x1b[90mUses the same Gemini API key as Step 1. Select the video generation model.\x1b[0m"
999
+ );
1000
+ let current_veo_model = config
1001
+ .extra
1002
+ .get("veoModel")
1003
+ .and_then(|v| v.as_str())
1004
+ .unwrap_or("veo-2.0-generate-001")
1005
+ .to_string();
1006
+ let selected_veo_model = prompt_select_or_custom(
1007
+ "Veo Model",
1008
+ static_model_options(VEO_VIDEO_MODEL_PRESETS),
1009
+ Some(&current_veo_model),
1010
+ "Custom Veo model...",
1011
+ )?;
1012
+ config.extra.insert(
1013
+ "veoModel".to_string(),
1014
+ serde_json::Value::String(selected_veo_model),
1015
+ );
1016
+ config.extra.insert(
1017
+ "videoGenProvider".to_string(),
1018
+ serde_json::Value::String("veo".to_string()),
1019
+ );
1020
+ }
1021
+
979
1022
  save_config(&config)?;
980
1023
  println!("\x1b[32m✅ Configuration saved successfully!\x1b[0m");
981
1024
  Ok(())
@@ -1052,52 +1095,52 @@ fn prompt_select_or_custom(
1052
1095
  loop {
1053
1096
  match event::poll(std::time::Duration::from_millis(100)) {
1054
1097
  Ok(true) => {
1055
- if let Event::Key(key_event) = event::read()? {
1056
- if key_event.kind == event::KeyEventKind::Press {
1057
- let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
1058
- && key_event
1059
- .modifiers
1060
- .contains(crossterm::event::KeyModifiers::CONTROL);
1061
- if is_ctrl_c {
1062
- disable_raw_mode()?;
1063
- println!("\n\x1b[31mOnboarding cancelled.\x1b[0m");
1064
- bail!("onboarding cancelled");
1065
- }
1098
+ if let Event::Key(key_event) = event::read()?
1099
+ && key_event.kind == event::KeyEventKind::Press
1100
+ {
1101
+ let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
1102
+ && key_event
1103
+ .modifiers
1104
+ .contains(crossterm::event::KeyModifiers::CONTROL);
1105
+ if is_ctrl_c {
1106
+ disable_raw_mode()?;
1107
+ println!("\n\x1b[31mOnboarding cancelled.\x1b[0m");
1108
+ bail!("onboarding cancelled");
1109
+ }
1066
1110
 
1067
- match key_event.code {
1068
- KeyCode::Up => {
1069
- if cursor > 0 {
1070
- cursor -= 1;
1071
- } else {
1072
- cursor = options.len() - 1;
1073
- }
1074
- disable_raw_mode()?;
1075
- print!("\x1b[{}A\x1b[J", options.len());
1076
- print_select_options(&options, cursor);
1077
- enable_raw_mode()?;
1111
+ match key_event.code {
1112
+ KeyCode::Up => {
1113
+ if cursor > 0 {
1114
+ cursor -= 1;
1115
+ } else {
1116
+ cursor = options.len() - 1;
1078
1117
  }
1079
- KeyCode::Down => {
1080
- if cursor < options.len() - 1 {
1081
- cursor += 1;
1082
- } else {
1083
- cursor = 0;
1084
- }
1085
- disable_raw_mode()?;
1086
- print!("\x1b[{}A\x1b[J", options.len());
1087
- print_select_options(&options, cursor);
1088
- enable_raw_mode()?;
1118
+ disable_raw_mode()?;
1119
+ print!("\x1b[{}A\x1b[J", options.len());
1120
+ print_select_options(&options, cursor);
1121
+ enable_raw_mode()?;
1122
+ }
1123
+ KeyCode::Down => {
1124
+ if cursor < options.len() - 1 {
1125
+ cursor += 1;
1126
+ } else {
1127
+ cursor = 0;
1089
1128
  }
1090
- KeyCode::Enter => {
1091
- disable_raw_mode()?;
1092
- println!();
1093
- let selected = options[cursor].clone();
1094
- if selected == custom_label {
1095
- return prompt_input(label, Some(current));
1096
- }
1097
- return Ok(selected);
1129
+ disable_raw_mode()?;
1130
+ print!("\x1b[{}A\x1b[J", options.len());
1131
+ print_select_options(&options, cursor);
1132
+ enable_raw_mode()?;
1133
+ }
1134
+ KeyCode::Enter => {
1135
+ disable_raw_mode()?;
1136
+ println!();
1137
+ let selected = options[cursor].clone();
1138
+ if selected == custom_label {
1139
+ return prompt_input(label, Some(current));
1098
1140
  }
1099
- _ => {}
1141
+ return Ok(selected);
1100
1142
  }
1143
+ _ => {}
1101
1144
  }
1102
1145
  }
1103
1146
  }
@@ -1223,10 +1266,10 @@ fn print_select_options(options: &[String], cursor: usize) {
1223
1266
 
1224
1267
  fn prompt_input(label: &str, default: Option<&str>) -> Result<String> {
1225
1268
  print!("{}", label);
1226
- if let Some(d) = default {
1227
- if !d.is_empty() {
1228
- print!(" [\x1b[90m{}\x1b[0m]", d);
1229
- }
1269
+ if let Some(d) = default
1270
+ && !d.is_empty()
1271
+ {
1272
+ print!(" [\x1b[90m{}\x1b[0m]", d);
1230
1273
  }
1231
1274
  print!(": ");
1232
1275
  io::stdout().flush()?;
@@ -1234,10 +1277,10 @@ fn prompt_input(label: &str, default: Option<&str>) -> Result<String> {
1234
1277
  let mut input = String::new();
1235
1278
  io::stdin().read_line(&mut input)?;
1236
1279
  let trimmed = input.trim();
1237
- if trimmed.is_empty() {
1238
- if let Some(d) = default {
1239
- return Ok(d.to_string());
1240
- }
1280
+ if trimmed.is_empty()
1281
+ && let Some(d) = default
1282
+ {
1283
+ return Ok(d.to_string());
1241
1284
  }
1242
1285
  Ok(trimmed.to_string())
1243
1286
  }