@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
@@ -31,11 +31,11 @@ use integrations::{channel_inventory, list_plugins};
31
31
  use mint_core::{
32
32
  AgentApproval, AgentProgress, AppliedCodeEdit, ApprovalOutcome, ChatRequest, ChatResponse,
33
33
  ChatSession, CodeEdit, CodeEditProposal, ImageGenRequest, InteractionMemory, MemoryStore,
34
- MintConfig, PictureEntry, TtsUrl, WeatherReport, apply_code_edits, classify_shell_command,
35
- config_path, google_tts_urls, list_saved_pictures, load_config, load_workflows,
36
- orchestrate_agent_loop, orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback,
37
- propose_code_edits, save_chat_images, save_config, save_workflows, start_channels, weather,
38
- workflows_path,
34
+ MintConfig, PictureEntry, TtsUrl, VideoGenRequest, VideoGenResponse, WeatherReport,
35
+ apply_code_edits, classify_shell_command, config_path, google_tts_urls, list_saved_pictures,
36
+ load_config, load_workflows, orchestrate_agent_loop, orchestrate_chat_stream_with_fallback,
37
+ orchestrate_chat_with_fallback, propose_code_edits, save_chat_images, save_config,
38
+ save_workflows, start_channels, weather, workflows_path,
39
39
  };
40
40
  use plugins::execute_plugin;
41
41
 
@@ -73,6 +73,7 @@ struct RuntimeStatus {
73
73
  backend: &'static str,
74
74
  config_path: String,
75
75
  active_provider: String,
76
+ active_model: String,
76
77
  available_providers: Vec<String>,
77
78
  integrations: Value,
78
79
  }
@@ -119,11 +120,8 @@ fn get_runtime_status() -> Result<RuntimeStatus, String> {
119
120
  .display()
120
121
  .to_string(),
121
122
  active_provider: config.ai_provider.clone(),
122
- available_providers: config
123
- .available_providers()
124
- .into_iter()
125
- .map(str::to_owned)
126
- .collect(),
123
+ active_model: config.active_model().to_string(),
124
+ available_providers: config.available_providers(),
127
125
  integrations: integration_status(&config),
128
126
  })
129
127
  }
@@ -309,6 +307,20 @@ fn update_config(app: AppHandle, config: MintConfig) -> Result<(), String> {
309
307
  Ok(())
310
308
  }
311
309
 
310
+ #[tauri::command]
311
+ fn set_active_model(
312
+ app: AppHandle,
313
+ provider: String,
314
+ model: Option<String>,
315
+ ) -> Result<String, String> {
316
+ let mut config = load_config().map_err(|error| error.to_string())?;
317
+ let display_name = config
318
+ .set_active_model(&provider, model.as_deref())
319
+ .map_err(|error| error.to_string())?;
320
+ let _ = app.emit("settings-changed", &config);
321
+ Ok(display_name)
322
+ }
323
+
312
324
  #[tauri::command]
