@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
@@ -81,6 +81,8 @@ pub struct MintConfig {
81
81
  pub disabled_tools: Vec<String>,
82
82
  pub agents: Vec<AgentConfig>,
83
83
  pub enable_agent_collaboration: bool,
84
+ /// User-defined OpenAI-compatible providers.
85
+ pub custom_providers: Vec<CustomProvider>,
84
86
  #[serde(flatten)]
85
87
  pub extra: BTreeMap<String, Value>,
86
88
  }
@@ -97,6 +99,45 @@ pub struct AgentConfig {
97
99
  pub enabled: bool,
98
100
  }
99
101
 
102
+ /// A single model entry inside a [`CustomProvider`].
103
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
104
+ #[serde(rename_all = "camelCase")]
105
+ pub struct CustomProviderModel {
106
+ /// The model identifier sent to the API, e.g. `"llama-3.1-8b"`.
107
+ pub model_id: String,
108
+ /// Human-readable label shown in the UI, e.g. `"Llama 3.1 8B"`.
109
+ pub display_name: String,
110
+ }
111
+
112
+ /// An extra HTTP header to include with every request to a [`CustomProvider`].
113
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
114
+ #[serde(rename_all = "camelCase")]
115
+ pub struct CustomProviderHeader {
116
+ pub name: String,
117
+ pub value: String,
118
+ }
119
+
120
+ /// A fully user-defined, OpenAI-compatible AI provider.
121
+ ///
122
+ /// When this provider is active the `ai_provider` config field is set to
123
+ /// `"custom:<id>"` where `<id>` is [`CustomProvider::id`].
124
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
125
+ #[serde(rename_all = "camelCase")]
126
+ pub struct CustomProvider {
127
+ /// Slug identifier (lowercase letters, numbers, hyphens, underscores).
128
+ pub id: String,
129
+ /// Human-readable name shown in the UI.
130
+ pub display_name: String,
131
+ /// Base URL of the OpenAI-compatible endpoint, e.g. `"http://localhost:20128/v1"`.
132
+ pub base_url: String,
133
+ /// API key sent as `Authorization: Bearer <key>`. Empty = no auth header.
134
+ pub api_key: String,
135
+ /// Models offered by this provider.
136
+ pub models: Vec<CustomProviderModel>,
137
+ /// Optional extra HTTP headers sent with every request.
138
+ pub headers: Vec<CustomProviderHeader>,
139
+ }
140
+
100
141
  impl Default for MintConfig {
101
142
  fn default() -> Self {
102
143
  let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
@@ -158,6 +199,7 @@ impl Default for MintConfig {
158
199
  disabled_tools: Vec::new(),
159
200
  agents: default_agents(),
160
201
  enable_agent_collaboration: false,
202
+ custom_providers: Vec::new(),
161
203
  extra: runtime_extra_defaults(),
162
204
  }
163
205
  }
@@ -205,36 +247,176 @@ impl MintConfig {
205
247
  "huggingface" => &self.hf_model,
206
248
  "local_openai" => &self.local_model_name,
207
249
  "ollama" => &self.ollama_model,
250
+ p if p.starts_with("custom:") => {
251
+ // Return the first model id of the matching custom provider, if any.
252
+ self.resolve_custom_provider(p)
253
+ .and_then(|cp| cp.models.first())
254
+ .map(|m| m.model_id.as_str())
255
+ .unwrap_or("")
256
+ }
208
257
  _ => &self.gemini_model,
209
258
  }
210
259
  }
211
260
 
