@pheem49/mint 1.9.0 → 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 (90) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +285 -140
  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 +287 -43
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/interactive.rs +2888 -0
  10. package/crates/mint-cli/src/main.rs +336 -2854
  11. package/crates/mint-cli/src/markdown.rs +294 -0
  12. package/crates/mint-cli/src/onboard.rs +44 -1
  13. package/crates/mint-cli/src/updater.rs +48 -6
  14. package/crates/mint-core/Cargo.toml +1 -0
  15. package/crates/mint-core/src/agent_loop.rs +1 -0
  16. package/crates/mint-core/src/api_server.rs +376 -13
  17. package/crates/mint-core/src/browser.rs +629 -101
  18. package/crates/mint-core/src/channels.rs +105 -6
  19. package/crates/mint-core/src/chat.rs +52 -4
  20. package/crates/mint-core/src/code_tools.rs +29 -11
  21. package/crates/mint-core/src/config.rs +118 -0
  22. package/crates/mint-core/src/image_gen.rs +294 -33
  23. package/crates/mint-core/src/lib.rs +5 -2
  24. package/crates/mint-core/src/mcp.rs +119 -6
  25. package/crates/mint-core/src/orchestration.rs +133 -11
  26. package/crates/mint-core/src/pictures.rs +64 -1
  27. package/crates/mint-core/src/skills.rs +82 -6
  28. package/crates/mint-core/src/video_gen.rs +235 -0
  29. package/package.json +8 -2
  30. package/shared-modules/luajit/luajit.json +2 -2
  31. package/skills-lock.json +41 -0
  32. package/src/renderer/index-web.html +1 -1
  33. package/src/renderer/index.html +1 -1
  34. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  35. package/src/renderer/shared/platform.ts +3 -0
  36. package/src/renderer/shared/types.ts +4 -0
  37. package/src/renderer/shared/utils/approval.tsx +86 -7
  38. package/src/renderer/shared/utils/markdown.tsx +217 -88
  39. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  40. package/src/renderer/src/components/ChatPanel.tsx +461 -64
  41. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  42. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  43. package/src/renderer/src/components/MintDashboard.tsx +150 -5
  44. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  45. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  46. package/src/renderer/src/components/Settings/GeneralTab.tsx +762 -634
  47. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  48. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  49. package/src/renderer/src/components/SettingsWindow.tsx +38 -2
  50. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  51. package/src/renderer/src/css/image-studio.css +106 -0
  52. package/src/renderer/src/css/settings.css +158 -0
  53. package/src/renderer/src/css/styles.css +352 -1
  54. package/src/renderer/src/css/veo-studio.css +626 -0
  55. package/src/renderer/src/index.css +1 -0
  56. package/src/renderer/src/tauri.ts +144 -17
  57. package/src/renderer/src-web/components/ChatPanel.tsx +360 -88
  58. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  59. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  60. package/src/renderer/src-web/components/MintDashboard.tsx +137 -4
  61. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  62. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  63. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +762 -629
  64. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  65. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  66. package/src/renderer/src-web/components/SettingsWindow.tsx +35 -2
  67. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  68. package/src/renderer/src-web/css/image-studio.css +106 -0
  69. package/src/renderer/src-web/css/settings.css +158 -0
  70. package/src/renderer/src-web/css/styles.css +353 -1
  71. package/src/renderer/src-web/css/veo-studio.css +650 -0
  72. package/src/renderer/src-web/index.css +1 -0
  73. package/src/renderer/src-web/tauri.ts +135 -16
  74. package/src-tauri/src/lib.rs +114 -8
  75. package/src-tauri/src/webhooks.rs +45 -31
  76. package/src-tauri/src/workflows.rs +1 -0
  77. package/src-tauri/tauri.conf.json +1 -1
  78. package/temp_gridgeist/AGENTS.md +8 -0
  79. package/temp_gridgeist/CHANGELOG.md +130 -0
  80. package/temp_gridgeist/LICENSE +21 -0
  81. package/temp_gridgeist/README.md +323 -0
  82. package/temp_gridgeist/README.th.md +325 -0
  83. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  84. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  85. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  86. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  87. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  88. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  89. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  90. 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;