313
325
  fn inspect_shell_command(command: String) -> mint_core::ShellClassification {
314
326
  classify_shell_command(&command)
@@ -316,7 +328,21 @@ fn inspect_shell_command(command: String) -> mint_core::ShellClassification {
316
328
 
317
329
  #[tauri::command]
318
330
  async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatResponse, String> {
319
- let config = load_config().map_err(|error| error.to_string())?;
331
+ let mut config = load_config().map_err(|error| error.to_string())?;
332
+ if let Some(ref path) = request.workspace_path {
333
+ if !path.trim().is_empty()
334
+ && config
335
+ .extra
336
+ .get("activeWorkspacePath")
337
+ .and_then(Value::as_str)
338
+ != Some(path.as_str())
339
+ {
340
+ config
341
+ .extra
342
+ .insert("activeWorkspacePath".into(), Value::String(path.clone()));
343
+ let _ = save_config(&config);
344
+ }
345
+ }
320
346
  let request = request.with_document_context(&config)?;
321
347
 
322
348
  if request.message.starts_with("/chat ") {
@@ -331,19 +357,27 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
331
357
 
332
358
  let abort_handle = join_handle.abort_handle();
333
359
  if !chat_id_str.is_empty() {
334
- mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
360
+ mint_core::ACTIVE_AGENTS
361
+ .lock()
362
+ .unwrap()
363
+ .insert(chat_id_str.clone(), abort_handle);
335
364
  }
336
365
 
337
366
  let res = join_handle.await;
338
367
 
339
368
  if !chat_id_str.is_empty() {
340
- mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
369
+ mint_core::ACTIVE_AGENTS
370
+ .lock()
371
+ .unwrap()
372
+ .remove(&chat_id_str);
341
373
  }
342
374
 
343
375
  let (response, _) = match res {
344
376
  Ok(Ok(val)) => val,
345
377
  Ok(Err(e)) => return Err(e.to_string()),
346
- Err(e) if e.is_cancelled() => return Err("Chat execution cancelled by user".to_string()),
378
+ Err(e) if e.is_cancelled() => {
379
+ return Err("Chat execution cancelled by user".to_string());
380
+ }
347
381
  Err(e) => return Err(format!("Task panicked: {}", e)),
348
382
  };
349
383
  return Ok(response);
@@ -388,6 +422,7 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
388
422
  let message_clone = request.message.clone();
389
423
  let root_clone = root.clone();
390
424
  let image_data_uri_clone = request.image_data_uri.clone();
425
+ let video_data_uri_clone = request.video_data_uri.clone();
391
426
  let chat_id_clone = request.chat_id.clone();
392
427
  let agent_id_clone = request.agent_id.clone();
393
428
 
@@ -397,6 +432,7 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
397
432
  &message_clone,
398
433
  &root_clone,
399
434
  image_data_uri_clone,
435
+ video_data_uri_clone,
400
436
  chat_id_clone.as_deref(),
401
437
  agent_id_clone.as_deref(),
402
438
  fast_mode,
@@ -409,13 +445,19 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
409
445
 
410
446
  let abort_handle = join_handle.abort_handle();
411
447
  if !chat_id_str.is_empty() {
412
- mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
448
+ mint_core::ACTIVE_AGENTS
449
+ .lock()
450
+ .unwrap()
451
+ .insert(chat_id_str.clone(), abort_handle);
413
452
  }
414
453
 
415
454
  let res = join_handle.await;
416
455
 
417
456
  if !chat_id_str.is_empty() {
418
- mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
457
+ mint_core::ACTIVE_AGENTS
458
+ .lock()
459
+ .unwrap()
460
+ .remove(&chat_id_str);
419
461
  }
420
462
 
421
463
  let res = match res {
@@ -439,7 +481,21 @@ async fn stream_chat_message(
439
481
  request: ChatRequest,
440
482
  on_event: Channel<DesktopStreamEvent>,
441
483
  ) -> Result<ChatResponse, String> {
442
- let config = load_config().map_err(|error| error.to_string())?;
484
+ let mut config = load_config().map_err(|error| error.to_string())?;
485
+ if let Some(ref path) = request.workspace_path {
486
+ if !path.trim().is_empty()
487
+ && config
488
+ .extra
489
+ .get("activeWorkspacePath")
490
+ .and_then(Value::as_str)
491
+ != Some(path.as_str())
492
+ {
493
+ config
494
+ .extra
495
+ .insert("activeWorkspacePath".into(), Value::String(path.clone()));
496
+ let _ = save_config(&config);
497
+ }
498
+ }
443
499
  let request = request.with_document_context(&config)?;
444
500
 
445
501
  if request.message.starts_with("/chat ") {
@@ -458,19 +514,27 @@ async fn stream_chat_message(
458
514
 
459
515
  let abort_handle = join_handle.abort_handle();
460
516
  if !chat_id_str.is_empty() {
461
- mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
517
+ mint_core::ACTIVE_AGENTS
518
+ .lock()
519
+ .unwrap()
520
+ .insert(chat_id_str.clone(), abort_handle);
462
521
  }
463
522
 
464
523
  let res = join_handle.await;
465
524
 
466
525
  if !chat_id_str.is_empty() {
467
- mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
526
+ mint_core::ACTIVE_AGENTS
527
+ .lock()
528
+ .unwrap()
529
+ .remove(&chat_id_str);
468
530
  }
469
531
 
470
532
  let (response, _) = match res {
471
533
  Ok(Ok(val)) => val,
472
534
  Ok(Err(e)) => return Err(e.to_string()),
473
- Err(e) if e.is_cancelled() => return Err("Chat execution cancelled by user".to_string()),
535
+ Err(e) if e.is_cancelled() => {
536
+ return Err("Chat execution cancelled by user".to_string());
537
+ }
474
538
  Err(e) => return Err(format!("Task panicked: {}", e)),
475
539
  };
476
540
  return Ok(response);
@@ -530,6 +594,7 @@ async fn stream_chat_message(
530
594
  let message_clone = request.message.clone();
531
595
  let root_clone = root.clone();
532
596
  let image_data_uri_clone = request.image_data_uri.clone();
597
+ let video_data_uri_clone = request.video_data_uri.clone();
533
598
  let chat_id_clone = request.chat_id.clone();
534
599
  let agent_id_clone = request.agent_id.clone();
535
600
 
@@ -539,6 +604,7 @@ async fn stream_chat_message(
539
604
  &message_clone,
540
605
  &root_clone,
541
606
  image_data_uri_clone,
607
+ video_data_uri_clone,
542
608
  chat_id_clone.as_deref(),
543
609
  agent_id_clone.as_deref(),
544
610
  fast_mode,
@@ -551,13 +617,19 @@ async fn stream_chat_message(
551
617
 
552
618
  let abort_handle = join_handle.abort_handle();
553
619
  if !chat_id_str.is_empty() {
554
- mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
620
+ mint_core::ACTIVE_AGENTS
621
+ .lock()
622
+ .unwrap()
623
+ .insert(chat_id_str.clone(), abort_handle);
555
624
  }
556
625
 
557
626
  let res = join_handle.await;
558
627
 
559
628
  if !chat_id_str.is_empty() {
560
- mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
629
+ mint_core::ACTIVE_AGENTS
630
+ .lock()
631
+ .unwrap()
632
+ .remove(&chat_id_str);
561
633
  }
562
634
 
563
635
  let res = match res {
@@ -600,12 +672,19 @@ fn save_interaction_agent_activity(
600
672
  fn save_system_interaction(
601
673
  chat_id: String,
602
674
  user_text: String,
675
+ ai_text: Option<String>,
603
676
  provider: String,
604
677
  model: String,
605
678
  ) -> Result<i64, String> {
606
679
  MemoryStore::open_default()
607
680
  .and_then(|memory| {
608
- memory.add_interaction_for_chat(&chat_id, &user_text, "", &provider, &model)
681
+ memory.add_interaction_for_chat(
682
+ &chat_id,
683
+ &user_text,
684
+ ai_text.as_deref().unwrap_or(""),
685
+ &provider,
686
+ &model,
687
+ )
609
688
  })
610
689
  .map_err(|error| error.to_string())
611
690
  }
@@ -680,6 +759,9 @@ fn list_learned_skills(workspace_path: Option<String>) -> Result<Vec<LearnedSkil
680
759
 
681
760
  let mut global_skills = Vec::new();
682
761
  if let Some(home) = dirs::home_dir() {
762
+ let global_agents_path = home.join(".gemini").join("config").join("AGENTS.md");
763
+ mint_core::skills::load_agent_rules_file(&global_agents_path, &mut global_skills);
764
+
683
765
  let global_skills_path = home.join(".config").join("mint").join("mint-skills");
684
766
  if !global_skills_path.exists() {
685
767
  let _ = std::fs::create_dir_all(&global_skills_path);
@@ -689,6 +771,12 @@ fn list_learned_skills(workspace_path: Option<String>) -> Result<Vec<LearnedSkil
689
771
 
690
772
  let mut workspace_skills = Vec::new();
691
773
  if let Ok(root) = workspace_root(workspace_path.as_deref()) {
774
+ let workspace_agents_path1 = root.join(".agents").join("AGENTS.md");
775
+ mint_core::skills::load_agent_rules_file(&workspace_agents_path1, &mut workspace_skills);
776
+
777
+ let workspace_agents_path2 = root.join("AGENTS.md");
778
+ mint_core::skills::load_agent_rules_file(&workspace_agents_path2, &mut workspace_skills);
779
+
692
780
  let workspace_skills_path1 = root.join(".agents").join("skills");
693
781
  mint_core::skills::load_skills_from_dir(&workspace_skills_path1, &mut workspace_skills);
694
782
 
@@ -843,6 +931,14 @@ async fn generate_images(request: ImageGenRequest) -> Result<DesktopImageGenResp
843
931
  })
844
932
  }
845
933
 
934
+ #[tauri::command]
935
+ async fn generate_video(request: VideoGenRequest) -> Result<VideoGenResponse, String> {
936
+ let config = load_config().map_err(|error| error.to_string())?;
937
+ mint_core::generate_video(&config, &request)
938
+ .await
939
+ .map_err(|error| error.to_string())
940
+ }
941
+
846
942
  #[tauri::command]
847
943
  fn list_pictures() -> Result<Vec<PictureEntry>, String> {
848
944
  list_saved_pictures().map_err(|error| error.to_string())
@@ -857,6 +953,37 @@ fn save_pictures(
857
953
  save_chat_images(images, source, message).map_err(|error| error.to_string())
858
954
  }
859
955
 
956
+ #[tauri::command]
957
+ fn upload_file(filename: String, data_b64: String) -> Result<String, String> {
958
+ // Determine mime from filename
959
+ let mime = filename
960
+ .rsplit_once('.')
961
+ .map(|(_, ext)| match ext.to_ascii_lowercase().as_str() {
962
+ "mp4" => "video/mp4",
963
+ "webm" => "video/webm",
964
+ "mov" => "video/quicktime",
965
+ "mkv" => "video/x-matroska",
966
+ "avi" => "video/x-msvideo",
967
+ _ => "application/octet-stream",
968
+ })
969
+ .unwrap_or("application/octet-stream");
970
+
971
+ let data_uri = format!("data:{};base64,{}", mime, data_b64);
972
+ match save_chat_images(
973
+ vec![data_uri],
974
+ Some("upload".into()),
975
+ Some("uploaded".into()),
976
+ ) {
977
+ Ok(mut saved) => {
978
+ if let Some(entry) = saved.pop() {
979
+ return Ok(format!("/api/pictures/{}", entry.filename));
980
+ }
981
+ Err("no entry saved".into())
982
+ }
983
+ Err(e) => Err(e.to_string()),
984
+ }
985
+ }
986
+
860
987
  #[tauri::command]
861
988
  fn open_folder(path: String) -> Result<ActionResult, String> {
862
989
  let target_path = PathBuf::from(path.trim());
@@ -1157,8 +1284,8 @@ fn install_tray(app: &AppHandle) -> tauri::Result<()> {
1157
1284
  fn install_shortcuts(app: &AppHandle) -> tauri::Result<()> {
1158
1285
  let main_shortcut = Shortcut::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::Space);
1159
1286
  let spotlight_shortcut = Shortcut::new(Some(Modifiers::ALT), Code::Space);
1160
- let main_handler = main_shortcut.clone();
1161
- let spotlight_handler = spotlight_shortcut.clone();
1287
+ let main_handler = main_shortcut;
1288
+ let spotlight_handler = spotlight_shortcut;
1162
1289
  app.plugin(
1163
1290
  tauri_plugin_global_shortcut::Builder::new()
1164
1291
  .with_handler(move |app, shortcut, event| {
@@ -1217,12 +1344,14 @@ pub fn run() {
1217
1344
  create_workspace_folder,
1218
1345
  delete_workspace_item,
1219
1346
  generate_images,
1347
+ generate_video,
1220
1348
  select_workspace_directory,
1221
1349
  get_config,
1222
1350
  get_updater_status,
1223
1351
  check_for_updates,
1224
1352
  install_available_update,
1225
1353
  update_config,
1354
+ set_active_model,
1226
1355
  inspect_shell_command,
1227
1356
  send_chat_message,
1228
1357
  stream_chat_message,
@@ -1241,6 +1370,7 @@ pub fn run() {
1241
1370
  delete_learned_skill,
1242
1371
  list_pictures,
1243
1372
  save_pictures,
1373
+ upload_file,
1244
1374
  open_folder,
1245
1375
  get_tts_urls,
1246
1376
  get_weather,
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
2
2
 
3
3
  use base64::{Engine as _, engine::general_purpose::STANDARD};
4
4
  use hmac::{Hmac, Mac};
5
- use mint_core::{ChatRequest, load_config, orchestrate_chat};
5
+ use mint_core::load_config;
6
6
  use serde_json::{Value, json};
7
7
  use sha2::Sha256;
8
8
  use tokio::{
@@ -14,10 +14,16 @@ type HmacSha256 = Hmac<Sha256>;
14
14
 
15
15
  pub fn start_webhooks() {
16
16
  tauri::async_runtime::spawn(async {
17
- let _ = serve(3000, Service::Line).await;
17
+ let (host, port) = load_config()
18
+ .map(|c| (c.line_webhook_host(), c.line_webhook_port()))
19
+ .unwrap_or_else(|_| ("127.0.0.1".into(), 3000));
20
+ let _ = serve(&host, port, Service::Line).await;
18
21
  });
19
22
  tauri::async_runtime::spawn(async {
20
- let _ = serve(3001, Service::Whatsapp).await;
23
+ let (host, port) = load_config()
24
+ .map(|c| (c.whatsapp_webhook_host(), c.whatsapp_webhook_port()))
25
+ .unwrap_or_else(|_| ("127.0.0.1".into(), 3001));
26
+ let _ = serve(&host, port, Service::Whatsapp).await;
21
27
  });
22
28
  }
23
29
 
@@ -27,8 +33,9 @@ enum Service {
27
33
  Whatsapp,
28
34
  }
29
35
 
30
- async fn serve(port: u16, service: Service) -> Result<(), String> {
31
- let listener = TcpListener::bind(("127.0.0.1", port))
36
+ async fn serve(host: &str, port: u16, service: Service) -> Result<(), String> {
37
+ let addr = format!("{host}:{port}");
38
+ let listener = TcpListener::bind(&addr)
32
39
  .await
33
40
  .map_err(|error| error.to_string())?;
34
41
  loop {
@@ -78,9 +85,28 @@ async fn handle_line(request: HttpRequest) -> Result<(&'static str, String), Str
78
85
  ) else {
79
86
  continue;
80
87
  };
81
- let answer = answer(text, "Reply concisely for a LINE chat.").await;
82
- let _ = mint_core::HTTP_CLIENT.clone().post("https://api.line.me/v2/bot/message/reply").bearer_auth(&token)
83
- .json(&json!({ "replyToken": reply_token, "messages": [{ "type": "text", "text": answer }] })).send().await;
88
+ let user_id = event["source"]["userId"].as_str().unwrap_or("unknown");
89
+ let formatted_chat_id = format!("line:{user_id}");
90
+ let answer = mint_core::channels::answer_channel(
91
+ text,
92
+ "Reply concisely for a LINE chat.",
93
+ Some(formatted_chat_id),
94
+ )
95
+ .await;
96
+
97
+ let mut messages = Vec::new();
98
+ if config.bridge_ack_enabled() {
99
+ messages.push(json!({ "type": "text", "text": config.bridge_ack_message() }));
100
+ }
101
+ messages.push(json!({ "type": "text", "text": answer }));
102
+
103
+ let _ = mint_core::HTTP_CLIENT
104
+ .clone()
105
+ .post("https://api.line.me/v2/bot/message/reply")
106
+ .bearer_auth(&token)
107
+ .json(&json!({ "replyToken": reply_token, "messages": messages }))
108
+ .send()
109
+ .await;
84
110
  }
85
111
  Ok(("200 OK", "ok".into()))
86
112
  }
@@ -133,35 +159,23 @@ async fn handle_whatsapp(request: HttpRequest) -> Result<(&'static str, String),
133
159
  else {
134
160
  continue;
135
161
  };
136
- let answer = answer(text, "Reply concisely for a WhatsApp chat.").await;
162
+ if config.bridge_ack_enabled() {
163
+ let _ = mint_core::HTTP_CLIENT.clone().post(format!("https://graph.facebook.com/v23.0/{phone_id}/messages")).bearer_auth(&access_token)
164
+ .json(&json!({ "messaging_product": "whatsapp", "to": to, "type": "text", "text": { "body": config.bridge_ack_message() } })).send().await;
165
+ }
166
+ let formatted_chat_id = format!("whatsapp:{to}");
167
+ let answer = mint_core::channels::answer_channel(
168
+ text,
169
+ "Reply concisely for a WhatsApp chat.",
170
+ Some(formatted_chat_id),
171
+ )
172
+ .await;
137
173
  let _ = mint_core::HTTP_CLIENT.clone().post(format!("https://graph.facebook.com/v23.0/{phone_id}/messages")).bearer_auth(&access_token)
138
174
  .json(&json!({ "messaging_product": "whatsapp", "to": to, "type": "text", "text": { "body": answer } })).send().await;
139
175
  }
140
176
  Ok(("200 OK", "ok".into()))
141
177
  }
142
178
 
143
- async fn answer(text: &str, system: &str) -> String {
144
- let Ok(config) = load_config() else {
145
- return "Mint config error".into();
146
- };
147
- orchestrate_chat(
148
- &config,
149
- &ChatRequest {
150
- message: text.into(),
151
- system_instruction: system.into(),
152
- chat_id: None,
153
- image_data_uri: None,
154
- audio_data_uri: None,
155
- document_attachment: None,
156
- workspace_path: None,
157
- agent_id: None,
158
- },
159
- )
160
- .await
161
- .map(|response| response.text)
162
- .unwrap_or_else(|error| format!("Mint error: {error}"))
163
- }
164
-
165
179
  struct HttpRequest {
166
180
  method: String,
167
181
  path: String,
@@ -79,6 +79,7 @@ pub fn start_monitor(app: AppHandle) {
79
79
  chat_id: None,
80
80
  image_data_uri: None,
81
81
  audio_data_uri: None,
82
+ video_data_uri: None,
82
83
  document_attachment: None,
83
84
  workspace_path: None,
84
85
  agent_id: None,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Mint",
4
- "version": "1.8.2",
4
+ "version": "1.10.0",
5
5
  "identifier": "com.pheem49.mint",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run dev:desktop",
@@ -0,0 +1,8 @@
1
+ # Project instructions
2
+
3
+ - Do not use any `superpowers:*` skills in this repository.
4
+ - Do not create or restore files under `docs/superpowers/`.
5
+ - Use the `solodeveling` skill at the start of every task in this repository. Resume `.solodeveling/state.md` when present and let the skill route the work to the smallest relevant lifecycle workflow.
6
+ - Keep one primary agent. Do not use subagents unless the user explicitly requests delegation.
7
+ - If `solodeveling` is unavailable, state that briefly and continue with the same evidence-driven lifecycle rather than blocking routine work.
8
+ - Work directly from the user's request and use only the minimum additional relevant skills.
@@ -0,0 +1,130 @@
1
+ # Changelog
2
+
3
+ All notable changes to Gridgeist are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [1.2.0] - 2026-07-20
9
+
10
+ ### Added
11
+
12
+ - Added a conditional system-contract reference covering semantic color,
13
+ typography, layout, spacing, component grammar, states, media, motion, and
14
+ proportional DESIGN.md interoperability.
15
+ - Added paired English and Thai behavioral evaluation coverage for design-system
16
+ coherence and persistent-artifact boundaries.
17
+
18
+ ### Changed
19
+
20
+ - Strengthened the core workflow and review checklist to reconcile existing
21
+ design-system sources, define reusable component rules, and review color/theming
22
+ and component coherence separately.
23
+ - Clarified that product content alone cannot make an underspecified broad redesign
24
+ brand-derived, and prohibited system definition or implementation edits while its
25
+ direction remains provisional.
26
+ - Made overlapping broad visual-skill ownership an explicit pre-thesis gate instead
27
+ of allowing the companion to be silently ignored or blended.
28
+ - Documented optional DESIGN.md intake and output in the English and Thai usage
29
+ guidance without making it a dependency.
30
+
31
+ ## [1.1.2] - 2026-07-18
32
+
33
+ ### Added
34
+
35
+ - Added compact companion coordination so Gridgeist remains the default owner of
36
+ product and visual direction without requiring optional context, asset, audit, or
37
+ verification capabilities.
38
+ - Added paired English and Thai evaluation scenarios for overlapping art-direction
39
+ skills, internationalization and user preferences, interaction alternatives, and
40
+ bounded performance evidence.
41
+
42
+ ### Changed
43
+
44
+ - Strengthened the review checklist for container resilience, language direction,
45
+ forced colors, unobscured focus, non-drag alternatives, media stability, and
46
+ evidence-bounded performance review.
47
+ - Documented optional companion workflows and role boundaries in the English and
48
+ Thai READMEs without making them installation dependencies.
49
+
50
+ ## [1.1.1] - 2026-07-18
51
+
52
+ ### Added
53
+
54
+ - Added isolated live smoke tests for Codex plugin clean installation, marketplace
55
+ refresh, reinstall, and universal installer updates on release tags and manual CI
56
+ runs.
57
+ - Added a bilingual direction-decision fixture, paired ambiguous/explicit scenarios,
58
+ and preserved raw regression outputs for the new collaboration boundary.
59
+
60
+ ### Changed
61
+
62
+ - Reordered the English and Thai Quickstarts so the Codex plugin and universal
63
+ installer lead, with repository cloning and manual copying kept as fallback.
64
+ - Added a conditional direction-alignment gate for broad, underspecified Create and
65
+ Redesign work without slowing explicit directions, Reviews, or narrow repairs.
66
+
67
+ ## [1.1.0] - 2026-07-16
68
+
69
+ ### Added
70
+
71
+ - Added explicit update instructions for Codex plugins, `npx skills`, Git clones,
72
+ and manual installations in English and Thai documentation.
73
+ - Added dependency-free release validation and GitHub Actions checks for skill,
74
+ plugin, marketplace, documentation, and bilingual evaluation alignment.
75
+
76
+ ### Changed
77
+
78
+ - Reframed Gridgeist around product-native structure so grids may be visible,
79
+ quiet, or invisible according to the established brand.
80
+ - Added explicit adaptation guidance for image-led, playful, technical,
81
+ content-heavy, and transactional interfaces.
82
+ - Added interaction-state inventory, verification evidence, and claim-integrity
83
+ requirements to the core workflow and review checklist.
84
+ - Strengthened behavioral evaluation guidance with repeat runs,
85
+ scenario-level evidence, implementation checks, and trigger boundaries.
86
+ - Made the documented Windows validation command explicitly use UTF-8.
87
+ - Pinned the public marketplace entry to the v1.1.0 release tag.
88
+
89
+ ## [1.0.1] - 2026-07-14
90
+
91
+ ### Fixed
92
+
93
+ - Added a Git-backed Codex marketplace catalog for two-command installation.
94
+ - Moved the installable skill to the standard `skills/gridgeist/` plugin path
95
+ so Codex plugin validation and marketplace installation use the same source.
96
+
97
+ ## [1.0.0] - 2026-07-13
98
+
99
+ Gridgeist's first stable release.
100
+
101
+ ### Added
102
+
103
+ - A reproducible Tracefield case study with the original baseline, exact prompt,
104
+ six-part rubric, verification evidence, and stated limitations.
105
+ - A dedicated case-study page and a field-proof section on the project website.
106
+ - Direct links from the English and Thai documentation to the live Tracefield
107
+ interface and its source repository.
108
+
109
+ ### Changed
110
+
111
+ - Promoted the Codex plugin manifest from `0.1.0` to `1.0.0` to mark the public
112
+ skill interface and its Create, Redesign, and Review workflows as stable.
113
+
114
+ ## [0.1.0] - 2026-07-13
115
+
116
+ ### Added
117
+
118
+ - Initial Gridgeist agent skill with Create, Redesign, and Review workflows.
119
+ - Codex plugin manifest and agent-facing interface metadata.
120
+ - English and Thai documentation and evaluation prompts.
121
+ - Interactive examples and a responsive GitHub Pages website.
122
+
123
+ [Unreleased]: https://github.com/ohmiler/gridgeist/compare/v1.2.0...HEAD
124
+ [1.2.0]: https://github.com/ohmiler/gridgeist/compare/v1.1.2...v1.2.0
125
+ [1.1.2]: https://github.com/ohmiler/gridgeist/compare/v1.1.1...v1.1.2
126
+ [1.1.1]: https://github.com/ohmiler/gridgeist/compare/v1.1.0...v1.1.1
127
+ [1.1.0]: https://github.com/ohmiler/gridgeist/compare/v1.0.1...v1.1.0
128
+ [1.0.1]: https://github.com/ohmiler/gridgeist/compare/v1.0.0...v1.0.1
129
+ [1.0.0]: https://github.com/ohmiler/gridgeist/compare/v0.1.0...v1.0.0
130
+ [0.1.0]: https://github.com/ohmiler/gridgeist/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.