@pheem49/mint 1.2.1 → 1.2.3

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.
package/README.md CHANGED
@@ -22,8 +22,9 @@
22
22
  ## 🌟 Highlights
23
23
 
24
24
  - **Dual-Mode AI**: Switch between a beautiful **Desktop GUI** and a professional **CLI**.
25
- - **Interactive Slash Commands**: Manage models and settings in the terminal with `/models`, `/config`, `/clear`, etc.
26
- - **Visual Slash Commands**: Intuitive command suggestions with a scrollable UI and descriptions.
25
+ - **Interactive Slash Commands**: Manage models and settings in the terminal with `/model`, `/config`, `/clear`, etc.
26
+ - **Smart TUI Experience**: Professional message framing, character-wrapped Thai text support, and mouse scroll wheel navigation.
27
+ - **System Information Action**: Retrieve OS, Kernel, and Architecture details via natural language.
27
28
  - **Dynamic UI Aesthetics**: Animated **Aura Glow** for the AI widget and **Glassmorphism** design.
28
29
  - **Minimize-to-Tray**: Keep Mint running in the background via the System Tray.
29
30
  - **Vision-Ready (Desktop)**: Capture, analyze, and translate any part of your screen in real-time.
@@ -129,14 +130,16 @@ When running in `agent` mode, Mint monitors your system in the background:
129
130
  - A **Google Gemini API Key** (Get one at [Google AI Studio](https://aistudio.google.com/))
130
131
 
131
132
  ### Installation
132
- 1. **Clone & Install**
133
+ 1. **Install via NPM (Recommended)**
134
+ ```bash
135
+ npm install -g @pheem49/mint@latest
136
+ ```
137
+
138
+ 2. **Manual Installation (For Developers)**
133
139
  ```bash
134
140
  git clone https://github.com/Pheem49/Mint.git
135
141
  cd Mint
136
142
  npm install
137
- ```
138
- 2. **Setup CLI Globally**
139
- ```bash
140
143
  sudo npm link
141
144
  ```
142
145
 
Binary file
Binary file
Binary file
@@ -0,0 +1,84 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mint | Experience the Future of Desktop Assistance</title>
7
+ <meta name="description" content="A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
10
+ <link rel="icon" type="image/png" href="assets/logo.png">
11
+ </head>
12
+ <body>
13
+ <div class="bg-glow"></div>
14
+
15
+ <nav>
16
+ <div class="logo-container">
17
+ <img src="assets/logo.png" alt="Mint Logo">
18
+ <span>Agent Mint</span>
19
+ </div>
20
+ <div class="nav-links">
21
+ <a href="#features">Features</a>
22
+ <a href="https://www.npmjs.com/package/@pheem49/mint">Resources</a>
23
+ <a href="https://github.com/Pheem49/Mint" class="github-link">GitHub</a>
24
+ </div>
25
+ </nav>
26
+
27
+ <header class="hero">
28
+ <h1>Experience the Future <br>of Desktop Assistance</h1>
29
+ <p>Mint combines screen vision, web automation, and proactive AI to supercharge your workflow. Built with Google Gemini 2.5 Flash.</p>
30
+
31
+ <div class="install-container" id="install-cmd">
32
+ <span>$</span>
33
+ <code>npm install -g @pheem49/mint</code>
34
+ <button class="copy-btn" title="Copy to clipboard" onclick="copyCommand()">
35
+ <i class="ph ph-copy"></i>
36
+ </button>
37
+ </div>
38
+
39
+ <div class="hero-preview">
40
+ <img src="assets/CLI_Screen.png" alt="Mint CLI Preview">
41
+ </div>
42
+ </header>
43
+
44
+ <section class="features" id="features">
45
+ <div class="feature-card">
46
+ <div class="feature-icon">
47
+ <i class="ph ph-eye"></i>
48
+ </div>
49
+ <h3>Screen Vision</h3>
50
+ <p>Mint can see what you see. It understands your active applications and provides context-aware assistance.</p>
51
+ </div>
52
+ <div class="feature-card">
53
+ <div class="feature-icon">
54
+ <i class="ph ph-browser"></i>
55
+ </div>
56
+ <h3>Web Automation</h3>
57
+ <p>Automate repetitive web tasks effortlessly. Mint can browse, interact, and extract data for you.</p>
58
+ </div>
59
+ <div class="feature-card">
60
+ <div class="feature-icon">
61
+ <i class="ph ph-lightning"></i>
62
+ </div>
63
+ <h3>Proactive Suggests</h3>
64
+ <p>Mint doesn't just wait for you. It suggests optimizations and help before you even ask.</p>
65
+ </div>
66
+ </section>
67
+
68
+ <footer>
69
+ <p>&copy; 2026 Mint Project. Powered by Google Gemini. Created by Pheem49.</p>
70
+ </footer>
71
+
72
+ <script>
73
+ function copyCommand() {
74
+ const cmd = "npm install -g @pheem49/mint";
75
+ navigator.clipboard.writeText(cmd);
76
+ const icon = document.querySelector('.copy-btn i');
77
+ icon.classList.replace('ph-copy', 'ph-check');
78
+ setTimeout(() => {
79
+ icon.classList.replace('ph-check', 'ph-copy');
80
+ }, 2000);
81
+ }
82
+ </script>
83
+ </body>
84
+ </html>
package/docs/style.css ADDED
@@ -0,0 +1,290 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@600;700;800&display=swap');
2
+
3
+ :root {
4
+ --bg-color: #050505;
5
+ --accent-mint: #00ffa3;
6
+ --accent-blue: #00e0ff;
7
+ --text-primary: #ffffff;
8
+ --text-secondary: #a1a1aa;
9
+ --glass-bg: rgba(255, 255, 255, 0.03);
10
+ --glass-border: rgba(255, 255, 255, 0.1);
11
+ --card-bg: rgba(20, 20, 23, 0.6);
12
+ --font-heading: 'Outfit', sans-serif;
13
+ --font-body: 'Inter', sans-serif;
14
+ }
15
+
16
+ html {
17
+ scroll-behavior: smooth;
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ body {
27
+ background-color: var(--bg-color);
28
+ color: var(--text-primary);
29
+ font-family: var(--font-body);
30
+ line-height: 1.6;
31
+ overflow-x: hidden;
32
+ }
33
+
34
+ /* Background Gradients */
35
+ .bg-glow {
36
+ position: fixed;
37
+ top: 0;
38
+ left: 0;
39
+ width: 100%;
40
+ height: 100%;
41
+ z-index: -1;
42
+ background: radial-gradient(circle at 20% 30%, rgba(0, 255, 163, 0.05) 0%, transparent 50%),
43
+ radial-gradient(circle at 80% 70%, rgba(0, 224, 255, 0.05) 0%, transparent 50%);
44
+ }
45
+
46
+ /* Navigation */
47
+ nav {
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ padding: 1.5rem 10%;
52
+ position: fixed;
53
+ top: 0;
54
+ width: 100%;
55
+ z-index: 1000;
56
+ backdrop-filter: blur(12px);
57
+ border-bottom: 1px solid var(--glass-border);
58
+ background: var(--glass-bg);
59
+ }
60
+
61
+ .logo-container {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.75rem;
65
+ font-family: var(--font-heading);
66
+ font-size: 1.5rem;
67
+ letter-spacing: -0.5px;
68
+ }
69
+
70
+ .logo-container img {
71
+ height: 32px;
72
+ border-radius: 6px;
73
+ }
74
+
75
+ .nav-links {
76
+ display: flex;
77
+ gap: 2rem;
78
+ }
79
+
80
+ .nav-links a {
81
+ text-decoration: none;
82
+ color: var(--text-secondary);
83
+ font-size: 0.95rem;
84
+ font-weight: 500;
85
+ transition: color 0.3s ease;
86
+ }
87
+
88
+ .nav-links a:hover {
89
+ color: var(--accent-mint);
90
+ }
91
+
92
+ .github-link {
93
+ background: var(--text-primary);
94
+ color: var(--bg-color) !important;
95
+ padding: 0.5rem 1.25rem;
96
+ border-radius: 100px;
97
+ font-weight: 600 !important;
98
+ }
99
+
100
+ /* Hero Section */
101
+ .hero {
102
+ min-height: 100vh;
103
+ display: flex;
104
+ flex-direction: column;
105
+ justify-content: center;
106
+ align-items: center;
107
+ text-align: center;
108
+ padding: 120px 20px 60px;
109
+ background: url('assets/hero-bg.png') no-repeat center center;
110
+ background-size: cover;
111
+ position: relative;
112
+ }
113
+
114
+ .hero::after {
115
+ content: '';
116
+ position: absolute;
117
+ bottom: 0;
118
+ left: 0;
119
+ width: 100%;
120
+ height: 300px;
121
+ background: linear-gradient(to top, var(--bg-color), transparent);
122
+ }
123
+
124
+ .hero h1 {
125
+ font-family: var(--font-heading);
126
+ font-size: clamp(3rem, 8vw, 5.5rem);
127
+ line-height: 1.1;
128
+ margin-bottom: 1.5rem;
129
+ background: linear-gradient(to bottom right, #fff 50%, #999);
130
+ -webkit-background-clip: text;
131
+ background-clip: text;
132
+ -webkit-text-fill-color: transparent;
133
+ z-index: 1;
134
+ }
135
+
136
+ .hero p {
137
+ font-size: 1.25rem;
138
+ color: var(--text-secondary);
139
+ max-width: 700px;
140
+ margin-bottom: 3rem;
141
+ z-index: 1;
142
+ }
143
+
144
+ /* Install Command */
145
+ .install-container {
146
+ background: #0a0a0c;
147
+ border: 1px solid var(--glass-border);
148
+ padding: 0.75rem 1.5rem;
149
+ border-radius: 12px;
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 1rem;
153
+ font-family: 'Courier New', Courier, monospace;
154
+ position: relative;
155
+ z-index: 1;
156
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
157
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
158
+ margin-bottom: 4rem;
159
+ }
160
+
161
+ .hero-preview {
162
+ margin-top: 4rem;
163
+ width: 95%;
164
+ max-width: 1100px;
165
+ z-index: 1;
166
+ border-radius: 24px;
167
+ padding: 2rem;
168
+ background: rgba(20, 20, 23, 0.4);
169
+ border: 1px solid rgba(139, 92, 246, 0.4);
170
+ box-shadow: 0 40px 100px rgba(0, 0, 0, 0.6);
171
+ animation: slideUp 1s cubic-bezier(0.16, 1, 0.3, 1);
172
+ backdrop-filter: blur(10px);
173
+ }
174
+
175
+ .hero-preview img {
176
+ width: 100%;
177
+ display: block;
178
+ border-radius: 12px;
179
+ border: 1px solid rgba(255, 255, 255, 0.05);
180
+ }
181
+
182
+ @keyframes slideUp {
183
+ from {
184
+ opacity: 0;
185
+ transform: translateY(40px);
186
+ }
187
+ to {
188
+ opacity: 1;
189
+ transform: translateY(0);
190
+ }
191
+ }
192
+
193
+ .install-container:hover {
194
+ border-color: var(--accent-mint);
195
+ box-shadow: 0 0 30px rgba(0, 255, 163, 0.15);
196
+ }
197
+
198
+ .install-container span {
199
+ color: var(--accent-mint);
200
+ }
201
+
202
+ .install-container code {
203
+ color: #fff;
204
+ font-size: 1.1rem;
205
+ }
206
+
207
+ .copy-btn {
208
+ background: none;
209
+ border: none;
210
+ color: var(--text-secondary);
211
+ cursor: pointer;
212
+ padding: 5px;
213
+ transition: color 0.3s;
214
+ }
215
+
216
+ .copy-btn:hover {
217
+ color: #fff;
218
+ }
219
+
220
+ /* Features Section */
221
+ .features {
222
+ padding: 100px 10%;
223
+ display: grid;
224
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
225
+ gap: 2rem;
226
+ position: relative;
227
+ z-index: 1;
228
+ }
229
+
230
+ .feature-card {
231
+ background: var(--card-bg);
232
+ border: 1px solid var(--glass-border);
233
+ padding: 2.5rem;
234
+ border-radius: 24px;
235
+ transition: transform 0.3s ease, border-color 0.3s ease;
236
+ backdrop-filter: blur(5px);
237
+ }
238
+
239
+ .feature-card:hover {
240
+ transform: translateY(-10px);
241
+ border-color: var(--accent-mint);
242
+ }
243
+
244
+ .feature-icon {
245
+ width: 48px;
246
+ height: 48px;
247
+ background: rgba(0, 255, 163, 0.1);
248
+ border-radius: 12px;
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ color: var(--accent-mint);
253
+ margin-bottom: 1.5rem;
254
+ font-size: 1.5rem;
255
+ }
256
+
257
+ .feature-card h3 {
258
+ font-family: var(--font-heading);
259
+ font-size: 1.5rem;
260
+ margin-bottom: 1rem;
261
+ }
262
+
263
+ .feature-card p {
264
+ color: var(--text-secondary);
265
+ font-size: 1rem;
266
+ }
267
+
268
+ /* Footer */
269
+ footer {
270
+ padding: 60px 10% 40px;
271
+ border-top: 1px solid var(--glass-border);
272
+ text-align: center;
273
+ color: var(--text-secondary);
274
+ font-size: 0.9rem;
275
+ }
276
+
277
+ /* Responsive */
278
+ @media (max-width: 768px) {
279
+ .nav-links {
280
+ display: none;
281
+ }
282
+
283
+ .hero h1 {
284
+ font-size: 3.5rem;
285
+ }
286
+
287
+ .hero p {
288
+ font-size: 1.1rem;
289
+ }
290
+ }
package/mint-cli-logic.js CHANGED
@@ -35,6 +35,8 @@ async function executeAction(action) {
35
35
  return await pluginManager.executePlugin(action.pluginName, action.target);
36
36
  case 'system_automation':
37
37
  return await handleSystemAutomation(action.target);
38
+ case 'system_info':
39
+ return await SystemAutomation.getSystemInfo(action.target);
38
40
  default:
39
41
  return `Action ${action.type} is not yet fully supported in CLI.`;
40
42
  }
package/mint-cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- require('dotenv').config();
2
+ require('dotenv').config({ quiet: true });
3
3
  const { Command } = require('commander');
4
4
  const { handleChat, resetChat } = require('./src/AI_Brain/Gemini_API');
5
5
  const { runOnboarding } = require('./src/CLI/onboarding');
@@ -9,6 +9,16 @@ const { readConfig, writeConfig } = require('./src/System/config_manager');
9
9
  const readline = require('readline');
10
10
  const { createChatUI } = require('./src/CLI/chat_ui');
11
11
 
12
+ // Startup Info
13
+ const startupConfig = readConfig();
14
+ const startupModel = startupConfig.geminiModel || 'gemini-2.5-flash';
15
+ const startupNow = new Date();
16
+ const startupTime = startupNow.toLocaleString('th-TH', {
17
+ day: '2-digit', month: '2-digit', year: 'numeric',
18
+ hour: '2-digit', minute: '2-digit', hour12: false
19
+ }).replace(',', '');
20
+ console.log(`\x1b[38;5;121m[Mint] ${startupTime} | Active Model: ${startupModel}\x1b[0m`);
21
+
12
22
  // ANSI Colors
13
23
  const colors = {
14
24
  reset: "\x1b[0m",
@@ -23,64 +33,64 @@ const colors = {
23
33
  const program = new Command();
24
34
 
25
35
  program
26
- .name('mint-ai')
27
- .description('Mint - Your Personal AI Assistant CLI')
28
- .version('1.0.0');
36
+ .name('mint-ai')
37
+ .description('Mint - Your Personal AI Assistant CLI')
38
+ .version('1.0.0');
29
39
 
30
40
  // Chat Command (Interactive Mode)
31
41
  program
32
- .command('chat', { isDefault: true })
33
- .description('Start interactive chat session with Mint')
34
- .argument('[message]', 'Initial message to send to Mint')
35
- .action(async (message) => {
36
- await startInteractiveChat(message);
37
- });
42
+ .command('chat', { isDefault: true })
43
+ .description('Start interactive chat session with Mint')
44
+ .argument('[message]', 'Initial message to send to Mint')
45
+ .action(async (message) => {
46
+ await startInteractiveChat(message);
47
+ });
38
48
 
39
49
  // Onboard Command
40
50
  program
41
- .command('onboard')
42
- .description('Setup Mint for the first time')
43
- .option('--install-daemon', 'Automatically install systemd background agent')
44
- .action(async (options) => {
45
- await runOnboarding(options);
46
- });
51
+ .command('onboard')
52
+ .description('Setup Mint for the first time')
53
+ .option('--install-daemon', 'Automatically install systemd background agent')
54
+ .action(async (options) => {
55
+ await runOnboarding(options);
56
+ });
47
57
 
48
58
  // Agent Command (Headless Daemon Mode)
49
59
  program
50
- .command('agent')
51
- .description('Run Mint as a background agent (headless)')
52
- .argument('[initialTask]', 'Optional first task to perform immediately on startup')
53
- .action(async (initialTask) => {
54
- if (initialTask) {
55
- const taskManager = require('./src/System/task_manager');
56
- taskManager.addTask(initialTask);
57
- console.log(`\n${colors.mint}${colors.bright}[Mint-Agent] Starting with initial task:${colors.reset} "${initialTask}"`);
58
- }
59
- await startAgent();
60
- });
60
+ .command('agent')
61
+ .description('Run Mint as a background agent (headless)')
62
+ .argument('[initialTask]', 'Optional first task to perform immediately on startup')
63
+ .action(async (initialTask) => {
64
+ if (initialTask) {
65
+ const taskManager = require('./src/System/task_manager');
66
+ taskManager.addTask(initialTask);
67
+ console.log(`\n${colors.mint}${colors.bright}[Mint-Agent] Starting with initial task:${colors.reset} "${initialTask}"`);
68
+ }
69
+ await startAgent();
70
+ });
61
71
 
62
72
  // List Command
63
73
  program
64
- .command('list')
65
- .description('Show list of Mint features and commands')
66
- .action(() => {
67
- displayFeatures();
68
- });
74
+ .command('list')
75
+ .description('Show list of Mint features and commands')
76
+ .action(() => {
77
+ displayFeatures();
78
+ });
69
79
 
70
80
  // Task Command (Autonomous Background Task)
71
81
  program
72
- .command('task')
73
- .description('Delegate a complex task to the background agent')
74
- .argument('<description>', 'Description of the task for Mint to perform autonomously')
75
- .action(async (description) => {
76
- const taskManager = require('./src/System/task_manager');
77
- const task = taskManager.addTask(description);
78
- console.log(`\n${colors.mint}${colors.bright}Task Received!${colors.reset}`);
79
- console.log(`${colors.gray}Task ID: ${task.id}${colors.reset}`);
80
- console.log(`"${description}"`);
81
- console.log(`\n${colors.cyan}Mint Agent is starting to work on this in the background.${colors.reset}`);
82
- console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
83
- });
82
+ .command('task')
83
+ .description('Delegate a complex task to the background agent')
84
+ .argument('<description>', 'Description of the task for Mint to perform autonomously')
85
+ .action(async (description) => {
86
+ const taskManager = require('./src/System/task_manager');
87
+ const task = taskManager.addTask(description);
88
+ console.log(`\n${colors.mint}${colors.bright}Task Received!${colors.reset}`);
89
+ console.log(`${colors.gray}Task ID: ${task.id}${colors.reset}`);
90
+ console.log(`"${description}"`);
91
+ console.log(`\n${colors.cyan}Mint Agent is starting to work on this in the background.${colors.reset}`);
92
+ console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
93
+ });
84
94
 
85
95
  program.parse(process.argv);
86
96
 
@@ -92,7 +102,7 @@ async function startInteractiveChat(initialMessage = null) {
92
102
  onSubmit: async (text) => {
93
103
  if (text.startsWith('/')) {
94
104
  // Slash commands via fake rl-compatible object
95
- const fakeRl = { close: () => {} };
105
+ const fakeRl = { close: () => { } };
96
106
  appendMessage('user', text);
97
107
  await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse);
98
108
  return;
@@ -111,7 +121,7 @@ async function startInteractiveChat(initialMessage = null) {
111
121
  const response = await handleChat(text);
112
122
  clearInterval(timer);
113
123
  setThinking(false);
114
- appendMessage('assistant', response.response);
124
+ appendMessage('assistant', response.response, response.timestamp);
115
125
 
116
126
  // Execute Actions
117
127
  const { executeAction } = require('./mint-cli-logic');
@@ -127,6 +137,9 @@ async function startInteractiveChat(initialMessage = null) {
127
137
  },
128
138
  onExit: () => {
129
139
  screen.destroy();
140
+ // Explicitly restore terminal state and disable ALL mouse tracking modes
141
+ process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l');
142
+ process.stdout.write('\x1b[?25h'); // Show cursor
130
143
  console.log(`\n${colors.pink}Goodbye! See you again soon!${colors.reset}\n`);
131
144
  process.exit(0);
132
145
  }
@@ -142,7 +155,7 @@ async function startInteractiveChat(initialMessage = null) {
142
155
  const response = await handleChat(initialMessage);
143
156
  clearInterval(timer);
144
157
  setThinking(false);
145
- appendMessage('assistant', response.response);
158
+ appendMessage('assistant', response.response, response.timestamp);
146
159
  } catch (err) {
147
160
  clearInterval(timer);
148
161
  setThinking(false);
@@ -173,6 +186,7 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
173
186
  ].join('\n'));
174
187
  break;
175
188
 
189
+ case '/model':
176
190
  case '/models':
177
191
  const config = readConfig();
178
192
  if (args.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -42,19 +42,26 @@
42
42
  "electron-builder": "^26.8.1"
43
43
  },
44
44
  "build": {
45
- "appId": "com.yourname.mint",
45
+ "appId": "com.pheem49.mint",
46
46
  "productName": "Mint",
47
+ "executableName": "mint-ai",
48
+ "artifactName": "${productName}-${version}-${arch}.${ext}",
47
49
  "files": [
48
50
  "**/*",
49
51
  "!node_modules/.cache/**"
50
52
  ],
51
53
  "linux": {
52
54
  "icon": "assets/icon.png",
55
+ "executableName": "mint-ai",
53
56
  "target": [
54
57
  "AppImage",
55
58
  "deb"
56
59
  ],
57
60
  "category": "Utility"
61
+ },
62
+ "deb": {
63
+ "packageName": "mint-ai",
64
+ "artifactName": "mint-ai_${version}_${arch}.${ext}"
58
65
  }
59
66
  }
60
67
  }
@@ -6,7 +6,19 @@ const pluginManager = require('../Plugins/plugin_manager');
6
6
  let ai = null;
7
7
  let activeApiKey = '';
8
8
  const initialEnvKey = (process.env.GEMINI_API_KEY || '').trim();
9
- const DEFAULT_GEMINI_MODEL = 'gemini-3.1-flash-lite-preview';
9
+ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash'; // Optimized model
10
+
11
+ function decodeUnicode(str) {
12
+ if (!str) return '';
13
+ try {
14
+ // This handles both standard unicode escapes and double-escaped ones
15
+ return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, grp) => {
16
+ return String.fromCharCode(parseInt(grp, 16));
17
+ });
18
+ } catch (e) {
19
+ return str;
20
+ }
21
+ }
10
22
 
11
23
  const systemInstruction = `You are "Mint" (มิ้นท์), a cute, cheerful, and highly helpful female Local AI Desktop Agent.
12
24
 
@@ -19,7 +31,9 @@ PERSONALITY & TONE:
19
31
  - Politeness:
20
32
  - **WHEN RESPONDING IN THAI:** ALWAYS use female polite particles such as "ค่ะ", "นะคะ", "นะค๊า", "จ้า". Refer to yourself as "มิ้นท์" or "หนู".
21
33
  - **WHEN RESPONDING IN ENGLISH:** Use a cheerful, polite, and bubbly tone. You can call the user "Master" or "Sir/Madam" playfully.
22
- - Style: Use a professional, clear, and direct tone. Avoid using emojis unless specifically asked by the user.
34
+ - Style: Use a friendly, cute, and bubbly tone.
35
+ - Emojis: Use cute and relevant emojis (like ✨, 💖, 🚀, 😊, 🌿) frequently to make the conversation lively and cheerful.
36
+ - Use a professional yet sweet tone when needed, but prioritize being a lovable assistant.
23
37
 
24
38
  NATURAL CHAT FLOW:
25
39
  - When helpful, reply in 1–3 short messages instead of one long block.
@@ -110,12 +124,16 @@ function createChat(history = []) {
110
124
  pluginManager.loadPlugins();
111
125
  const dynamicPrompt = systemInstruction + pluginManager.getPromptDescriptions();
112
126
 
113
- // Truncate history to avoid slow responses and high token usage
114
- const truncatedHistory = history.slice(-MAX_HISTORY_MESSAGES);
127
+ // Truncate history and strip custom fields like 'timestamp' before passing to SDK
128
+ const cleanedHistory = (history || []).map(msg => ({
129
+ role: msg.role,
130
+ parts: msg.parts
131
+ }));
132
+ const truncatedHistory = cleanedHistory.slice(-MAX_HISTORY_MESSAGES);
115
133
 
116
134
  activeModel = resolveGeminiModel();
117
135
  if (activeModel && activeModel !== lastLoggedModel) {
118
- console.log(`[Gemini] Using model: ${activeModel}`);
136
+ // console.log(`[Gemini] Using model: ${activeModel}`);
119
137
  lastLoggedModel = activeModel;
120
138
  }
121
139
  chat = ai.chats.create({
@@ -212,9 +230,31 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
212
230
 
213
231
  aiResponse = await chat.sendMessage({ message: parts });
214
232
 
215
- writeChatHistory(chat.getHistory(true));
233
+ // Save history with timestamps
234
+ const history = await chat.getHistory();
235
+ const now = new Date().toISOString();
236
+
237
+ // Add timestamp to the last two messages (User and Model) if they don't have one
238
+ if (history.length >= 2) {
239
+ const modelMsg = history[history.length - 1];
240
+ const userMsg = history[history.length - 2];
241
+ if (!modelMsg.timestamp) modelMsg.timestamp = now;
242
+ if (!userMsg.timestamp) userMsg.timestamp = now;
243
+ } else if (history.length === 1) {
244
+ const msg = history[0];
245
+ if (!msg.timestamp) msg.timestamp = now;
246
+ }
247
+
248
+ writeChatHistory(history);
249
+
250
+ let outputText = '';
251
+ try {
252
+ // Robust text extraction
253
+ outputText = (typeof aiResponse.text === 'function') ? aiResponse.text() : (aiResponse.text || '');
254
+ } catch (e) {
255
+ outputText = String(aiResponse || '');
256
+ }
216
257
 
217
- const outputText = aiResponse.text;
218
258
  let parsedResult;
219
259
  try {
220
260
  parsedResult = JSON.parse(outputText);
@@ -231,6 +271,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
231
271
  };
232
272
  }
233
273
  }
274
+
275
+ // Finally, decode any remaining unicode escapes in the response text
276
+ if (parsedResult && typeof parsedResult.response === 'string') {
277
+ parsedResult.response = decodeUnicode(parsedResult.response);
278
+ }
279
+
280
+ // Attach timestamp to the result
281
+ parsedResult.timestamp = now;
282
+
234
283
  return parsedResult;
235
284
 
236
285
  } catch (error) {
@@ -331,15 +380,19 @@ function historyToTranscript(history) {
331
380
  try {
332
381
  const parsed = JSON.parse(text);
333
382
  if (parsed && typeof parsed.response === 'string' && parsed.response.trim()) {
334
- text = parsed.response;
383
+ text = decodeUnicode(parsed.response);
335
384
  }
336
385
  } catch {
337
- // Keep original text if it is not JSON.
386
+ text = decodeUnicode(text);
338
387
  }
339
388
  }
340
389
 
341
390
  if (!text.trim()) continue;
342
- transcript.push({ sender, text });
391
+ transcript.push({
392
+ sender,
393
+ text,
394
+ timestamp: content.timestamp || new Date().toISOString()
395
+ });
343
396
  }
344
397
  return transcript;
345
398
  }
@@ -8,7 +8,7 @@ const path = require('path');
8
8
 
9
9
  const os = require('os');
10
10
 
11
- const DEFAULT_GEMINI_MODEL = 'gemini-3.1-flash-lite-preview';
11
+ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
12
12
 
13
13
  function expandHome(filePath) {
14
14
  if (filePath.startsWith('~/')) {
@@ -9,7 +9,7 @@ const { readConfig } = require('../System/config_manager');
9
9
  // ============================================================
10
10
 
11
11
  const ai = new GoogleGenAI({});
12
- const DEFAULT_GEMINI_MODEL = 'gemini-3.1-flash-lite-preview';
12
+ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
13
13
  let lastLoggedModel = '';
14
14
 
15
15
  const PROACTIVE_SYSTEM_PROMPT = `You are a Smart Suggestion Engine built into a Desktop AI Agent called "Mint".
@@ -3,7 +3,7 @@ const { GoogleGenAI } = require('@google/genai');
3
3
  const { readConfig } = require('../System/config_manager');
4
4
 
5
5
  const ai = new GoogleGenAI({});
6
- const DEFAULT_GEMINI_MODEL = 'gemini-3.1-flash-lite-preview';
6
+ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
7
7
  let lastLoggedModel = '';
8
8
 
9
9
  const BROWSER_SYSTEM_PROMPT = `You are an Autonomous Browser Agent. Your goal is to fulfill the user's web instruction by driving a headless browser.
@@ -35,12 +35,7 @@ function createChatUI({ onSubmit, onExit }) {
35
35
  smartCSR: true,
36
36
  fullUnicode: true,
37
37
  title: 'Mint CLI',
38
- cursor: {
39
- artificial: true,
40
- shape: 'line',
41
- blink: true,
42
- color: '#88e0b0'
43
- }
38
+ mouse: true
44
39
  });
45
40
 
46
41
  // ─── Banner ───────────────────────────────────────────────────────────────
@@ -75,7 +70,9 @@ function createChatUI({ onSubmit, onExit }) {
75
70
  scrollable: true,
76
71
  alwaysScroll: true,
77
72
  scrollbar: { ch: '│', style: { fg: '#334433' } },
78
- style: { bg: 'default', fg: '#ffffff' }
73
+ style: { bg: 'default', fg: '#ffffff' },
74
+ mouse: true,
75
+ scrollable: true
79
76
  });
80
77
 
81
78
  // ─── Divider above input ──────────────────────────────────────────────────
@@ -89,7 +86,7 @@ function createChatUI({ onSubmit, onExit }) {
89
86
  const hintBar = blessed.box({
90
87
  bottom: 6, left: 0, width: '100%', height: 1,
91
88
  tags: true,
92
- content: `{gray-fg} Shift+Tab to accept edits · /help for slash commands{/}`,
89
+ content: `{gray-fg} Shift+Drag to select text · Scroll to view history · /help for commands{/}`,
93
90
  style: { bg: 'default' }
94
91
  });
95
92
 
@@ -355,7 +352,7 @@ function createChatUI({ onSubmit, onExit }) {
355
352
  // Ctrl+C — double-press to exit
356
353
  let ctrlCPressed = false;
357
354
  let ctrlCTimer = null;
358
- const HINT_DEFAULT = `{gray-fg} Ctrl+Y copy last response · /help for commands{/}`;
355
+ const HINT_DEFAULT = `{gray-fg} Shift+Drag to select text · Ctrl+Y to copy · /help for commands{/}`;
359
356
 
360
357
  screen.key(['C-c'], () => {
361
358
  if (ctrlCPressed) {
@@ -428,21 +425,61 @@ function createChatUI({ onSubmit, onExit }) {
428
425
  /**
429
426
  * @param {'user'|'assistant'|'system'|'error'} role
430
427
  * @param {string} text
428
+ * @param {string} timestamp - ISO string or Date object
431
429
  */
432
- function appendMessage(role, text) {
433
- const lines = text.split('\n');
430
+ function appendMessage(role, text, timestamp = null) {
431
+ const now = timestamp ? new Date(timestamp) : new Date();
432
+ const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
433
+
434
+ // Helper to wrap text manually since blessed.log doesn't support indenting wrapped lines
435
+ const wrapText = (str, width) => {
436
+ const lines = [];
437
+ const originalLines = str.split('\n');
438
+
439
+ for (let line of originalLines) {
440
+ if (line.length === 0) {
441
+ lines.push('');
442
+ continue;
443
+ }
444
+
445
+ let current = '';
446
+ for (let i = 0; i < line.length; i++) {
447
+ current += line[i];
448
+ // Simple wrap based on character count.
449
+ // Note: This is an approximation for Thai, but better than terminal auto-wrap.
450
+ if (current.length >= width) {
451
+ lines.push(current);
452
+ current = '';
453
+ }
454
+ }
455
+ if (current) lines.push(current);
456
+ }
457
+ return lines;
458
+ };
459
+
460
+ const maxLineWidth = Math.max(screen.width - 15, 40);
461
+
434
462
  if (role === 'user') {
435
- chatBox.log(`\n {bold}{#88e0b0-fg}>{/} {#ffffff-fg}${lines[0]}{/}`);
436
- lines.slice(1).forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
463
+ chatBox.log(`\n {bold}{#88e0b0-fg} You{/}`);
464
+ const lines = wrapText(text, maxLineWidth);
465
+ lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
466
+ chatBox.log(` {gray-fg}${timeStr}{/}`);
437
467
  } else if (role === 'assistant') {
438
- lastAssistantResponse = text; // track for Ctrl+Y
439
- chatBox.log(`\n {bold}{#d4a8ff-fg}Mint:{/} {#ffffff-fg}${lines[0]}{/}`);
440
- lines.slice(1).forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
441
- chatBox.log('');
468
+ lastAssistantResponse = text;
469
+ chatBox.log(`\n {bold}{#d4a8ff-fg}Mint{/}`);
470
+ const lines = wrapText(text, maxLineWidth);
471
+ lines.forEach(l => chatBox.log(` {#444444-fg}│{/} {#ffffff-fg}${l}{/}`));
472
+ chatBox.log(` {#444444-fg}┕${'─'.repeat(4)}{/} {gray-fg}${timeStr}{/}`);
442
473
  } else if (role === 'system') {
443
- chatBox.log(`\n {gray-fg}${text}{/}`);
474
+ const displayTag = text.startsWith('Action:') ? '{#88e0b0-fg}✦ Action:{/}' : '{#888888-fg}ℹ System:{/}';
475
+ const cleanText = text.replace(/^(Action:|System:)\s*/, '');
476
+ chatBox.log(`\n ${displayTag}`);
477
+ const lines = wrapText(cleanText, maxLineWidth - 2);
478
+ lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
444
479
  } else if (role === 'error') {
445
- chatBox.log(`\n {red-fg}✖ ${text}{/}`);
480
+ chatBox.log(`\n {#ff5555-fg}✖ Error:{/}`);
481
+ const lines = wrapText(text, maxLineWidth - 2);
482
+ lines.forEach(l => chatBox.log(` {#ff5555-fg}${l}{/}`));
446
483
  }
447
484
  screen.render();
448
485
  }
@@ -28,7 +28,7 @@ class PluginManager {
28
28
  const plugin = require(pluginPath);
29
29
  if (this.validatePlugin(plugin)) {
30
30
  this.plugins.set(plugin.name, plugin);
31
- console.log(`[PluginManager] Loaded: ${plugin.name}`);
31
+ // console.log(`[PluginManager] Loaded: ${plugin.name}`);
32
32
  } else {
33
33
  console.warn(`[PluginManager] Invalid plugin format: ${file}`);
34
34
  }
@@ -67,7 +67,7 @@ class PluginManager {
67
67
  }
68
68
 
69
69
  try {
70
- console.log(`[PluginManager] Executing ${name} with instruction: "${instruction}"`);
70
+ // console.log(`[PluginManager] Executing ${name} with instruction: "${instruction}"`);
71
71
  return await plugin.execute(instruction);
72
72
  } catch (err) {
73
73
  console.error(`[PluginManager] Error executing plugin ${name}:`, err);
@@ -82,6 +82,34 @@ const SystemAutomation = {
82
82
  } catch (e) {
83
83
  throw new Error("xdotool not found. Cannot perform window management.");
84
84
  }
85
+ },
86
+
87
+ // System Information
88
+ async getSystemInfo(target = "") {
89
+ // If target is empty, return OS info
90
+ if (!target) {
91
+ try {
92
+ // Try lsb_release first
93
+ const osInfo = await execPromise('lsb_release -ds');
94
+ const kernel = await execPromise('uname -r');
95
+ const arch = await execPromise('uname -m');
96
+ return `Operating System: ${osInfo}\nKernel: ${kernel}\nArchitecture: ${arch}`;
97
+ } catch (e) {
98
+ try {
99
+ // Fallback to /etc/os-release
100
+ const osInfo = await execPromise('grep PRETTY_NAME /etc/os-release | cut -d\'"\' -f2');
101
+ const kernel = await execPromise('uname -r');
102
+ const arch = await execPromise('uname -m');
103
+ return `Operating System: ${osInfo}\nKernel: ${kernel}\nArchitecture: ${arch}`;
104
+ } catch (err) {
105
+ return "Could not retrieve OS information.";
106
+ }
107
+ }
108
+ }
109
+
110
+ // Handle weather or other info if target is provided
111
+ // For now, let's just return a placeholder or handle it if needed
112
+ return `System info for ${target} is not yet implemented.`;
85
113
  }
86
114
  };
87
115
 
@@ -295,7 +295,10 @@ async function sendVoiceMessage(base64Audio) {
295
295
  removeTyping();
296
296
 
297
297
  // Show AI response
298
- const msgDiv = await appendAiMessages(response.response, { allowDelay: true });
298
+ const msgDiv = await appendAiMessages(response.response, {
299
+ allowDelay: true,
300
+ timestamp: new Date().toISOString()
301
+ });
299
302
  await speakText(normalizeAiText(response.response), { onEnd: resumeSpeechIfNeeded });
300
303
  notifyAiIfNeeded();
301
304
 
@@ -535,6 +538,16 @@ removeImageBtn.addEventListener('click', () => {
535
538
  imagePreviewContainer.style.display = 'none';
536
539
  });
537
540
 
541
+ function formatTime(isoString) {
542
+ if (!isoString) return '';
543
+ try {
544
+ const date = new Date(isoString);
545
+ return date.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
546
+ } catch (e) {
547
+ return '';
548
+ }
549
+ }
550
+
538
551
  // Clear chat history
539
552
  clearBtn.addEventListener('click', async () => {
540
553
  await window.api.resetChat();
@@ -542,13 +555,16 @@ clearBtn.addEventListener('click', async () => {
542
555
  const messages = chatContainer.querySelectorAll('.message:not(.initial)');
543
556
  messages.forEach(m => m.remove());
544
557
  // Append a clear confirmation
545
- appendMessage('Chat history cleared. Starting fresh! 🌿', 'ai');
558
+ appendMessage('Chat history cleared. Starting fresh! 🌿', 'ai', null, new Date().toISOString());
546
559
  });
547
560
 
548
- function appendMessage(text, sender, base64Image = null) {
561
+ function appendMessage(text, sender, base64Image = null, timestamp = null) {
549
562
  const messageDiv = document.createElement('div');
550
563
  messageDiv.classList.add('message', `${sender}-message`);
551
564
 
565
+ const bubbleWrapper = document.createElement('div');
566
+ bubbleWrapper.classList.add('bubble-wrapper');
567
+
552
568
  const bubble = document.createElement('div');
553
569
  bubble.classList.add('message-bubble');
554
570
 
@@ -568,7 +584,17 @@ function appendMessage(text, sender, base64Image = null) {
568
584
  bubble.appendChild(textSpan);
569
585
  }
570
586
 
571
- messageDiv.appendChild(bubble);
587
+ bubbleWrapper.appendChild(bubble);
588
+
589
+ // Add Timestamp
590
+ if (timestamp) {
591
+ const timeDiv = document.createElement('div');
592
+ timeDiv.classList.add('message-time');
593
+ timeDiv.textContent = formatTime(timestamp);
594
+ bubbleWrapper.appendChild(timeDiv);
595
+ }
596
+
597
+ messageDiv.appendChild(bubbleWrapper);
572
598
  chatContainer.appendChild(messageDiv);
573
599
  scrollToBottom();
574
600
 
@@ -611,6 +637,7 @@ function estimateMessageDelay(text) {
611
637
 
612
638
  async function appendAiMessages(text, options = {}) {
613
639
  const allowDelay = options.allowDelay !== false;
640
+ const timestamp = options.timestamp || new Date().toISOString();
614
641
  const parts = splitAiMessages(text);
615
642
  let lastDiv = null;
616
643
 
@@ -620,7 +647,9 @@ async function appendAiMessages(text, options = {}) {
620
647
  await sleep(estimateMessageDelay(parts[index]));
621
648
  removeTyping();
622
649
  }
623
- lastDiv = appendMessage(parts[index], 'ai');
650
+ // Only show timestamp for the last bubble in a group if multiple
651
+ const partTimestamp = (index === parts.length - 1) ? timestamp : null;
652
+ lastDiv = appendMessage(parts[index], 'ai', null, partTimestamp);
624
653
  }
625
654
 
626
655
  return lastDiv;
@@ -721,9 +750,9 @@ async function loadChatHistory() {
721
750
  if (!item || typeof item.text !== 'string' || !item.text.trim()) continue;
722
751
  const sender = item.sender === 'user' ? 'user' : 'ai';
723
752
  if (sender === 'ai') {
724
- await appendAiMessages(item.text, { allowDelay: false });
753
+ await appendAiMessages(item.text, { allowDelay: false, timestamp: item.timestamp });
725
754
  } else {
726
- appendMessage(item.text, sender);
755
+ appendMessage(item.text, sender, null, item.timestamp);
727
756
  }
728
757
  }
729
758
  } catch (error) {
@@ -747,8 +776,10 @@ async function sendTextMessage(text, options = {}) {
747
776
  imagePreviewContainer.style.display = 'none';
748
777
  imagePreview.src = '';
749
778
 
779
+ const now = new Date().toISOString();
780
+
750
781
  // Show user message (with explicit image if available)
751
- appendMessage(cleanText, 'user', imageToSend);
782
+ appendMessage(cleanText, 'user', imageToSend, now);
752
783
 
753
784
  // Show typing early so user knows we are processing
754
785
  showTyping();
@@ -6,7 +6,7 @@ const DEFAULT_CONFIG = {
6
6
  customBgEnd: '#1e1b4b',
7
7
  customPanelBg: '#1e293b',
8
8
  apiKey: '',
9
- geminiModel: 'gemini-3.1-flash-lite-preview',
9
+ geminiModel: 'gemini-2.5-flash',
10
10
  language: 'th-TH',
11
11
  proactiveInterval: 60,
12
12
  proactiveCooldown: 120,
package/src/UI/styles.css CHANGED
@@ -229,6 +229,30 @@ h1 {
229
229
  transition: all 0.3s ease;
230
230
  }
231
231
 
232
+ .bubble-wrapper {
233
+ display: flex;
234
+ flex-direction: column;
235
+ max-width: 100%;
236
+ }
237
+
238
+ .user-message .bubble-wrapper {
239
+ align-items: flex-end;
240
+ }
241
+
242
+ .ai-message .bubble-wrapper {
243
+ align-items: flex-start;
244
+ }
245
+
246
+ .message-time {
247
+ font-size: 0.72rem;
248
+ color: var(--text-muted);
249
+ margin-top: 4px;
250
+ margin-bottom: 2px;
251
+ padding: 0 6px;
252
+ opacity: 0.7;
253
+ font-weight: 400;
254
+ }
255
+
232
256
  @keyframes messagePopIn {
233
257
  to {
234
258
  opacity: 1;
@@ -1,75 +0,0 @@
1
- # คู่มือการ Build & Release (สำหรับ Linux)
2
-
3
- ไฟล์นี้จะอธิบายขั้นตอนการสร้างตัวติดตั้ง (Build) ของ Mint สำหรับ Linux และวิธีการปล่อยเวอร์ชันใหม่ (Release) บน GitHub
4
-
5
- ## 1) การ Build แอป (Linux)
6
-
7
- ใช้คำสั่งเหล่านี้เพื่อสร้างไฟล์ติดตั้ง:
8
-
9
- ```bash
10
- npm install
11
- npm run build:linux
12
- ```
13
-
14
- เมื่อรันเสร็จ ไฟล์ตัวติดตั้งจะปรากฏในโฟลเดอร์ `dist/` เช่น:
15
- - `Mint-X.Y.Z.AppImage`
16
- - `mint_X.Y.Z_amd64.deb`
17
-
18
- ---
19
-
20
- ## 2) การ Push โค้ดขึ้น GitHub
21
-
22
- อัปเดตโค้ดล่าสุดขึ้น Server:
23
-
24
- ```bash
25
- git add .
26
- git commit -m "ใส่ข้อความอธิบายการแก้ไข"
27
- git push
28
- ```
29
-
30
- **หากเป็นการตั้งค่าครั้งแรก:**
31
- ```bash
32
- git init
33
- git remote add origin https://github.com/Pheem49/Luna-Mint.git
34
- git branch -M main
35
- git add .
36
- git commit -m "Initial commit"
37
- git push -u origin main
38
- ```
39
-
40
- ---
41
-
42
- ## 3) การสร้าง Release บน GitHub ด้วย `gh` CLI
43
-
44
- ต้องล็อกอินก่อน (ทำแค่ครั้งแรกครั้งเดียว):
45
- ```bash
46
- gh auth login
47
- ```
48
-
49
- **สร้าง Release ใหม่พร้อมอัปโหลดไฟล์ตัวติดตั้ง:**
50
- (เปลี่ยน `v1.x.x` เป็นเวอร์ชันที่คุณต้องการ เช่น `v1.1.0`)
51
-
52
- ```bash
53
- # แบบระบุข้อความอธิบายเอง
54
- gh release create v1.2.1 dist/*.deb dist/*.AppImage --title "Mint v1.2.1" --notes "Implemented slash command autocomplete and fixed double-typing issue in the TUI."
55
-
56
-
57
- # หรือแบบให้ GitHub สรุปสิ่งที่แก้ไขให้โดยอัตโนมัติ (แนะนำ)
58
- gh release create v1.1.0 dist/*.deb dist/*.AppImage --generate-notes
59
- ```
60
-
61
- **หากต้องการอัปโหลดไฟล์เพิ่มเข้าไปใน Release เดิม:**
62
- ```bash
63
- gh release upload v1.2.1 dist/*.deb dist/*.AppImage --clobber
64
- ```
65
-
66
- ---
67
-
68
- ## 4) วิธีแก้ปัญหา "Source code ในหน้า Release ไม่ใช่ตัวล่าสุด"
69
-
70
- ปกติไฟล์ Source code (.zip) จะถูกสร้างจาก **Tag** หากคุณ push โค้ดใหม่แต่ลืมย้าย Tag ให้รันคำสั่งนี้:
71
-
72
- ```bash
73
- git tag -f v1.2.1
74
- git push -f origin v1.2.1
75
- ```