@pheem49/mint 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +285 -140
  4. package/bin/mint +0 -0
  5. package/crates/mint-cli/Cargo.toml +1 -0
  6. package/crates/mint-cli/src/actions.rs +233 -0
  7. package/crates/mint-cli/src/agent.rs +287 -43
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/interactive.rs +2888 -0
  10. package/crates/mint-cli/src/main.rs +336 -2854
  11. package/crates/mint-cli/src/markdown.rs +294 -0
  12. package/crates/mint-cli/src/onboard.rs +44 -1
  13. package/crates/mint-cli/src/updater.rs +48 -6
  14. package/crates/mint-core/Cargo.toml +1 -0
  15. package/crates/mint-core/src/agent_loop.rs +1 -0
  16. package/crates/mint-core/src/api_server.rs +376 -13
  17. package/crates/mint-core/src/browser.rs +629 -101
  18. package/crates/mint-core/src/channels.rs +105 -6
  19. package/crates/mint-core/src/chat.rs +52 -4
  20. package/crates/mint-core/src/code_tools.rs +29 -11
  21. package/crates/mint-core/src/config.rs +118 -0
  22. package/crates/mint-core/src/image_gen.rs +294 -33
  23. package/crates/mint-core/src/lib.rs +5 -2
  24. package/crates/mint-core/src/mcp.rs +119 -6
  25. package/crates/mint-core/src/orchestration.rs +133 -11
  26. package/crates/mint-core/src/pictures.rs +64 -1
  27. package/crates/mint-core/src/skills.rs +82 -6
  28. package/crates/mint-core/src/video_gen.rs +235 -0
  29. package/package.json +8 -2
  30. package/shared-modules/luajit/luajit.json +2 -2
  31. package/skills-lock.json +41 -0
  32. package/src/renderer/index-web.html +1 -1
  33. package/src/renderer/index.html +1 -1
  34. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  35. package/src/renderer/shared/platform.ts +3 -0
  36. package/src/renderer/shared/types.ts +4 -0
  37. package/src/renderer/shared/utils/approval.tsx +86 -7
  38. package/src/renderer/shared/utils/markdown.tsx +217 -88
  39. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  40. package/src/renderer/src/components/ChatPanel.tsx +461 -64
  41. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  42. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  43. package/src/renderer/src/components/MintDashboard.tsx +150 -5
  44. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  45. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  46. package/src/renderer/src/components/Settings/GeneralTab.tsx +762 -634
  47. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  48. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  49. package/src/renderer/src/components/SettingsWindow.tsx +38 -2
  50. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  51. package/src/renderer/src/css/image-studio.css +106 -0
  52. package/src/renderer/src/css/settings.css +158 -0
  53. package/src/renderer/src/css/styles.css +352 -1
  54. package/src/renderer/src/css/veo-studio.css +626 -0
  55. package/src/renderer/src/index.css +1 -0
  56. package/src/renderer/src/tauri.ts +144 -17
  57. package/src/renderer/src-web/components/ChatPanel.tsx +360 -88
  58. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  59. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  60. package/src/renderer/src-web/components/MintDashboard.tsx +137 -4
  61. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  62. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  63. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +762 -629
  64. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  65. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  66. package/src/renderer/src-web/components/SettingsWindow.tsx +35 -2
  67. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  68. package/src/renderer/src-web/css/image-studio.css +106 -0
  69. package/src/renderer/src-web/css/settings.css +158 -0
  70. package/src/renderer/src-web/css/styles.css +353 -1
  71. package/src/renderer/src-web/css/veo-studio.css +650 -0
  72. package/src/renderer/src-web/index.css +1 -0
  73. package/src/renderer/src-web/tauri.ts +135 -16
  74. package/src-tauri/src/lib.rs +114 -8
  75. package/src-tauri/src/webhooks.rs +45 -31
  76. package/src-tauri/src/workflows.rs +1 -0
  77. package/src-tauri/tauri.conf.json +1 -1
  78. package/temp_gridgeist/AGENTS.md +8 -0
  79. package/temp_gridgeist/CHANGELOG.md +130 -0
  80. package/temp_gridgeist/LICENSE +21 -0
  81. package/temp_gridgeist/README.md +323 -0
  82. package/temp_gridgeist/README.th.md +325 -0
  83. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  84. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  85. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  86. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  87. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  88. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  89. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  90. package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
