@pheem49/mint 1.5.5 → 1.6.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 (222) hide show
  1. package/.codex +0 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/.github/workflows/ci.yml +45 -0
  4. package/.github/workflows/release.yml +79 -0
  5. package/Cargo.lock +5792 -0
  6. package/Cargo.toml +32 -0
  7. package/README.md +387 -353
  8. package/assets/icon.png +0 -0
  9. package/bin/mint +0 -0
  10. package/crates/mint-cli/Cargo.toml +23 -0
  11. package/crates/mint-cli/src/agent.rs +851 -0
  12. package/crates/mint-cli/src/gmail.rs +216 -0
  13. package/crates/mint-cli/src/image.rs +142 -0
  14. package/crates/mint-cli/src/main.rs +2837 -0
  15. package/crates/mint-cli/src/mcp.rs +63 -0
  16. package/crates/mint-cli/src/onboard.rs +1149 -0
  17. package/crates/mint-cli/src/setup.rs +390 -0
  18. package/crates/mint-cli/src/skills.rs +8 -0
  19. package/crates/mint-cli/src/updater.rs +279 -0
  20. package/crates/mint-core/Cargo.toml +22 -0
  21. package/crates/mint-core/src/agent_loop.rs +94 -0
  22. package/crates/mint-core/src/api_server.rs +991 -0
  23. package/crates/mint-core/src/channels.rs +248 -0
  24. package/crates/mint-core/src/chat.rs +895 -0
  25. package/crates/mint-core/src/code_tools.rs +729 -0
  26. package/crates/mint-core/src/config.rs +368 -0
  27. package/crates/mint-core/src/files.rs +159 -0
  28. package/crates/mint-core/src/knowledge.rs +541 -0
  29. package/crates/mint-core/src/lib.rs +84 -0
  30. package/crates/mint-core/src/mcp.rs +273 -0
  31. package/crates/mint-core/src/memory.rs +673 -0
  32. package/crates/mint-core/src/orchestration.rs +2157 -0
  33. package/crates/mint-core/src/pictures.rs +314 -0
  34. package/crates/mint-core/src/plugins.rs +727 -0
  35. package/crates/mint-core/src/safety.rs +416 -0
  36. package/crates/mint-core/src/semantic.rs +254 -0
  37. package/crates/mint-core/src/shell.rs +317 -0
  38. package/crates/mint-core/src/skills.rs +71 -0
  39. package/crates/mint-core/src/symbols.rs +157 -0
  40. package/crates/mint-core/src/tasks.rs +308 -0
  41. package/crates/mint-core/src/tts.rs +92 -0
  42. package/crates/mint-core/src/weather.rs +93 -0
  43. package/crates/mint-core/src/web_search.rs +200 -0
  44. package/crates/mint-core/src/workflows.rs +81 -0
  45. package/crates/mint-core/tests/mcp_stdio.rs +45 -0
  46. package/crates/mint-core/tests/memory_persistence.rs +172 -0
  47. package/crates/mint-core/tests/pictures_storage.rs +14 -0
  48. package/crates/mint-core/tests/task_lifecycle.rs +87 -0
  49. package/package.json +35 -99
  50. package/src/bin/index.js +16 -0
  51. package/src/renderer/index-web.html +17 -0
  52. package/src/renderer/index.html +17 -0
  53. package/src/renderer/public/Live2DCubismCore.js +9 -0
  54. package/src/renderer/public/assets/icon.png +0 -0
  55. package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
  56. package/src/renderer/src/App.tsx +33 -0
  57. package/src/renderer/src/calculator.ts +47 -0
  58. package/src/renderer/src/components/ChatPanel.tsx +1598 -0
  59. package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
  60. package/src/renderer/src/components/Live2DStage.tsx +374 -0
  61. package/src/renderer/src/components/MintDashboard.tsx +950 -0
  62. package/src/renderer/src/components/ModelPanel.tsx +154 -0
  63. package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
  64. package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
  65. package/src/renderer/src/components/ScreenPicker.tsx +579 -0
  66. package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
  67. package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
  68. package/src/renderer/src/components/WidgetWindow.tsx +36 -0
  69. package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
  70. package/src/{UI → renderer/src/css}/settings.css +69 -16
  71. package/src/renderer/src/css/spotlight.css +113 -0
  72. package/src/renderer/src/css/styles.css +3722 -0
  73. package/src/renderer/src/css/widget.css +185 -0
  74. package/src/renderer/src/env.d.ts +116 -0
  75. package/src/renderer/src/index.css +379 -0
  76. package/src/renderer/src/main.tsx +13 -0
  77. package/src/renderer/src/tauri.ts +996 -0
  78. package/src/renderer/src-web/App.tsx +25 -0
  79. package/src/renderer/src-web/calculator.ts +47 -0
  80. package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
  81. package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
  82. package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
  83. package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
  84. package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
  85. package/src/renderer/src-web/css/settings.css +1100 -0
  86. package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
  87. package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
  88. package/src/{UI → renderer/src-web/css}/widget.css +2 -2
  89. package/src/renderer/src-web/env.d.ts +107 -0
  90. package/src/renderer/src-web/index.css +379 -0
  91. package/src/renderer/src-web/main.tsx +13 -0
  92. package/src/renderer/src-web/tauri.ts +983 -0
  93. package/tsconfig.json +30 -0
  94. package/vite.config.ts +33 -0
  95. package/vite.config.web.ts +51 -0
  96. package/GUIDE_TH.md +0 -125
  97. package/assets/Agent_Mint.png +0 -0
  98. package/assets/CLI_Screen.png +0 -0
  99. package/assets/Settings.png +0 -0
  100. package/benchmark_ai.js +0 -71
  101. package/install.ps1 +0 -64
  102. package/install.sh +0 -54
  103. package/main.js +0 -139
  104. package/mint-cli-logic.js +0 -3
  105. package/mint-cli.js +0 -410
  106. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
  107. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
  108. package/preload-picker.js +0 -11
  109. package/preload-settings.js +0 -11
  110. package/preload.js +0 -41
  111. package/scripts/install_linux_desktop_entry.js +0 -48
  112. package/src/AI_Brain/Gemini_API.js +0 -813
  113. package/src/AI_Brain/agent_orchestrator.js +0 -73
  114. package/src/AI_Brain/autonomous_brain.js +0 -179
  115. package/src/AI_Brain/behavior_memory.js +0 -135
  116. package/src/AI_Brain/headless_agent.js +0 -143
  117. package/src/AI_Brain/knowledge_base.js +0 -349
  118. package/src/AI_Brain/memory_store.js +0 -662
  119. package/src/AI_Brain/proactive_engine.js +0 -172
  120. package/src/AI_Brain/provider_adapter.js +0 -365
  121. package/src/Automation_Layer/browser_automation.js +0 -149
  122. package/src/Automation_Layer/file_operations.js +0 -286
  123. package/src/Automation_Layer/open_app.js +0 -85
  124. package/src/Automation_Layer/open_website.js +0 -38
  125. package/src/CLI/approval_handler.js +0 -47
  126. package/src/CLI/chat_router.js +0 -247
  127. package/src/CLI/chat_ui.js +0 -1159
  128. package/src/CLI/cli_colors.js +0 -115
  129. package/src/CLI/cli_formatters.js +0 -94
  130. package/src/CLI/code_agent.js +0 -1667
  131. package/src/CLI/code_session_memory.js +0 -62
  132. package/src/CLI/gmail_auth.js +0 -210
  133. package/src/CLI/image_input.js +0 -90
  134. package/src/CLI/intent_detectors.js +0 -181
  135. package/src/CLI/interactive_chat.js +0 -658
  136. package/src/CLI/list_features.js +0 -64
  137. package/src/CLI/onboarding.js +0 -416
  138. package/src/CLI/repo_summarizer.js +0 -282
  139. package/src/CLI/semantic_code_search.js +0 -312
  140. package/src/CLI/skill_manager.js +0 -41
  141. package/src/CLI/slash_command_handler.js +0 -418
  142. package/src/CLI/symbol_indexer.js +0 -231
  143. package/src/CLI/updater.js +0 -230
  144. package/src/CLI/workspace_manager.js +0 -90
  145. package/src/Channels/brave_search_bridge.js +0 -35
  146. package/src/Channels/discord_bridge.js +0 -66
  147. package/src/Channels/google_search_bridge.js +0 -38
  148. package/src/Channels/line_bridge.js +0 -60
  149. package/src/Channels/slack_bridge.js +0 -48
  150. package/src/Channels/telegram_bridge.js +0 -41
  151. package/src/Channels/whatsapp_bridge.js +0 -57
  152. package/src/Command_Parser/parser.js +0 -45
  153. package/src/Plugins/dev_tools.js +0 -41
  154. package/src/Plugins/discord.js +0 -20
  155. package/src/Plugins/docker.js +0 -47
  156. package/src/Plugins/gmail.js +0 -251
  157. package/src/Plugins/google_calendar.js +0 -252
  158. package/src/Plugins/mcp_manager.js +0 -95
  159. package/src/Plugins/notion.js +0 -256
  160. package/src/Plugins/obsidian.js +0 -54
  161. package/src/Plugins/plugin_manager.js +0 -81
  162. package/src/Plugins/spotify.js +0 -173
  163. package/src/Plugins/system_metrics.js +0 -31
  164. package/src/Plugins/system_monitor.js +0 -72
  165. package/src/System/action_executor.js +0 -178
  166. package/src/System/bridge_manager.js +0 -76
  167. package/src/System/chat_history_manager.js +0 -83
  168. package/src/System/config_manager.js +0 -194
  169. package/src/System/custom_workflows.js +0 -163
  170. package/src/System/daemon_manager.js +0 -67
  171. package/src/System/google_tts_urls.js +0 -51
  172. package/src/System/granular_automation.js +0 -157
  173. package/src/System/ipc_handlers.js +0 -332
  174. package/src/System/notifications.js +0 -23
  175. package/src/System/optional_require.js +0 -23
  176. package/src/System/picture_store.js +0 -109
  177. package/src/System/proactive_loop.js +0 -153
  178. package/src/System/safety_manager.js +0 -273
  179. package/src/System/sandbox_runner.js +0 -182
  180. package/src/System/screen_capture.js +0 -175
  181. package/src/System/smart_context.js +0 -227
  182. package/src/System/system_automation.js +0 -162
  183. package/src/System/system_events.js +0 -79
  184. package/src/System/system_info.js +0 -125
  185. package/src/System/task_manager.js +0 -222
  186. package/src/System/tool_registry.js +0 -293
  187. package/src/System/window_manager.js +0 -220
  188. package/src/UI/floating.css +0 -80
  189. package/src/UI/floating.html +0 -17
  190. package/src/UI/floating.js +0 -67
  191. package/src/UI/live2d_manager.js +0 -600
  192. package/src/UI/preload-floating.js +0 -7
  193. package/src/UI/preload-spotlight.js +0 -11
  194. package/src/UI/preload-widget.js +0 -5
  195. package/src/UI/proactive-glow.html +0 -42
  196. package/src/UI/renderer.js +0 -2127
  197. package/src/UI/screenPicker.html +0 -214
  198. package/src/UI/screenPicker.js +0 -262
  199. package/src/UI/settings.html +0 -577
  200. package/src/UI/settings.js +0 -770
  201. package/src/UI/spotlight.html +0 -23
  202. package/src/UI/spotlight.js +0 -185
  203. package/src/UI/widget.html +0 -29
  204. package/src/UI/widget.js +0 -10
  205. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  206. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
  207. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
  208. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
  209. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
  210. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
  211. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
  212. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
  213. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
  214. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
  215. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
  216. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
  217. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
  218. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
  219. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
  220. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
  221. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
  222. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