212
- pub fn available_providers(&self) -> Vec<&'static str> {
261
+ /// Sets the active provider and model, saves the configuration to disk, and logs a system event interaction.
262
+ pub fn set_active_model(
263
+ &mut self,
264
+ provider: &str,
265
+ model: Option<&str>,
266
+ ) -> Result<String, ConfigError> {
267
+ let old_provider = self.ai_provider.clone();
268
+ let old_active_model = self.active_model().to_string();
269
+
270
+ self.ai_provider = provider.to_string();
271
+ if let Some(m) = model {
272
+ let m_trimmed = m.trim();
273
+ if !m_trimmed.is_empty() {
274
+ match provider {
275
+ "anthropic" => self.anthropic_model = m_trimmed.to_string(),
276
+ "openai" => self.openai_model = m_trimmed.to_string(),
277
+ "openrouter" => self.openrouter_model = m_trimmed.to_string(),
278
+ "deepseek" => self.deepseek_model = m_trimmed.to_string(),
279
+ "huggingface" => self.hf_model = m_trimmed.to_string(),
280
+ "local_openai" => self.local_model_name = m_trimmed.to_string(),
281
+ "ollama" => self.ollama_model = m_trimmed.to_string(),
282
+ "gemini" => self.gemini_model = m_trimmed.to_string(),
283
+ _ => {}
284
+ }
285
+ }
286
+ }
287
+ save_config(self)?;
288
+
289
+ let active_model = self.active_model().to_string();
290
+ let display_name = format!("{} • {}", self.ai_provider, active_model);
291
+
292
+ let changed = old_provider != self.ai_provider || old_active_model != active_model;
293
+ if changed {
294
+ if let Ok(memory) = crate::MemoryStore::open_default() {
295
+ let _ = memory.add_interaction_for_chat_with_fallback(
296
+ crate::CHAT_CLI_ID,
297
+ &display_name,
298
+ "",
299
+ "system",
300
+ "provider_change",
301
+ None,
302
+ );
303
+ }
304
+ }
305
+
306
+ Ok(display_name)
307
+ }
308
+
309
+ /// Returns the `CustomProvider` whose id matches `provider_key`.
310
+ ///
311
+ /// `provider_key` must be in `"custom:<id>"` format (the leading `"custom:"`
312
+ /// prefix is stripped before the lookup).
313
+ pub fn resolve_custom_provider(&self, provider_key: &str) -> Option<&CustomProvider> {
314
+ let id = provider_key.strip_prefix("custom:").unwrap_or(provider_key);
315
+ self.custom_providers.iter().find(|cp| cp.id == id)
316
+ }
317
+
318
+ pub fn available_providers(&self) -> Vec<String> {
213
319
  let mut providers = Vec::new();
214
320
  if has_value(&self.anthropic_api_key) {
215
- providers.push("anthropic");
321
+ providers.push("anthropic".to_owned());
216
322
  }
217
323
  if has_value(&self.openai_api_key) {
218
- providers.push("openai");
324
+ providers.push("openai".to_owned());
219
325
  }
220
326
  if has_value(&self.openrouter_api_key) {
221
- providers.push("openrouter");
327
+ providers.push("openrouter".to_owned());
222
328
  }
223
329
  if has_value(&self.deepseek_api_key) {
224
- providers.push("deepseek");
330
+ providers.push("deepseek".to_owned());
225
331
  }
226
332
  if has_value(&self.api_key) {
227
- providers.push("gemini");
333
+ providers.push("gemini".to_owned());
228
334
  }
229
335
  if has_value(&self.hf_api_key) {
230
- providers.push("huggingface");
336
+ providers.push("huggingface".to_owned());
231
337
  }
232
338
  if has_value(&self.local_api_base_url) {
233
- providers.push("local_openai");
339
+ providers.push("local_openai".to_owned());
340
+ }
341
+ providers.push("ollama".to_owned());
342
+ // Include all user-defined custom providers that have a base URL configured.
343
+ for cp in &self.custom_providers {
344
+ if !cp.base_url.trim().is_empty() {
345
+ providers.push(format!("custom:{}", cp.id));
346
+ }
234
347
  }
235
- providers.push("ollama");
236
348
  providers
237
349
  }