@@ -33,27 +33,27 @@ export function readableAssistantText(text: string): string {
33
33
  function sanitizeLatex(text: string): string {
34
34
  return text
35
35
  // arrows
36
- .replace(/\$\\rightarrow\$|\\\(\\rightarrow\\\)/g, '→')
37
- .replace(/\$\\leftarrow\$|\\\(\\leftarrow\\\)/g, '←')
38
- .replace(/\$\\Rightarrow\$|\\\(\\Rightarrow\\\)/g, '⇒')
39
- .replace(/\$\\Leftarrow\$|\\\(\\Leftarrow\\\)/g, '⇐')
40
- .replace(/\$\\leftrightarrow\$|\\\(\\leftrightarrow\\\)/g, '↔')
36
+ .replace(/\$\\rightarrow\$|\\\(\\rightarrow\\\)|\\rightarrow|\bightarrow\b/g, '→')
37
+ .replace(/\$\\leftarrow\$|\\\(\\leftarrow\\\)|\\leftarrow|\beftarrow\b/g, '←')
38
+ .replace(/\$\\Rightarrow\$|\\\(\\Rightarrow\\\)|\\Rightarrow|\begRightarrow\b/g, '⇒')
39
+ .replace(/\$\\Leftarrow\$|\\\(\\Leftarrow\\\)|\\Leftarrow/g, '⇐')
40
+ .replace(/\$\\leftrightarrow\$|\\\(\\leftrightarrow\\\)|\\leftrightarrow/g, '↔')
41
41
  // comparison
42
- .replace(/\$\\leq\$|\\\(\\leq\\\)/g, '≤')
43
- .replace(/\$\\geq\$|\\\(\\geq\\\)/g, '≥')
44
- .replace(/\$\\neq\$|\\\(\\neq\\\)/g, '≠')
42
+ .replace(/\$\\leq\$|\\\(\\leq\\\)|\\leq/g, '≤')
43
+ .replace(/\$\\geq\$|\\\(\\geq\\\)|\\geq/g, '≥')
44
+ .replace(/\$\\neq\$|\\\(\\neq\\\)|\\neq/g, '≠')
45
45
  .replace(/\$\\approx\$|\\\(\\approx\\\)/g, '≈')
46
46
  // math
47
- .replace(/\$\\times\$|\\\(\\times\\\)/g, '×')
48
- .replace(/\$\\div\$|\\\(\\div\\\)/g, '÷')
47
+ .replace(/\$\\times\$|\\\(\\times\\\)|\\times/g, '×')
48
+ .replace(/\$\\div\$|\\\(\\div\\\)|\\div/g, '÷')
49
49
  .replace(/\$\\pm\$|\\\(\\pm\\\)/g, '±')
50
- .replace(/\$\\infty\$|\\\(\\infty\\\)/g, '∞')
50
+ .replace(/\$\\infty\$|\\\(\\infty\\\)|\\infty/g, '∞')
51
51
  // other common symbols
52
- .replace(/\$\\cdot\$|\\\(\\cdot\\\)/g, '·')
53
- .replace(/\$\\in\$|\\\(\\in\\\)/g, '∈')
54
- .replace(/\$\\subset\$|\\\(\\subset\\\)/g, '⊂')
55
- .replace(/\$\\cup\$|\\\(\\cup\\\)/g, '∪')
56
- .replace(/\$\\cap\$|\\\(\\cap\\\)/g, '∩')
52
+ .replace(/\$\\cdot\$|\\\(\\cdot\\\)|\\cdot/g, '·')
53
+ .replace(/\$\\in\$|\\\(\\in\\\)|\\in\b/g, '∈')
54
+ .replace(/\$\\subset\$|\\\(\\subset\\\)|\\subset/g, '⊂')
55
+ .replace(/\$\\cup\$|\\\(\\cup\\\)|\\cup\b/g, '∪')
56
+ .replace(/\$\\cap\$|\\\(\\cap\\\)|\\cap\b/g, '∩')
57
57
  // strip remaining inline math delimiters $...$ and \(...\) that weren't matched
58
58
  .replace(/\$([^$\n]{1,60})\$/g, '$1')
59
59
  .replace(/\\\(([^)]{1,60})\\\)/g, '$1')
@@ -85,11 +85,32 @@ export function renderSpeakerIcon(isSpeaking: boolean): ReactNode {
85
85
  )
86
86
  }