@@ -0,0 +1,541 @@
1
+ use std::{
2
+ collections::hash_map::DefaultHasher,
3
+ fs,
4
+ hash::{Hash, Hasher},
5
+ path::{Path, PathBuf},
6
+ process::Command,
7
+ };
8
+
9
+ use quick_xml::{Reader, events::Event};
10
+ use rusqlite::{Connection, params, types::Type};
11
+ use serde::Serialize;
12
+ use thiserror::Error;
13
+
14
+ use crate::{Capability, MintConfig, SafetyError, assert_path_capability, memory_path};
15
+
16
+ const MAX_FILE_BYTES: u64 = 10 * 1024 * 1024;
17
+ const CHUNK_CHARACTERS: usize = 1000;
18
+ const CHUNK_OVERLAP: usize = 200;
19
+
20
+ #[derive(Debug, Error)]
21
+ pub enum KnowledgeError {
22
+ #[error(transparent)]
23
+ Safety(#[from] SafetyError),
24
+ #[error("unable to locate Mint knowledge database: {0}")]
25
+ DatabasePath(String),
26
+ #[error("database error: {0}")]
27
+ Database(#[from] rusqlite::Error),
28
+ #[error("unable to read knowledge file {path}: {source}")]
29
+ Read {
30
+ path: PathBuf,
31
+ source: std::io::Error,
32
+ },
33
+ #[error("knowledge file is too large (> 10 MiB): {0}")]
34
+ TooLarge(PathBuf),
35
+ #[error("native text indexing does not support this file type yet: {0}")]
36
+ UnsupportedFileType(PathBuf),
37
+ #[error("unable to extract text from {path}: {message}")]
38
+ Extract { path: PathBuf, message: String },
39
+ #[error("knowledge file does not contain readable text: {0}")]
40
+ Empty(PathBuf),
41
+ }
42
+
43
+ #[derive(Debug, Clone)]
44
+ pub struct KnowledgeStore {
45
+ path: PathBuf,
46
+ }
47
+
48
+ #[derive(Debug, Clone, Serialize)]
49
+ #[serde(rename_all = "camelCase")]
50
+ pub struct KnowledgeSource {
51
+ pub id: i64,
52
+ pub path: String,
53
+ pub name: String,
54
+ pub last_indexed: String,
55
+ }
56
+
57
+ #[derive(Debug, Clone, Serialize)]
58
+ #[serde(rename_all = "camelCase")]
59
+ pub struct KnowledgeHit {
60
+ pub source: String,
61
+ pub text: String,
62
+ pub score: f32,
63
+ }
64
+
65
+ impl KnowledgeStore {
66
+ pub fn open_default() -> Result<Self, KnowledgeError> {
67
+ Ok(Self::open(memory_path().map_err(|error| {
68
+ KnowledgeError::DatabasePath(error.to_string())
69
+ })?))
70
+ }
71
+
72
+ pub fn open(path: impl Into<PathBuf>) -> Self {
73
+ Self { path: path.into() }
74
+ }
75
+
76
+ pub fn index_file(&self, path: &Path, config: &MintConfig) -> Result<usize, KnowledgeError> {
77
+ let path = assert_path_capability(path, Capability::Read, config)?;
78
+ let metadata = fs::metadata(&path).map_err(|source| KnowledgeError::Read {
79
+ path: path.clone(),
80
+ source,
81
+ })?;
82
+ if metadata.len() > MAX_FILE_BYTES {
83
+ return Err(KnowledgeError::TooLarge(path));
84
+ }
85
+ if !supported(&path) {
86
+ return Err(KnowledgeError::UnsupportedFileType(path));
87
+ }
88
+ let content = extract_text(&path)?;
89
+ if content.trim().is_empty() {
90
+ return Err(KnowledgeError::Empty(path));
91
+ }
92
+ let chunks = chunks(&content);
93
+ let connection = self.connection()?;
94
+ let path_text = path.to_string_lossy().to_string();
95
+ let name = path
96
+ .file_name()
97
+ .map(|name| name.to_string_lossy().to_string())
98
+ .unwrap_or_else(|| path_text.clone());
99
+ let hash = content_hash(&content);
100
+ connection.execute(
101
+ "INSERT INTO sources (path, name, hash, last_indexed)
102
+ VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)
103
+ ON CONFLICT(path) DO UPDATE SET
104
+ name = excluded.name,
105
+ hash = excluded.hash,
106
+ last_indexed = CURRENT_TIMESTAMP",
107
+ params![path_text, name, hash],
108
+ )?;
109
+ let source_id: i64 = connection.query_row(
110
+ "SELECT id FROM sources WHERE path = ?1",
111
+ params![path_text],
112
+ |row| row.get(0),
113
+ )?;
114
+ connection.execute(
115
+ "DELETE FROM chunks WHERE source_id = ?1",
116
+ params![source_id],
117
+ )?;
118
+ for chunk in &chunks {
119
+ connection.execute(
120
+ "INSERT INTO chunks (source_id, text, embedding) VALUES (?1, ?2, ?3)",
121
+ params![source_id, chunk, encode_embedding(&embedding(chunk))],
122
+ )?;
123
+ }
124
+ Ok(chunks.len())
125
+ }
126
+
127
+ pub fn list_sources(&self) -> Result<Vec<KnowledgeSource>, KnowledgeError> {
128
+ let connection = self.connection()?;
129
+ let mut statement = connection.prepare(
130
+ "SELECT id, path, name, last_indexed FROM sources ORDER BY last_indexed DESC, id DESC",
131
+ )?;
132
+ statement
133
+ .query_map([], |row| {
134
+ Ok(KnowledgeSource {
135
+ id: row.get(0)?,
136
+ path: row.get(1)?,
137
+ name: row.get(2)?,
138
+ last_indexed: row.get(3)?,
139
+ })
140
+ })?
141
+ .collect::<Result<Vec<_>, _>>()
142
+ .map_err(Into::into)
143
+ }
144
+
145
+ pub fn search(&self, query: &str, limit: usize) -> Result<Vec<KnowledgeHit>, KnowledgeError> {
146
+ let connection = self.connection()?;
147
+ let query_embedding = embedding(query);
148
+ let mut statement = connection.prepare(
149
+ "SELECT sources.name, chunks.text, chunks.embedding
150
+ FROM chunks JOIN sources ON sources.id = chunks.source_id",
151
+ )?;
152
+ let mut hits = statement
153
+ .query_map([], |row| {
154
+ let text: String = row.get(1)?;
155
+ let vector = row
156
+ .get::<_, Option<Vec<u8>>>(2)?
157
+ .map(|raw| {
158
+ decode_embedding(&raw).map_err(|error| {
159
+ rusqlite::Error::FromSqlConversionFailure(2, Type::Blob, error.into())
160
+ })
161
+ })
162
+ .transpose()?
163
+ .unwrap_or_else(|| embedding(&text));
164
+ Ok(KnowledgeHit {
165
+ source: row.get(0)?,
166
+ text,
167
+ score: cosine_similarity(&query_embedding, &vector),
168
+ })
169
+ })?
170
+ .collect::<Result<Vec<_>, _>>()?;
171
+ hits.sort_by(|left, right| right.score.total_cmp(&left.score));
172
+ hits.truncate(limit);
173
+ Ok(hits)
174
+ }
175
+
176
+ fn connection(&self) -> Result<Connection, KnowledgeError> {
177
+ if let Some(directory) = self.path.parent() {
178
+ fs::create_dir_all(directory).map_err(|source| KnowledgeError::Read {
179
+ path: directory.to_path_buf(),
180
+ source,
181
+ })?;
182
+ }
183
+ let connection = Connection::open(&self.path)?;
184
+
185
+ static INITIALIZED_DATABASES: std::sync::LazyLock<
186
+ std::sync::Mutex<std::collections::HashSet<PathBuf>>,
187
+ > = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new()));
188
+
189
+ let needs_init = {
190
+ let mut set = INITIALIZED_DATABASES.lock().unwrap();
191
+ set.insert(self.path.clone())
192
+ };
193
+
194
+ if needs_init {
195
+ connection.execute_batch(
196
+ "PRAGMA journal_mode = WAL;
197
+ PRAGMA synchronous = NORMAL;
198
+ CREATE TABLE IF NOT EXISTS sources (
199
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
200
+ path TEXT UNIQUE,
201
+ name TEXT,
202
+ hash TEXT,
203
+ last_indexed DATETIME DEFAULT CURRENT_TIMESTAMP
204
+ );
205
+ CREATE TABLE IF NOT EXISTS chunks (
206
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
207
+ source_id INTEGER,
208
+ text TEXT,
209
+ embedding BLOB,
210
+ FOREIGN KEY(source_id) REFERENCES sources(id) ON DELETE CASCADE
211
+ );
212
+ CREATE INDEX IF NOT EXISTS idx_chunks_source ON chunks(source_id);",
213
+ )?;
214
+ }
215
+ Ok(connection)
216
+ }
217
+ }
218
+
219
+ pub fn extract_document_text(path: &Path, config: &MintConfig) -> Result<String, KnowledgeError> {
220
+ let path = assert_path_capability(path, Capability::Read, config)?;
221
+ let metadata = fs::metadata(&path).map_err(|source| KnowledgeError::Read {
222
+ path: path.clone(),
223
+ source,
224
+ })?;
225
+ if metadata.len() > MAX_FILE_BYTES {
226
+ return Err(KnowledgeError::TooLarge(path));
227
+ }
228
+ if !supported(&path) {
229
+ return Err(KnowledgeError::UnsupportedFileType(path));
230
+ }
231
+ let content = extract_text(&path)?;
232
+ if content.trim().is_empty() {
233
+ return Err(KnowledgeError::Empty(path));
234
+ }
235
+ Ok(content)
236
+ }
237
+
238
+ fn supported(path: &Path) -> bool {
239
+ path.extension()
240
+ .and_then(|extension| extension.to_str())
241
+ .is_some_and(|extension| {
242
+ matches!(
243
+ extension.to_ascii_lowercase().as_str(),
244
+ "txt"
245
+ | "md"
246
+ | "pdf"
247
+ | "docx"
248
+ | "xlsx"
249
+ | "json"
250
+ | "toml"
251
+ | "yaml"
252
+ | "yml"
253
+ | "rs"
254
+ | "ts"
255
+ | "tsx"
256
+ | "js"
257
+ | "jsx"
258
+ | "py"
259
+ | "html"
260
+ | "css"
261
+ )
262
+ })
263
+ }
264
+
265
+ fn extract_text(path: &Path) -> Result<String, KnowledgeError> {
266
+ match extension(path).as_str() {
267
+ "pdf" => command_text(path, "pdftotext", &["-layout"], Some("-")),
268
+ "docx" => {
269
+ let xml = command_text(path, "unzip", &["-p"], Some("word/document.xml"))?;
270
+ xml_text(&xml, &["w:t", "w:tab", "w:br", "w:p"])
271
+ }
272
+ "xlsx" => extract_xlsx(path),
273
+ _ => fs::read_to_string(path).map_err(|source| KnowledgeError::Read {
274
+ path: path.to_path_buf(),
275
+ source,
276
+ }),
277
+ }
278
+ }
279
+
280
+ fn extract_xlsx(path: &Path) -> Result<String, KnowledgeError> {
281
+ let files = command_text(path, "unzip", &["-Z1"], None)?;
282
+ let mut text = String::new();
283
+ for entry in files.lines().filter(|entry| {
284
+ *entry == "xl/sharedStrings.xml"
285
+ || (entry.starts_with("xl/worksheets/") && entry.ends_with(".xml"))
286
+ }) {
287
+ let xml = command_text(path, "unzip", &["-p"], Some(entry))?;
288
+ let extracted = xml_text(&xml, &["t", "v", "row"])?;
289
+ if !extracted.trim().is_empty() {
290
+ text.push_str(&format!("\nSheet XML: {entry}\n{extracted}\n"));
291
+ }
292
+ }
293
+ Ok(text)
294
+ }
295
+
296
+ fn command_text(
297
+ path: &Path,
298
+ program: &'static str,
299
+ prefix: &[&str],
300
+ suffix: Option<&str>,
301
+ ) -> Result<String, KnowledgeError> {
302
+ let mut command = Command::new(program);
303
+ command.args(prefix).arg(path);
304
+ if let Some(suffix) = suffix {
305
+ command.arg(suffix);
306
+ }
307
+ let output = command.output().map_err(|error| KnowledgeError::Extract {
308
+ path: path.to_path_buf(),
309
+ message: format!("unable to run {program}: {error}"),
310
+ })?;
311
+ if !output.status.success() {
312
+ return Err(KnowledgeError::Extract {
313
+ path: path.to_path_buf(),
314
+ message: format!(
315
+ "{program} exited with {}: {}",
316
+ output.status,
317
+ String::from_utf8_lossy(&output.stderr).trim()
318
+ ),
319
+ });
320
+ }
321
+ Ok(String::from_utf8_lossy(&output.stdout).into_owned())
322
+ }
323
+
324
+ fn xml_text(xml: &str, text_elements: &[&str]) -> Result<String, KnowledgeError> {
325
+ let mut reader = Reader::from_str(xml);
326
+ let mut active = false;
327
+ let mut output = String::new();
328
+ loop {
329
+ match reader.read_event() {
330
+ Ok(Event::Start(element)) => {
331
+ let name = String::from_utf8_lossy(element.name().as_ref()).into_owned();
332
+ active = text_elements.contains(&name.as_str());
333
+ if matches!(name.as_str(), "w:p" | "row") {
334
+ output.push('\n');
335
+ }
336
+ }
337
+ Ok(Event::Empty(element)) => {
338
+ let name = String::from_utf8_lossy(element.name().as_ref()).into_owned();
339
+ if matches!(name.as_str(), "w:tab") {
340
+ output.push('\t');
341
+ } else if matches!(name.as_str(), "w:br") {
342
+ output.push('\n');
343
+ }
344
+ }
345
+ Ok(Event::Text(text)) if active => {
346
+ output.push_str(&text.decode().map_err(|error| KnowledgeError::Extract {
347
+ path: PathBuf::from("<xml>"),
348
+ message: error.to_string(),
349
+ })?);
350
+ output.push(' ');
351
+ }
352
+ Ok(Event::End(_)) => active = false,
353
+ Ok(Event::Eof) => break,
354
+ Err(error) => {
355
+ return Err(KnowledgeError::Extract {
356
+ path: PathBuf::from("<xml>"),
357
+ message: error.to_string(),
358
+ });
359
+ }
360
+ _ => {}
361
+ }
362
+ }
363
+ Ok(output)
364
+ }
365
+
366
+ fn extension(path: &Path) -> String {
367
+ path.extension()
368
+ .and_then(|extension| extension.to_str())
369
+ .unwrap_or_default()
370
+ .to_ascii_lowercase()
371
+ }
372
+
373
+ fn chunks(text: &str) -> Vec<String> {
374
+ let characters = text.chars().collect::<Vec<_>>();
375
+ let mut chunks = Vec::new();
376
+ let mut start = 0;
377
+ while start < characters.len() {
378
+ let end = (start + CHUNK_CHARACTERS).min(characters.len());
379
+ chunks.push(characters[start..end].iter().collect());
380
+ if end == characters.len() {
381
+ break;
382
+ }
383
+ start = end.saturating_sub(CHUNK_OVERLAP);
384
+ }
385
+ chunks
386
+ }
387
+
388
+ fn content_hash(content: &str) -> String {
389
+ let mut hasher = DefaultHasher::new();
390
+ content.hash(&mut hasher);
391
+ format!("{:016x}", hasher.finish())
392
+ }
393
+
394
+ const EMBEDDING_DIMENSIONS: usize = 256;
395
+
396
+ fn embedding(text: &str) -> Vec<f32> {
397
+ let mut vector = vec![0.0; EMBEDDING_DIMENSIONS];
398
+ for token in text
399
+ .split(|character: char| !character.is_alphanumeric())
400
+ .filter(|token| !token.is_empty())
401
+ {
402
+ let mut hasher = DefaultHasher::new();
403
+ let has_upper = token.chars().any(|c| c.is_uppercase());
404
+ if has_upper {
405
+ use std::cell::RefCell;
406
+ thread_local! {
407
+ static LOWERCASE_BUF: RefCell<String> = RefCell::new(String::with_capacity(64));
408
+ }
409
+ LOWERCASE_BUF.with(|buf| {
410
+ let mut buf = buf.borrow_mut();
411
+ buf.clear();
412
+ for c in token.chars() {
413
+ for lc in c.to_lowercase() {
414
+ buf.push(lc);
415
+ }
416
+ }
417
+ use std::hash::Hash;
418
+ buf.hash(&mut hasher);
419
+ });
420
+ } else {
421
+ token.hash(&mut hasher);
422
+ }
423
+ let hash = hasher.finish();
424
+ let index = hash as usize % EMBEDDING_DIMENSIONS;
425
+ vector[index] += if hash & 1 == 0 { 1.0 } else { -1.0 };
426
+ }
427
+ let norm = vector.iter().map(|value| value * value).sum::<f32>().sqrt();
428
+ if norm > 0.0 {
429
+ vector.iter_mut().for_each(|value| *value /= norm);
430
+ }
431
+ vector
432
+ }
433
+
434
+ fn encode_embedding(vector: &[f32]) -> Vec<u8> {
435
+ vector
436
+ .iter()
437
+ .flat_map(|value| value.to_le_bytes())
438
+ .collect()
439
+ }
440
+
441
+ fn decode_embedding(raw: &[u8]) -> Result<Vec<f32>, &'static str> {
442
+ if !raw.len().is_multiple_of(4) {
443
+ return Err("embedding blob length is invalid");
444
+ }
445
+ Ok(raw
446
+ .chunks_exact(4)
447
+ .map(|bytes| f32::from_le_bytes(bytes.try_into().unwrap()))
448
+ .collect())
449
+ }
450
+
451
+ fn cosine_similarity(left: &[f32], right: &[f32]) -> f32 {
452
+ left.iter()
453
+ .zip(right)
454
+ .map(|(left, right)| left * right)
455
+ .sum()
456
+ }
457
+
458
+ #[cfg(test)]
459
+ mod tests {
460
+ use super::*;
461
+
462
+ #[test]
463
+ fn indexes_and_searches_text_files() {
464
+ let root = std::env::temp_dir().join(format!("mint-knowledge-{}", std::process::id()));
465
+ fs::create_dir_all(&root).unwrap();
466
+ let file = root.join("notes.md");
467
+ fs::write(&file, "Mint native knowledge search").unwrap();
468
+ let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
469
+ let config = MintConfig {
470
+ allowed_read_paths: vec![root.clone()],
471
+ blocked_paths: vec![],
472
+ ..MintConfig::default()
473
+ };
474
+ assert_eq!(store.index_file(&file, &config).unwrap(), 1);
475
+ assert_eq!(
476
+ store.search("native knowledge", 5).unwrap()[0].source,
477
+ "notes.md"
478
+ );
479
+ let _ = fs::remove_dir_all(root);
480
+ }
481
+
482
+ #[test]
483
+ fn extracts_docx_xml_text() {
484
+ assert_eq!(
485
+ xml_text(
486
+ "<w:p><w:r><w:t>Hello</w:t></w:r><w:r><w:t>Mint</w:t></w:r></w:p>",
487
+ &["w:t", "w:p"]
488
+ )
489
+ .unwrap()
490
+ .trim(),
491
+ "Hello Mint"
492
+ );
493
+ }
494
+
495
+ #[test]
496
+ fn embedding_search_ranks_related_chunk_first() {
497
+ let root = std::env::temp_dir().join("mint-knowledge-embedding");
498
+ let _ = fs::remove_dir_all(&root);
499
+ fs::create_dir_all(&root).unwrap();
500
+ let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
501
+ let config = MintConfig {
502
+ allowed_read_paths: vec![root.clone()],
503
+ blocked_paths: vec![],
504
+ ..MintConfig::default()
505
+ };
506
+ let rust = root.join("rust.md");
507
+ let cooking = root.join("cooking.md");
508
+ fs::write(&rust, "Rust backend ownership borrowing cargo").unwrap();
509
+ fs::write(&cooking, "Pasta tomato basil kitchen recipe").unwrap();
510
+ store.index_file(&rust, &config).unwrap();
511
+ store.index_file(&cooking, &config).unwrap();
512
+ assert_eq!(
513
+ store.search("cargo rust backend", 1).unwrap()[0].source,
514
+ "rust.md"
515
+ );
516
+ let _ = fs::remove_dir_all(root);
517
+ }
518
+
519
+ #[test]
520
+ fn searches_legacy_chunks_without_stored_embeddings() {
521
+ let root = std::env::temp_dir().join("mint-knowledge-legacy-embedding");
522
+ let _ = fs::remove_dir_all(&root);
523
+ fs::create_dir_all(&root).unwrap();
524
+ let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
525
+ let connection = store.connection().unwrap();
526
+ connection
527
+ .execute(
528
+ "INSERT INTO sources (path, name, hash) VALUES ('legacy', 'legacy.md', 'hash')",
529
+ [],
530
+ )
531
+ .unwrap();
532
+ connection
533
+ .execute(
534
+ "INSERT INTO chunks (source_id, text, embedding) VALUES (1, 'legacy rust backend', NULL)",
535
+ [],
536
+ )
537
+ .unwrap();
538
+ assert_eq!(store.search("rust", 1).unwrap()[0].source, "legacy.md");
539
+ let _ = fs::remove_dir_all(root);
540
+ }
541
+ }
@@ -0,0 +1,84 @@
1
+ #![recursion_limit = "256"]
2
+
3
+ pub mod agent_loop;
4
+ pub mod chat;
5
+ pub mod code_tools;
6
+ pub mod config;
7
+ pub mod files;
8
+ pub mod knowledge;
9
+ pub mod mcp;
10
+ pub mod memory;
11
+ pub mod orchestration;
12
+ pub mod pictures;
13
+ pub mod plugins;
14
+ pub mod safety;
15
+ pub mod semantic;
16
+ pub mod shell;
17
+ pub mod skills;
18
+ pub mod symbols;
19
+ pub mod tasks;
20
+ pub mod tts;
21
+ pub mod weather;
22
+ pub mod web_search;
23
+ pub mod workflows;
24
+
25
+ pub use agent_loop::{AgentActionFuture, AgentLoopError, parse_agent_json, run_agent_loop};
26
+ pub use chat::{
27
+ ChatError, ChatRequest, ChatResponse, send_chat, send_chat_with_fallback, stream_chat,
28
+ stream_chat_with_fallback,
29
+ };
30
+
31
+ pub use code_tools::{
32
+ AppliedCodeEdit, CodeEdit, CodeEditPreview, CodeEditProposal, CodeFile, CodeInspectionError,
33
+ CodePatchHunk, CodePlan, CodeSearchHit, RepositorySummary, apply_code_edits, build_code_patch,
34
+ fetch_github_repo_summary, inspect_code_plan, list_code_files, parse_github_url,
35
+ propose_code_edits, read_code_file, repository_summary, search_code,
36
+ };
37
+ pub use config::{
38
+ ConfigError, MintConfig, config_path, initialize_config, load_config, save_config,
39
+ set_config_value,
40
+ };
41
+ pub use files::{FileOperationError, PathKind, PathMatch, create_folder, find_paths};
42
+ pub use knowledge::{
43
+ KnowledgeError, KnowledgeHit, KnowledgeSource, KnowledgeStore, extract_document_text,
44
+ };
45
+ pub use mcp::{
46
+ McpError, McpServer, add_mcp_server, call_configured_mcp_tool, call_mcp_tool,
47
+ clear_mcp_servers, configured_mcp_servers, list_mcp_servers, remove_mcp_server,
48
+ };
49
+ pub use memory::{
50
+ CHAT_CLI_ID, ChatSession, DEFAULT_CONVERSATION_ID, InteractionMemory, LearnedSkill,
51
+ MemoryError, MemoryStore, WorkspaceSession, memory_path,
52
+ };
53
+ pub use orchestration::{
54
+ AgentApproval, AgentProgress, AgentResult, ApprovalOutcome, OrchestrationError,
55
+ orchestrate_agent_loop, orchestrate_chat, orchestrate_chat_stream,
56
+ orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback,
57
+ };
58
+ pub use pictures::{
59
+ PictureEntry, PictureError, list_saved_pictures, parse_data_uri, save_chat_images,
60
+ save_sent_image,
61
+ };
62
+ pub use plugins::{NativePlugin, PluginError, execute_native_plugin, native_plugins};
63
+ pub use safety::{
64
+ Capability, SafetyError, SafetyTier, ShellClassification, ShellCommandMode,
65
+ assert_path_capability, classify_shell_command, shell_mode_allowed,
66
+ };
67
+ pub use semantic::{
68
+ SemanticChunk, SemanticError, SemanticHit, SemanticIndex, index_semantic_code,
69
+ search_semantic_code,
70
+ };
71
+ pub use shell::{ShellError, ShellOutput, run_shell_command};
72
+ pub use skills::{SkillError, learn_skill, learned_skills_context};
73
+ pub use symbols::{CodeSymbol, SymbolError, SymbolIndex, build_symbol_index};
74
+ pub use tasks::{Task, TaskError, TaskStore, tasks_path};
75
+ pub use tts::{TtsUrl, google_tts_urls};
76
+ pub use weather::{WeatherError, WeatherReport, weather};
77
+ pub use workflows::{WorkflowError, load_workflows, workflows_path};
78
+ pub mod api_server;
79
+ pub use api_server::start_api_server;
80
+ pub mod channels;
81
+ pub use channels::start_channels;
82
+
83
+ pub static HTTP_CLIENT: std::sync::LazyLock<reqwest::Client> =
84
+ std::sync::LazyLock::new(reqwest::Client::new);