350
+
351
+ pub fn active_workspace_path(&self) -> Option<PathBuf> {
352
+ self.extra
353
+ .get("activeWorkspacePath")
354
+ .or_else(|| self.extra.get("defaultWorkspacePath"))
355
+ .and_then(Value::as_str)
356
+ .map(str::trim)
357
+ .filter(|s| !s.is_empty())
358
+ .map(PathBuf::from)
359
+ .or_else(|| std::env::current_dir().ok())
360
+ }
361
+
362
+ pub fn line_webhook_host(&self) -> String {
363
+ self.extra
364
+ .get("lineWebhookHost")
365
+ .and_then(Value::as_str)
366
+ .map(str::trim)
367
+ .filter(|s| !s.is_empty())
368
+ .unwrap_or("127.0.0.1")
369
+ .to_string()
370
+ }
371
+
372
+ pub fn line_webhook_port(&self) -> u16 {
373
+ self.extra
374
+ .get("lineWebhookPort")
375
+ .and_then(|v| {
376
+ v.as_u64()
377
+ .or_else(|| v.as_str().and_then(|s| s.parse().ok()))
378
+ })
379
+ .map(|p| p as u16)
380
+ .unwrap_or(3000)
381
+ }
382
+
383
+ pub fn whatsapp_webhook_host(&self) -> String {
384
+ self.extra
385
+ .get("whatsappWebhookHost")
386
+ .and_then(Value::as_str)
387
+ .map(str::trim)
388
+ .filter(|s| !s.is_empty())
389
+ .unwrap_or("127.0.0.1")
390
+ .to_string()
391
+ }
392
+
393
+ pub fn whatsapp_webhook_port(&self) -> u16 {
394
+ self.extra
395
+ .get("whatsappWebhookPort")
396
+ .and_then(|v| {
397
+ v.as_u64()
398
+ .or_else(|| v.as_str().and_then(|s| s.parse().ok()))
399
+ })
400
+ .map(|p| p as u16)
401
+ .unwrap_or(3001)
402
+ }
403
+
404
+ pub fn bridge_ack_enabled(&self) -> bool {
405
+ self.extra
406
+ .get("enableBridgeAckNotification")
407
+ .and_then(Value::as_bool)
408
+ .unwrap_or(true)
409
+ }
410
+
411
+ pub fn bridge_ack_message(&self) -> String {
412
+ self.extra
413
+ .get("bridgeAckMessage")
414
+ .and_then(Value::as_str)
415
+ .map(str::trim)
416
+ .filter(|s| !s.is_empty())
417
+ .unwrap_or("[Mint Agent] Remote command received, processing...")
418
+ .to_string()
419
+ }
238
420
  }
239
421
 