87
87
 
88
+ export function renderCopyIcon(isCopied: boolean): ReactNode {
89
+ if (isCopied) {
90
+ return (
91
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block', color: 'var(--accent, #10b981)' }}>
92
+ <polyline points="20 6 9 17 4 12" />
93
+ </svg>
94
+ )
95
+ }
96
+ return (
97
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block' }}>
98
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
99
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
100
+ </svg>
101
+ )
102
+ }
103
+
104
+
105
+
88
106
  export function renderFormattedMessage(text: string): ReactNode {
89
107
  const displayText = readableAssistantText(text)
90
108
  if (!displayText) return null
91
109
 
92
- const formatInline = (str: string) => {
110
+ const formatInline = (str: string): ReactNode => {
111
+ if (!str) return null
112
+
113
+ // Split code backticks first
93
114
  const codeParts = str.split(/`([\s\S]*?)`/g)
94
115
  return codeParts.map((codePart, codeIndex) => {
95
116
  if (codeIndex % 2 === 1) {
@@ -99,27 +120,92 @@ export function renderFormattedMessage(text: string): ReactNode {
99
120
  </code>
100
121
  )
101
122
  }
102
- const boldParts = codePart.split(/\*\*([\s\S]*?)\*\*/g)
103
- return boldParts.map((boldPart, boldIndex) => {
104
- if (boldIndex % 2 === 1) {
105
- return (
106
- <strong key={`bold-${boldIndex}`} className="chat-bold-highlight">
107
- {boldPart}
108
- </strong>
109
- )
110
- }
111
- const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
112
- return mentionParts.map((mentionPart, mentionIndex) => {
113
- if (mentionIndex % 2 === 1) {
123
+
124
+ // Match markdown links [label](url)
125
+ const linkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s\)]+)\)/g
126
+ const linkMatches: Array<{ index: number; length: number; label: string; url: string }> = []
127
+ let match: RegExpExecArray | null
128
+ while ((match = linkRegex.exec(codePart)) !== null) {
129
+ linkMatches.push({
130
+ index: match.index,
131
+ length: match[0].length,
132
+ label: match[1],
133
+ url: match[2],
134
+ })
135
+ }
136
+
137
+ const renderTextAndFormatting = (subStr: string, keyPrefix: string): ReactNode => {
138
+ // Process bold **text**
139
+ const boldParts = subStr.split(/\*\*([\s\S]*?)\*\*/g)
140
+ return boldParts.map((boldPart, boldIndex) => {
141
+ if (boldIndex % 2 === 1) {
114
142
  return (
115
- <span key={`mention-${mentionIndex}`} className="chat-mention">
116
- {mentionPart}
117
- </span>
143
+ <strong key={`${keyPrefix}-bold-${boldIndex}`} className="chat-bold-highlight">
144
+ {boldPart}
145
+ </strong>
118
146
  )
119
147
  }
120
- return mentionPart
148
+ // Process mentions (@...)
149
+ const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
150
+ return mentionParts.map((mentionPart, mentionIndex) => {
151
+ if (mentionIndex % 2 === 1) {
152
+ return (
153
+ <span key={`${keyPrefix}-mention-${mentionIndex}`} className="chat-mention">
154
+ {mentionPart}
155
+ </span>
156
+ )
157
+ }
158
+ // Process raw URLs (https://... or http://...)
159
+ const urlRegex = /(https?:\/\/[^\s<>\(\)]+)/g
160
+ const urlParts = mentionPart.split(urlRegex)
161
+ return urlParts.map((urlPart, urlIndex) => {
162
+ if (urlIndex % 2 === 1) {
163
+ return (
164
+ <a
165
+ key={`${keyPrefix}-url-${urlIndex}`}
166
+ href={urlPart}
167
+ target="_blank"
168
+ rel="noopener noreferrer"
169
+ className="chat-link"
170
+ >
171
+ {urlPart}
172
+ </a>
173
+ )
174
+ }
175
+ return urlPart
176
+ })
177
+ })
121
178
  })
179
+ }
180
+
181
+ if (linkMatches.length === 0) {
182
+ return renderTextAndFormatting(codePart, `c-${codeIndex}`)
183
+ }
184
+
185
+ const linkElements: ReactNode[] = []
186
+ let lastIdx = 0
187
+ linkMatches.forEach((lMatch, lIdx) => {
188
+ if (lMatch.index > lastIdx) {
189
+ linkElements.push(renderTextAndFormatting(codePart.slice(lastIdx, lMatch.index), `c-${codeIndex}-pre-${lIdx}`))
190
+ }
191
+ linkElements.push(
192
+ <a
193
+ key={`link-${codeIndex}-${lIdx}`}
194
+ href={lMatch.url}
195
+ target="_blank"
196
+ rel="noopener noreferrer"
197
+ className="chat-link"
198
+ >
199
+ {renderTextAndFormatting(lMatch.label, `c-${codeIndex}-label-${lIdx}`)}
200
+ </a>
201
+ )
202
+ lastIdx = lMatch.index + lMatch.length
122
203
  })
204
+ if (lastIdx < codePart.length) {
205
+ linkElements.push(renderTextAndFormatting(codePart.slice(lastIdx), `c-${codeIndex}-post`))
206
+ }
207
+
208
+ return linkElements
123
209
  })
124
210
  }
125
211
 
@@ -231,7 +317,7 @@ export function renderFormattedMessage(text: string): ReactNode {
231
317
  }
232
318
 
233
319
  const lines = displayText.split('\n')
234
- const items: ReactNode[] = []
320
+ const blocks: ReactNode[] = []
235
321
 
236
322
  let inCodeBlock = false
237
323
  let codeBlockLang = ''
@@ -242,22 +328,40 @@ export function renderFormattedMessage(text: string): ReactNode {
242
328
 
243
329
  const flushTable = (index: number) => {
244
330
  if (tableLines.length > 0) {
245
- items.push(renderHtmlTable(tableLines, `table-${index}`))
331
+ blocks.push(renderHtmlTable(tableLines, `table-${index}`))
246
332
  tableLines = []
247
333
  inTable = false
248
334
  }
249
335
  }
250
336
 
337
+ let currentParagraphLines: string[] = []
338
+
339
+ const flushParagraph = (index: number) => {
340
+ if (currentParagraphLines.length > 0) {
341
+ const paragraphText = currentParagraphLines.join('\n')
342
+ if (paragraphText.trim()) {
343
+ blocks.push(
344
+ <div key={`para-${index}`} className="chat-paragraph">
345
+ {formatInline(paragraphText)}
346
+ </div>
347
+ )
348
+ }
349
+ currentParagraphLines = []
350
+ }
351
+ }
352
+
251
353
  for (let i = 0; i < lines.length; i++) {
252
354
  const line = lines[i]
355
+ const trimmedLine = line.trim()
356
+
357
+ // Code block toggle
358
+ if (trimmedLine.startsWith('```')) {
359
+ if (inTable) flushTable(i)
360
+ flushParagraph(i)
253
361
 
254
- if (line.trim().startsWith('```')) {
255
- if (inTable) {
256
- flushTable(i)
257
- }
258
362
  if (inCodeBlock) {
259
363
  const codeText = codeBlockLines.join('\n')
260
- items.push(
364
+ blocks.push(
261
365
  <ChatCodeBlock
262
366
  key={`code-block-${i}`}
263
367
  code={codeText}
@@ -268,7 +372,7 @@ export function renderFormattedMessage(text: string): ReactNode {
268
372
  codeBlockLines = []
269
373
  } else {
270
374
  inCodeBlock = true
271
- codeBlockLang = line.trim().slice(3).trim() || 'plaintext'
375
+ codeBlockLang = trimmedLine.slice(3).trim() || 'plaintext'
272
376
  }
273
377
  continue
274
378
  }
@@ -278,63 +382,87 @@ export function renderFormattedMessage(text: string): ReactNode {
278
382
  continue
279
383
  }
280
384
 
385
+ // Markdown Table
281
386
  if (isTableLine(line)) {
387
+ flushParagraph(i)
282
388
  inTable = true
283
389
  tableLines.push(line)
284
- } else {
285
- if (inTable) {
286
- flushTable(i)
287
- }
390
+ continue
391
+ } else if (inTable) {
392
+ flushTable(i)
393
+ }
288
394
 
289
- const headerMatch = line.match(/^(#{1,6})\s+(.*)$/)
290
- if (headerMatch) {
291
- const level = headerMatch[1].length
292
- const content = headerMatch[2]
293
-
294
- const style = {
295
- fontWeight: 'bold',
296
- display: 'block',
297
- marginTop: level === 1 ? '16px' : level === 2 ? '14px' : '10px',
298
- marginBottom: '6px',
299
- fontSize: level === 1 ? '1.25em' : level === 2 ? '1.15em' : '1.05em',
300
- color: 'var(--text-main)',
301
- }
395
+ // Empty line -> flush paragraph (creates paragraph gap)
396
+ if (!trimmedLine) {
397
+ flushParagraph(i)
398
+ continue
399
+ }
302
400
 
303
- items.push(
304
- <span key={`line-${i}`} style={style}>
305
- {formatInline(content)}
306
- </span>
307
- )
308
- } else {
309
- const listMatch = line.match(/^(\s*)([-*+])\s+(.*)$/)
310
- if (listMatch) {
311
- const indent = listMatch[1]
312
- const content = listMatch[3]
313
- items.push(
314
- <Fragment key={`line-${i}`}>
315
- {indent}• {formatInline(content)}
316
- {i < lines.length - 1 && '\n'}
317
- </Fragment>
318
- )
319
- } else {
320
- items.push(
321
- <Fragment key={`line-${i}`}>
322
- {formatInline(line)}
323
- {i < lines.length - 1 && '\n'}
324
- </Fragment>
325
- )
326
- }
327
- }
401
+ // Markdown Headers (# Header)
402
+ const headerMatch = line.match(/^(#{1,6})\s+(.*)$/)
403
+ if (headerMatch) {
404
+ flushParagraph(i)
405
+ const level = Math.min(headerMatch[1].length, 6)
406
+ const content = headerMatch[2]
407
+ blocks.push(
408
+ <div key={`header-${i}`} className={`chat-heading chat-heading-${level}`}>
409
+ {formatInline(content)}
410
+ </div>
411
+ )
412
+ continue
413
+ }
414
+
415
+ // Numbered list items: "1. ", "2) ", "(1) ", etc.
416
+ const orderedMatch = line.match(/^(\s*)(?:(\d+)[\.\)]|[\(\[](\d+)[\)\]])\s+(.*)$/)
417
+ if (orderedMatch) {
418
+ flushParagraph(i)
419
+ const num = orderedMatch[2] || orderedMatch[3]
420
+ const content = orderedMatch[4]
421
+ blocks.push(
422
+ <div key={`ordered-${i}`} className="chat-list-item chat-ordered-item">
423
+ <span className="chat-list-number">{num}.</span>
424
+ <span className="chat-list-text">{formatInline(content)}</span>
425
+ </div>
426
+ )
427
+ continue
428
+ }
429
+
430
+ // Bullet list items: "- ", "* ", "+ ", "• "
431
+ const bulletMatch = line.match(/^(\s*)([-*+•])\s+(.*)$/)
432
+ if (bulletMatch) {
433
+ flushParagraph(i)
434
+ const content = bulletMatch[3]
435
+ blocks.push(
436
+ <div key={`bullet-${i}`} className="chat-list-item chat-bullet-item">
437
+ <span className="chat-list-bullet">•</span>
438
+ <span className="chat-list-text">{formatInline(content)}</span>
439
+ </div>
440
+ )
441
+ continue
328
442
  }
329
- }
330
443
 
331
- if (inTable) {
332
- flushTable(lines.length)
444
+ // Emoji header / Section title (e.g. "🚀 ขั้นตอนเริ่มต้นสำหรับคุณภีม:", "⚠️ ข้อควรระวังจากมัน (สำคัญมาก!):")
445
+ const isEmojiHeader = /^(?:[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]).*:$/u.test(trimmedLine)
446
+ if (isEmojiHeader) {
447
+ flushParagraph(i)
448
+ blocks.push(
449
+ <div key={`section-${i}`} className="chat-heading chat-heading-3 chat-section-title">
450
+ {formatInline(line)}
451
+ </div>
452
+ )
453
+ continue
454
+ }
455
+
456
+ // Regular text line -> append to current paragraph
457
+ currentParagraphLines.push(line)
333
458
  }
334
459
 
460
+ if (inTable) flushTable(lines.length)
461
+ flushParagraph(lines.length)
462
+
335
463
  if (inCodeBlock && codeBlockLines.length > 0) {
336
464
  const codeText = codeBlockLines.join('\n')
337
- items.push(
465
+ blocks.push(
338
466
  <ChatCodeBlock
339
467
  key={`code-block-end`}
340
468
  code={codeText}
@@ -343,5 +471,6 @@ export function renderFormattedMessage(text: string): ReactNode {
343
471
  )
344
472
  }
345
473
 
346
- return <>{items}</>
474
+ return <div className="chat-formatted-body">{blocks}</div>
347
475
  }
476
+
@@ -0,0 +1,239 @@
1
+ import { SLASH_COMMANDS } from '../constants/slashCommands'
2
+
3
+ export interface SlashCommandExecutionResult {
4
+ handled: boolean
5
+ action?:
6
+ | 'clear_chat'
7
+ | 'toggle_fast'
8
+ | 'toggle_multi_agent'
9
+ | 'set_agent_mode'
10
+ | 'set_provider_model'
11
+ | 'change_workspace'
12
+ | 'open_image_picker'
13
+ | 'paste_image'
14
+ | 'open_plugins'
15
+ | 'generate_veo'
16
+ | 'exit_session'
17
+ | 'system_message'
18
+ payload?: any
19
+ systemText?: string
20
+ }
21
+
22
+ export function parseSlashCommand(input: string): { command: string; rest: string } | null {
23
+ const trimmed = input.trim()
24
+ if (!trimmed.startsWith('/')) return null
25
+ const spaceIndex = trimmed.search(/\s/)
26
+ if (spaceIndex === -1) {
27
+ return { command: trimmed.toLowerCase(), rest: '' }
28
+ }
29
+ return {
30
+ command: trimmed.slice(0, spaceIndex).toLowerCase(),
31
+ rest: trimmed.slice(spaceIndex).trim(),
32
+ }
33
+ }
34
+
35
+ export function executeSlashCommand(
36
+ input: string,
37
+ context: {
38
+ activeProvider?: string
39
+ activeModel?: string
40
+ availableProviders?: string[]
41
+ workspacePath?: string
42
+ interactionsCount?: number
43
+ fastMode?: boolean
44
+ multiAgent?: boolean
45
+ } = {}
46
+ ): SlashCommandExecutionResult {
47
+ const parsed = parseSlashCommand(input)
48
+ if (!parsed) return { handled: false }
49
+
50
+ const { command, rest } = parsed
51
+
52
+ switch (command) {
53
+ case '/help': {
54
+ let helpText = `### ⚡ Mint Interactive Slash Commands\n\n`
55
+ helpText += `| Command | Description | Usage |\n`
56
+ helpText += `| :--- | :--- | :--- |\n`
57
+ SLASH_COMMANDS.forEach((c) => {
58
+ helpText += `| \`${c.command}\` | ${c.description} | \`${c.usage || c.command}\` |\n`
59
+ })
60
+ helpText += `\n*Tip: You can also use \`@\` in the chat bar to reference workspace files!*`
61
+ return {
62
+ handled: true,
63
+ action: 'system_message',
64
+ systemText: helpText,
65
+ }
66
+ }
67
+
68
+ case '/fast': {
69
+ const mode = rest.toLowerCase()
70
+ const targetState = mode === 'on' ? true : mode === 'off' ? false : !(context.fastMode ?? false)
71
+ return {
72
+ handled: true,
73
+ action: 'toggle_fast',
74
+ payload: { enabled: targetState },
75
+ systemText: `⚡ Fast Mode set to **${targetState ? 'ON (Thinking hidden)' : 'OFF (Thinking visible)'}**.`,
76
+ }
77
+ }
78
+
79
+ case '/code': {
80
+ return {
81
+ handled: true,
82
+ action: 'set_agent_mode',
83
+ payload: { prompt: rest, agentMode: true },
84
+ }
85
+ }
86
+
87
+ case '/multi-agent': {
88
+ const mode = rest.toLowerCase()
89
+ const targetState = mode === 'on' ? true : mode === 'off' ? false : !(context.multiAgent ?? false)
90
+ return {
91
+ handled: true,
92
+ action: 'toggle_multi_agent',
93
+ payload: { enabled: targetState },
94
+ systemText: `🤖 Multi-Agent Collaboration set to **${targetState ? 'ON' : 'OFF'}**.`,
95
+ }
96
+ }
97
+
98
+ case '/cd': {
99
+ return {
100
+ handled: true,
101
+ action: 'change_workspace',
102
+ payload: { path: rest },
103
+ }
104
+ }
105
+
106
+ case '/models': {
107
+ if (rest) {
108
+ return {
109
+ handled: true,
110
+ action: 'set_provider_model',
111
+ payload: { target: rest },
112
+ systemText: `🔄 Switching provider/model to \`${rest}\`...`,
113
+ }
114
+ }
115
+ const providers = context.availableProviders || []
116
+ let text = `### 🤖 Available Providers & Current Model\n\n`
117
+ text += `- **Active Provider:** \`${context.activeProvider || 'None'}\`\n`
118
+ text += `- **Active Model:** \`${context.activeModel || 'Default'}\`\n\n`
119
+ text += `**Available Providers:** ${providers.map((p) => `\`${p}\``).join(', ') || 'None'}\n\n`
120
+ text += `*Use \`/models <provider_name>\` to switch active provider.*`
121
+ return {
122
+ handled: true,
123
+ action: 'system_message',
124
+ systemText: text,
125
+ }
126
+ }
127
+
128
+ case '/image-provider': {
129
+ if (rest) {
130
+ return {
131
+ handled: true,
132
+ action: 'system_message',
133
+ systemText: `🎨 Image generation provider set to \`${rest}\`.`,
134
+ }
135
+ }
136
+ return {
137
+ handled: true,
138
+ action: 'system_message',
139
+ systemText: `🎨 **Image Gen Providers:** Gemini Imagen 3, OpenAI DALL-E 3, Flux.1, HuggingFace.\nSwitch via Settings > Media Providers.`,
140
+ }
141
+ }
142
+
143
+ case '/video-provider': {
144
+ if (rest) {
145
+ return {
146
+ handled: true,
147
+ action: 'system_message',
148
+ systemText: `🎬 Video generation provider set to \`${rest}\`.`,
149
+ }
150
+ }
151
+ return {
152
+ handled: true,
153
+ action: 'system_message',
154
+ systemText: `🎬 **Video Gen Providers:** Google Veo 2.0, Runway Gen-3.\nSwitch via Settings > Media Providers.`,
155
+ }
156
+ }
157
+
158
+ case '/veo': {
159
+ if (!rest) {
160
+ return {
161
+ handled: true,
162
+ action: 'system_message',
163
+ systemText: `⚠️ Please specify a prompt for Veo video generation. Usage: \`/veo <prompt>\``,
164
+ }
165
+ }
166
+ return {
167
+ handled: true,
168
+ action: 'generate_veo',
169
+ payload: { prompt: rest },
170
+ }
171
+ }
172
+
173
+ case '/image': {
174
+ return {
175
+ handled: true,
176
+ action: 'open_image_picker',
177
+ payload: { pathOrPrompt: rest },
178
+ }
179
+ }
180
+
181
+ case '/paste': {
182
+ return {
183
+ handled: true,
184
+ action: 'paste_image',
185
+ payload: { prompt: rest },
186
+ }
187
+ }
188
+
189
+ case '/mcp': {
190
+ return {
191
+ handled: true,
192
+ action: 'system_message',
193
+ systemText: `🔌 **MCP Servers Status**\n\n- Active MCP plugins configured in Settings > Plugins.\n- Open Settings > Plugins tab to add or configure Model Context Protocol tools.`,
194
+ }
195
+ }
196
+
197
+ case '/plugins': {
198
+ return {
199
+ handled: true,
200
+ action: 'open_plugins',
201
+ }
202
+ }
203
+
204
+ case '/stats': {
205
+ let statsText = `### 📊 Mint Session Statistics\n\n`
206
+ statsText += `- **Active Workspace:** \`${context.workspacePath || 'Global'}\`\n`
207
+ statsText += `- **Current Provider:** \`${context.activeProvider || 'Unknown'}\`\n`
208
+ statsText += `- **Current Model:** \`${context.activeModel || 'Default'}\`\n`
209
+ statsText += `- **Total Interactions:** \`${context.interactionsCount ?? 0}\`\n`
210
+ statsText += `- **Fast Mode:** \`${context.fastMode ? 'Enabled' : 'Disabled'}\`\n`
211
+ statsText += `- **Multi-Agent:** \`${context.multiAgent ? 'Enabled' : 'Disabled'}\`\n`
212
+ return {
213
+ handled: true,
214
+ action: 'system_message',
215
+ systemText: statsText,
216
+ }
217
+ }
218
+
219
+ case '/memory': {
220
+ return {
221
+ handled: true,
222
+ action: 'system_message',
223
+ systemText: `🧠 **Long-Term Memory Store**\n\nManage profile variables & interactions in Settings > Memory.`,
224
+ }
225
+ }
226
+
227
+ case '/learn':
228
+ case '/skill': {
229
+ return {
230
+ handled: true,
231
+ action: 'system_message',
232
+ systemText: `📚 **Skill & Instruction Manager**\n\nImport skills in Settings > Plugins or place markdown files in \`.agents/skills/\`.`,
233
+ }
234
+ }
235
+
236
+ default:
237
+ return { handled: false }
238
+ }
239
+ }