@@ -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(())
@@ -2,16 +2,41 @@ use std::path::PathBuf;
2
2
  use std::process::Command;
3
3
  use std::time::{SystemTime, UNIX_EPOCH};
4
4
 
5
- use anyhow::{Context, Result, bail};
5
+ use anyhow::{Result, bail};
6
6
 
7
7
  const PACKAGE: &str = "@pheem49/mint";
8
8
 
9
9
  pub fn run(check_only: bool, dry_run: bool, approved: bool) -> Result<()> {
10
10
  let current = env!("CARGO_PKG_VERSION");
11
+
12
+ use indicatif::{ProgressBar, ProgressStyle};
13
+ use std::time::Duration;
14
+
15
+ let spinner = ProgressBar::new_spinner();
16
+ spinner.set_style(
17
+ ProgressStyle::default_spinner()
18
+ .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
19
+ .template("{spinner:.green} {msg}")
20
+ .unwrap(),
21
+ );
22
+ spinner.set_message("Checking for updates...");
23
+ spinner.enable_steady_tick(Duration::from_millis(80));
24
+
11
25
  let output = Command::new(npm())
12
26
  .args(["view", PACKAGE, "version", "--json"])
13
- .output()
14
- .context("unable to run npm update check")?;
27
+ .output();
28
+
29
+ let output = match output {
30
+ Ok(out) => {
31
+ spinner.finish_and_clear();
32
+ out
33
+ }
34
+ Err(e) => {
35
+ spinner.finish_and_clear();
36
+ bail!("unable to run npm update check: {}", e);
37
+ }
38
+ };
39
+
15
40
  if !output.status.success() {
16
41
  bail!(
17
42
  "npm update check failed: {}",
@@ -34,14 +59,31 @@ pub fn run(check_only: bool, dry_run: bool, approved: bool) -> Result<()> {
34
59
  if !approved {
35
60
  bail!("update installation requires --approve");
36
61
  }
62
+
63
+ let spinner = ProgressBar::new_spinner();
64
+ spinner.set_style(
65
+ ProgressStyle::default_spinner()
66
+ .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
67
+ .template("{spinner:.green} {msg}")
68
+ .unwrap(),
69
+ );
70
+ spinner.set_message(format!("Updating to {latest} via npm..."));
71
+ spinner.enable_steady_tick(Duration::from_millis(80));
72
+
37
73
  let mut command = Command::new(npm());
38
74
  command.args(["install", "-g", &format!("{PACKAGE}@latest")]);
39
75
  if dry_run {
40
76
  command.arg("--dry-run");
41
77
  }
42
- let status = command
43
- .status()
44
- .context("unable to run npm global update")?;
78
+ let status = command.status();
79
+ spinner.finish_and_clear();
80
+
81
+ let status = match status {
82
+ Ok(s) => s,
83
+ Err(e) => {
84
+ bail!("unable to run npm global update: {}", e);
85
+ }
86
+ };
45
87
  if !status.success() {
46
88
  bail!("npm global update failed with status {status}");
47
89
  }
@@ -17,6 +17,7 @@ rusqlite.workspace = true
17
17
  serde.workspace = true
18
18
  serde_json.workspace = true
19
19
  sha2.workspace = true
20
+ similar.workspace = true
20
21
  thiserror.workspace = true
21
22
  tokio.workspace = true
22
23
  tokio-tungstenite.workspace = true
@@ -59,6 +59,7 @@ where
59
59
  chat_id: None,
60
60
  image_data_uri: None,
61
61
  audio_data_uri: None,
62
+ video_data_uri: None,
62
63
  document_attachment: None,
63
64
  workspace_path: None,
64
65
  agent_id: None,