240
422
  pub fn config_path() -> Result<PathBuf, ConfigError> {
@@ -438,7 +620,10 @@ mod tests {
438
620
 
439
621
  #[test]
440
622
  fn default_config_always_exposes_ollama() {
441
- assert_eq!(MintConfig::default().available_providers(), vec!["ollama"]);
623
+ assert_eq!(
624
+ MintConfig::default().available_providers(),
625
+ vec!["ollama".to_owned()]
626
+ );
442
627
  }
443
628
 
444
629
  #[test]
@@ -454,12 +639,12 @@ mod tests {
454
639
  assert_eq!(
455
640
  config.available_providers(),
456
641
  vec![
457
- "openai",
458
- "openrouter",
459
- "deepseek",
460
- "gemini",
461
- "local_openai",
462
- "ollama"
642
+ "openai".to_owned(),
643
+ "openrouter".to_owned(),
644
+ "deepseek".to_owned(),
645
+ "gemini".to_owned(),
646
+ "local_openai".to_owned(),
647
+ "ollama".to_owned(),
463
648
  ]
464
649
  );
465
650
  }
@@ -509,8 +694,108 @@ mod tests {
509
694
  save_config_to(&path, &initial_config).unwrap();
510
695
 
511
696
  let loaded = load_config_from(&path).unwrap();
512
- assert!(loaded.theme.len() > 0);
697
+ assert!(!loaded.theme.is_empty());
513
698
 
514
699
  let _ = std::fs::remove_file(&path);
515
700
  }
701
+
702
+ #[test]
703
+ fn custom_provider_round_trips() {
704
+ let config = MintConfig {
705
+ custom_providers: vec![CustomProvider {
706
+ id: "myprovider".into(),
707
+ display_name: "My Provider".into(),
708
+ base_url: "http://localhost:20128/v1".into(),
709
+ api_key: "sk-abc".into(),
710
+ models: vec![CustomProviderModel {
711
+ model_id: "llama-3.1-8b".into(),
712
+ display_name: "Llama 3.1 8B".into(),
713
+ }],
714
+ headers: vec![CustomProviderHeader {
715
+ name: "X-Custom".into(),
716
+ value: "test".into(),
717
+ }],
718
+ }],
719
+ ..MintConfig::default()
720
+ };
721
+ let json = serde_json::to_string(&config).unwrap();
722
+ let restored: MintConfig = serde_json::from_str(&json).unwrap();
723
+ assert_eq!(restored, config);
724
+ }
725
+
726
+ #[test]
727
+ fn custom_provider_appears_in_available_providers() {
728
+ let config = MintConfig {
729
+ custom_providers: vec![CustomProvider {
730
+ id: "myprovider".into(),
731
+ base_url: "http://localhost:20128/v1".into(),
732
+ ..CustomProvider::default()
733
+ }],
734
+ ..MintConfig::default()
735
+ };
736
+ assert!(
737
+ config
738
+ .available_providers()
739
+ .contains(&"custom:myprovider".to_owned())
740
+ );
741
+ }
742
+
743
+ #[test]
744
+ fn custom_provider_without_base_url_excluded() {
745
+ let config = MintConfig {
746
+ custom_providers: vec![CustomProvider {
747
+ id: "empty".into(),
748
+ base_url: "".into(),
749
+ ..CustomProvider::default()
750
+ }],
751
+ ..MintConfig::default()
752
+ };
753
+ assert!(
754
+ !config
755
+ .available_providers()
756
+ .contains(&"custom:empty".to_owned())
757
+ );
758
+ }
759
+
760
+ #[test]
761
+ fn active_model_resolves_first_custom_model() {
762
+ let config = MintConfig {
763
+ ai_provider: "custom:myprovider".into(),
764
+ custom_providers: vec![CustomProvider {
765
+ id: "myprovider".into(),
766
+ base_url: "http://localhost:20128/v1".into(),
767
+ models: vec![CustomProviderModel {
768
+ model_id: "llama-3.1-8b".into(),
769
+ display_name: String::new(),
770
+ }],
771
+ ..CustomProvider::default()
772
+ }],
773
+ ..MintConfig::default()
774
+ };
775
+ assert_eq!(config.active_model(), "llama-3.1-8b");
776
+ }
777
+
778
+ #[test]
779
+ fn resolve_custom_provider_returns_none_for_unknown() {
780
+ let config = MintConfig::default();
781
+ assert!(
782
+ config
783
+ .resolve_custom_provider("custom:nonexistent")
784
+ .is_none()
785
+ );
786
+ }
787
+
788
+ #[test]
789
+ fn resolve_custom_provider_strips_prefix() {
790
+ let config = MintConfig {
791
+ custom_providers: vec![CustomProvider {
792
+ id: "myp".into(),
793
+ ..CustomProvider::default()
794
+ }],
795
+ ..MintConfig::default()
796
+ };
797
+ assert!(config.resolve_custom_provider("custom:myp").is_some());
798
+ // Also works without prefix
799
+ assert!(config.resolve_custom_provider("myp").is_some());
800
+ }
516
801
  }