@pheem49/mint 1.7.1 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/Cargo.lock +34 -3
  3. package/Cargo.toml +2 -2
  4. package/README.md +40 -0
  5. package/Release_Note.md +95 -0
  6. package/assets/Mint_CLI.png +0 -0
  7. package/crates/mint-cli/Cargo.toml +1 -0
  8. package/crates/mint-cli/src/agent.rs +142 -48
  9. package/crates/mint-cli/src/main.rs +231 -22
  10. package/crates/mint-core/src/api_server.rs +71 -4
  11. package/crates/mint-core/src/browser.rs +401 -0
  12. package/crates/mint-core/src/chat.rs +79 -0
  13. package/crates/mint-core/src/config.rs +69 -1
  14. package/crates/mint-core/src/lib.rs +6 -1
  15. package/crates/mint-core/src/orchestration.rs +137 -1
  16. package/crates/mint-core/src/skills.rs +113 -4
  17. package/crates/mint-core/src/workflows.rs +9 -22
  18. package/package.json +2 -1
  19. package/src/renderer/src/App.tsx +1 -1
  20. package/src/renderer/src/components/ChatPanel.tsx +197 -47
  21. package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
  22. package/src/renderer/src/components/ImageStudioPanel.tsx +18 -4
  23. package/src/renderer/src/components/MintDashboard.tsx +239 -7
  24. package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
  25. package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
  26. package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
  27. package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
  28. package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
  29. package/src/renderer/src/components/Settings/PluginsTab.tsx +632 -0
  30. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
  31. package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
  32. package/src/renderer/src/components/SettingsWindow.tsx +175 -970
  33. package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
  34. package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
  35. package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
  36. package/src/renderer/src/css/image-studio.css +15 -4
  37. package/src/renderer/src/css/settings.css +75 -6
  38. package/src/renderer/src/css/styles.css +419 -49
  39. package/src/renderer/src/css/workflows.css +542 -0
  40. package/src/renderer/src/env.d.ts +2 -0
  41. package/src/renderer/src/index.css +13 -1
  42. package/src/renderer/src/tauri.ts +140 -2
  43. package/src/renderer/src-web/components/ChatPanel.tsx +190 -46
  44. package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
  45. package/src/renderer/src-web/components/ImageStudioPanel.tsx +18 -4
  46. package/src/renderer/src-web/components/MintDashboard.tsx +197 -3
  47. package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
  48. package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
  49. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
  50. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
  51. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
  52. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +632 -0
  53. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
  54. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
  55. package/src/renderer/src-web/components/SettingsWindow.tsx +187 -976
  56. package/src/renderer/src-web/css/image-studio.css +15 -4
  57. package/src/renderer/src-web/css/settings.css +75 -6
  58. package/src/renderer/src-web/css/styles.css +394 -50
  59. package/src/renderer/src-web/env.d.ts +2 -0
  60. package/src/renderer/src-web/index.css +12 -1
  61. package/src/renderer/src-web/tauri.ts +60 -2
  62. package/src-tauri/src/browser.rs +1 -141
  63. package/src-tauri/src/headless.rs +3 -2
  64. package/src-tauri/src/lib.rs +195 -111
  65. package/src-tauri/src/workflows.rs +56 -4
  66. package/src-tauri/tauri.conf.json +1 -1
@@ -134,6 +134,28 @@ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
134
134
  return invoke<RuntimeStatus>('get_runtime_status')
135
135
  }
136
136
 
137
+ export interface DetectedTools {
138
+ docker: boolean
139
+ git: boolean
140
+ gh: boolean
141
+ node: boolean
142
+ }
143
+
144
+ export async function detectSystemTools(): Promise<DetectedTools> {
145
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
146
+ const API_BASE = getApiBase();
147
+ try {
148
+ const res = await fetch(`${API_BASE}/detect-tools`);
149
+ return await res.json();
150
+ } catch (e) {
151
+ console.error("Failed to detect tools from local server:", e);
152
+ return { docker: false, git: false, gh: false, node: false };
153
+ }
154
+ }
155
+ const { invoke } = await import('@tauri-apps/api/core')
156
+ return invoke<DetectedTools>('detect_system_tools')
157
+ }
158
+
137
159
  export async function sendChatMessage(
138
160
  message: string,
139
161
  imageDataUri?: string | null,
@@ -293,6 +315,30 @@ export async function getRecentInteractions(limit = 50, chatId?: string | null):
293
315
  return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
294
316
  }
295
317
 
318
+ export async function saveSystemInteraction(
319
+ chatId: string,
320
+ userText: string,
321
+ provider: string,
322
+ model: string,
323
+ ): Promise<any> {
324
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
325
+ const API_BASE = getApiBase();
326
+ try {
327
+ const res = await fetch(`${API_BASE}/interactions`, {
328
+ method: 'POST',
329
+ headers: { 'Content-Type': 'application/json' },
330
+ body: JSON.stringify({ chatId, userText, provider, model }),
331
+ });
332
+ return await res.json();
333
+ } catch (e) {
334
+ console.error("Failed to save system interaction on local server:", e);
335
+ return { success: false };
336
+ }
337
+ }
338
+ const { invoke } = await import('@tauri-apps/api/core')
339
+ return invoke('save_system_interaction', { chatId, userText, provider, model })
340
+ }
341
+
296
342
  export async function saveInteractionAgentActivity(
297
343
  interactionId: number,
298
344
  activity: AgentProgress[],
@@ -553,12 +599,12 @@ export async function setDefaultImageProvider(provider: string): Promise<boolean
553
599
 
554
600
 
555
601
 
556
- export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
602
+ export async function submitToolApproval(token: string, approved: boolean, answer?: string): Promise<void> {
557
603
  if (typeof window === 'undefined' || !isTauriRuntime()) {
558
604
  return;
559
605
  }
560
606
  const { invoke } = await import('@tauri-apps/api/core')
561
- return invoke('submit_tool_approval', { token, approved })
607
+ return invoke('submit_tool_approval', { token, approved, answer })
562
608
  }
563
609
 
564
610
  export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {
@@ -638,6 +684,7 @@ export function installTauriAdapters() {
638
684
  openFolder: async () => ({ success: false, message: 'Opening local folders is only available in the desktop app.' }),
639
685
  openCustomWorkflows: async () => ({ success: false, message: 'Opening workflow files is only available in the desktop app.' }),
640
686
  reloadCustomWorkflows: async () => ({ success: false, message: 'Reloading workflow files is only available in the desktop app.' }),
687
+ saveCustomWorkflows: async () => ({ success: false, message: 'Saving workflow files is only available in the desktop app.' }),
641
688
  };
642
689
 
643
690
  (window as any).spotlightAPI = {
@@ -733,6 +780,9 @@ export function installTauriAdapters() {
733
780
  openSettings: () => {
734
781
  window.location.hash = '#/settings';
735
782
  },
783
+ openWorkflows: () => {
784
+ window.location.hash = '#/workflows';
785
+ },
736
786
  readClipboard: async () => '',
737
787
  writeClipboard: async () => {},
738
788
  getSystemInfo: async () => {
@@ -877,6 +927,10 @@ export function installTauriAdapters() {
877
927
  const { invoke } = await import('@tauri-apps/api/core')
878
928
  return invoke('reload_custom_workflows')
879
929
  },
930
+ saveCustomWorkflows: async (workflows) => {
931
+ const { invoke } = await import('@tauri-apps/api/core')
932
+ return invoke('save_custom_workflows', { workflows })
933
+ },
880
934
  }
881
935
 
882
936
  window.spotlightAPI = {
@@ -1003,6 +1057,10 @@ export function installTauriAdapters() {
1003
1057
  const { invoke } = await import('@tauri-apps/api/core')
1004
1058
  return invoke('open_window', { kind: 'settings' })
1005
1059
  },
1060
+ openWorkflows: async () => {
1061
+ const { invoke } = await import('@tauri-apps/api/core')
1062
+ return invoke('open_window', { kind: 'workflows' })
1063
+ },
1006
1064
  readClipboard: () => navigator.clipboard.readText(),
1007
1065
  writeClipboard: (text) => navigator.clipboard.writeText(text),
1008
1066
  getSystemInfo: async () => ({ backend: 'rust' }),
@@ -1,141 +1 @@
1
- use futures_util::{SinkExt, StreamExt};
2
- use mint_core::MintConfig;
3
- use serde::Serialize;
4
- use serde_json::{Value, json};
5
- use tokio_tungstenite::{connect_async, tungstenite::Message};
6
-
7
- #[derive(Debug, Clone, Serialize)]
8
- #[serde(rename_all = "camelCase")]
9
- pub struct BrowserTab {
10
- pub id: String,
11
- pub title: String,
12
- pub url: String,
13
- }
14
-
15
- pub async fn list_tabs(config: &MintConfig) -> Result<Vec<BrowserTab>, String> {
16
- Ok(fetch_pages(config)
17
- .await?
18
- .into_iter()
19
- .filter_map(|page| {
20
- Some(BrowserTab {
21
- id: page["id"].as_str()?.to_owned(),
22
- title: page["title"].as_str().unwrap_or_default().to_owned(),
23
- url: page["url"].as_str().unwrap_or_default().to_owned(),
24
- })
25
- })
26
- .collect())
27
- }
28
-
29
- pub async fn navigate(config: &MintConfig, url: &str) -> Result<String, String> {
30
- if !(url.starts_with("https://") || url.starts_with("http://")) {
31
- return Err("browser navigation only supports http and https URLs".into());
32
- }
33
- let response = cdp_call(config, "Page.navigate", json!({ "url": url })).await?;
34
- response["result"]["frameId"]
35
- .as_str()
36
- .map(|_| format!("navigating to {url}"))
37
- .ok_or_else(|| response_error(&response))
38
- }
39
-
40
- pub async fn read_page_text(config: &MintConfig) -> Result<String, String> {
41
- let response = cdp_call(
42
- config,
43
- "Runtime.evaluate",
44
- json!({
45
- "expression": "document.body ? document.body.innerText.substring(0, 12000) : ''",
46
- "returnByValue": true
47
- }),
48
- )
49
- .await?;
50
- response["result"]["result"]["value"]
51
- .as_str()
52
- .map(str::to_owned)
53
- .ok_or_else(|| response_error(&response))
54
- }
55
-
56
- pub async fn click(config: &MintConfig, selector: &str) -> Result<String, String> {
57
- let selector = selector.trim();
58
- if selector.is_empty() || selector.len() > 500 {
59
- return Err("browser selector must contain between 1 and 500 characters".into());
60
- }
61
- let selector = serde_json::to_string(selector).map_err(|error| error.to_string())?;
62
- let response = cdp_call(
63
- config,
64
- "Runtime.evaluate",
65
- json!({
66
- "expression": format!(
67
- "(() => {{ const element = document.querySelector({selector}); if (!element) return 'not-found'; element.click(); return 'clicked'; }})()"
68
- ),
69
- "returnByValue": true
70
- }),
71
- )
72
- .await?;
73
- match response["result"]["result"]["value"].as_str() {
74
- Some("clicked") => Ok("clicked".into()),
75
- Some("not-found") => Err(format!("browser selector not found: {selector}")),
76
- _ => Err(response_error(&response)),
77
- }
78
- }
79
-
80
- async fn cdp_call(config: &MintConfig, method: &str, params: Value) -> Result<Value, String> {
81
- let page = fetch_pages(config)
82
- .await?
83
- .into_iter()
84
- .find(|page| page["type"] == "page")
85
- .ok_or("Chrome DevTools did not report an open browser page")?;
86
- let socket_url = page["webSocketDebuggerUrl"]
87
- .as_str()
88
- .ok_or("Chrome DevTools page does not expose a websocket URL")?;
89
- let (mut socket, _) = connect_async(socket_url)
90
- .await
91
- .map_err(|error| format!("unable to connect to Chrome DevTools websocket: {error}"))?;
92
- socket
93
- .send(Message::Text(
94
- json!({ "id": 1, "method": method, "params": params })
95
- .to_string()
96
- .into(),
97
- ))
98
- .await
99
- .map_err(|error| error.to_string())?;
100
- while let Some(message) = socket.next().await {
101
- let message = message.map_err(|error| error.to_string())?;
102
- let Message::Text(raw) = message else {
103
- continue;
104
- };
105
- let value: Value = serde_json::from_str(&raw).map_err(|error| error.to_string())?;
106
- if value["id"] == 1 {
107
- return Ok(value);
108
- }
109
- }
110
- Err("Chrome DevTools websocket closed before returning a response".into())
111
- }
112
-
113
- async fn fetch_pages(config: &MintConfig) -> Result<Vec<Value>, String> {
114
- let endpoint = config
115
- .extra
116
- .get("browserDebugUrl")
117
- .and_then(Value::as_str)
118
- .unwrap_or("http://127.0.0.1:9222/json/list");
119
- let value: Value = mint_core::HTTP_CLIENT
120
- .clone()
121
- .get(endpoint)
122
- .send()
123
- .await
124
- .map_err(|error| format!("unable to reach Chrome DevTools at {endpoint}: {error}"))?
125
- .error_for_status()
126
- .map_err(|error| error.to_string())?
127
- .json()
128
- .await
129
- .map_err(|error| error.to_string())?;
130
- value
131
- .as_array()
132
- .cloned()
133
- .ok_or_else(|| "Chrome DevTools response was not a page list".into())
134
- }
135
-
136
- fn response_error(response: &Value) -> String {
137
- response["error"]["message"]
138
- .as_str()
139
- .unwrap_or("Chrome DevTools returned an unexpected response")
140
- .to_owned()
141
- }
1
+ // Deleted - Moved browser automation functions to mint-core.
@@ -8,11 +8,11 @@ use serde::Deserialize;
8
8
  use serde_json::{Value, json};
9
9
  use tauri::{AppHandle, Emitter, Manager};
10
10
 
11
- use crate::browser::{click, navigate, read_page_text};
11
+ use mint_core::browser::{click, navigate, read_page_text, type_text};
12
12
 
13
13
  const MAX_STEPS: usize = 20;
14
14
  const SYSTEM_PROMPT: &str = r#"You are Mint's native background task agent. Return only JSON:
15
- {"thought":"short progress note","action":"done|propose_folder|propose_write_file|open_url|browser_read|browser_click|knowledge_search|propose_bash","target":"path, URL, selector, query, command, or final result","data":"optional file content"}
15
+ {"thought":"short progress note","action":"done|propose_folder|propose_write_file|open_url|browser_read|browser_click|browser_type|knowledge_search|propose_bash","target":"path, URL, selector, query, command, or final result","data":"optional file content"}
16
16
  Use only one action per response. Background tasks never mutate the filesystem or execute shell commands. Use propose_folder, propose_write_file, and propose_bash so Mint can record a proposal for explicit user approval."#;
17
17
 
18
18
  #[derive(Debug, Deserialize)]
@@ -177,6 +177,7 @@ async fn execute_action(
177
177
  "open_url" => navigate(config, &action.target).await,
178
178
  "browser_read" => read_page_text(config).await,
179
179
  "browser_click" => click(config, &action.target).await,
180
+ "browser_type" => type_text(config, &action.target, &action.data).await,
180
181
  "knowledge_search" => {
181
182
  let hits = KnowledgeStore::open_default()
182
183
  .map_err(|error| error.to_string())?