@pheem49/mint 1.5.0 → 1.5.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.
- package/README.md +27 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +497 -23
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
- 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 +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- 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 +23 -0
- package/package.json +26 -1
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +396 -50
- package/src/CLI/code_agent.js +203 -14
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/updater.js +6 -4
- package/src/System/action_executor.js +59 -10
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +368 -0
- package/src/UI/renderer.js +176 -18
- package/src/UI/styles.css +452 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- package/tests/workspace_manager.test.js +0 -56
package/src/CLI/code_agent.js
CHANGED
|
@@ -7,8 +7,11 @@ const axios = require('axios');
|
|
|
7
7
|
const cheerio = require('cheerio');
|
|
8
8
|
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
9
9
|
const safetyManager = require('../System/safety_manager');
|
|
10
|
+
const memoryStore = require('../AI_Brain/memory_store');
|
|
10
11
|
const { readWorkspaceSession, writeWorkspaceSession } = require('./code_session_memory');
|
|
11
|
-
const { executeAction } = require('
|
|
12
|
+
const { executeAction } = require('../System/action_executor');
|
|
13
|
+
const toolRegistry = require('../System/tool_registry');
|
|
14
|
+
const sandboxRunner = require('../System/sandbox_runner');
|
|
12
15
|
|
|
13
16
|
async function webSearch(query, onProgress = () => {}) {
|
|
14
17
|
if (!query) throw new Error('Search query required.');
|
|
@@ -169,6 +172,109 @@ function extractJson(text) {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
175
|
+
function normalizeExecutorAction(action, input = {}) {
|
|
176
|
+
return {
|
|
177
|
+
type: action,
|
|
178
|
+
target: input.target || input.path || input.query || '',
|
|
179
|
+
path: input.path,
|
|
180
|
+
pathType: input.type,
|
|
181
|
+
openAfter: input.openAfter,
|
|
182
|
+
pluginName: input.pluginName,
|
|
183
|
+
server: input.server,
|
|
184
|
+
args: input.args,
|
|
185
|
+
x: input.x,
|
|
186
|
+
y: input.y,
|
|
187
|
+
button: input.button
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function formatActionPreview(action, input = {}) {
|
|
192
|
+
if (input.command) return input.command;
|
|
193
|
+
if (input.path) return input.path;
|
|
194
|
+
if (input.target) return input.target;
|
|
195
|
+
if (input.query) return input.query;
|
|
196
|
+
return action;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function evaluateActionResult(action, toolResult = '') {
|
|
200
|
+
if (!toolRegistry.isImportantAction(action)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const text = String(toolResult || '');
|
|
205
|
+
if (/^Error:|blocked|denied|failed|exception|not found/i.test(text)) {
|
|
206
|
+
return {
|
|
207
|
+
status: 'failed',
|
|
208
|
+
message: `Evaluator: ${action} may have failed. Review the observation before continuing.`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (action === 'run_shell' && /(ERR!|Error:|FAIL|failed|not found|permission denied)/i.test(text)) {
|
|
213
|
+
return {
|
|
214
|
+
status: 'warning',
|
|
215
|
+
message: 'Evaluator: shell output contains error-like text; verify before claiming success.'
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
status: 'passed',
|
|
221
|
+
message: `Evaluator: ${action} completed without obvious errors.`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function splitDataUri(dataUri = '') {
|
|
226
|
+
const match = String(dataUri).match(/^data:([^;]+);base64,([\s\S]+)$/);
|
|
227
|
+
if (!match) return null;
|
|
228
|
+
return {
|
|
229
|
+
mimeType: match[1],
|
|
230
|
+
data: match[2]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function contentToText(content) {
|
|
235
|
+
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
236
|
+
return String(content.text || '');
|
|
237
|
+
}
|
|
238
|
+
return String(content || '');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function contentToGeminiParts(content) {
|
|
242
|
+
const text = contentToText(content);
|
|
243
|
+
const parts = text ? [{ text }] : [];
|
|
244
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
245
|
+
const image = splitDataUri(content.imageDataUri);
|
|
246
|
+
if (image) {
|
|
247
|
+
parts.push({ inlineData: { mimeType: image.mimeType, data: image.data } });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return parts.length > 0 ? parts : [{ text: '' }];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function contentToOpenAIContent(content) {
|
|
254
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
255
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
256
|
+
return [
|
|
257
|
+
{ type: 'text', text },
|
|
258
|
+
{ type: 'image_url', image_url: { url: content.imageDataUri } }
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
return text;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function contentToAnthropicContent(content) {
|
|
265
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
266
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
267
|
+
const image = splitDataUri(content.imageDataUri);
|
|
268
|
+
if (image) {
|
|
269
|
+
return [
|
|
270
|
+
{ type: 'image', source: { type: 'base64', media_type: image.mimeType, data: image.data } },
|
|
271
|
+
{ type: 'text', text }
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return text;
|
|
276
|
+
}
|
|
277
|
+
|
|
172
278
|
function getSupportedCodeProviderOrder(config, availableProviders = getAvailableProviders(config || {}), requestedOverride = null) {
|
|
173
279
|
const requestedProvider = requestedOverride || (config && config.aiProvider) || 'gemini';
|
|
174
280
|
const priority = ['anthropic', 'openai', 'gemini', 'local_openai'];
|
|
@@ -371,7 +477,8 @@ async function runShell(workspaceRoot, command) {
|
|
|
371
477
|
throw new Error('Shell command is required.');
|
|
372
478
|
}
|
|
373
479
|
assertSafeShell(command);
|
|
374
|
-
const { stdout, stderr } = await
|
|
480
|
+
const { stdout, stderr } = await sandboxRunner.runShell(command, {
|
|
481
|
+
source: 'code_agent',
|
|
375
482
|
cwd: workspaceRoot,
|
|
376
483
|
maxBuffer: 1024 * 1024 * 4
|
|
377
484
|
});
|
|
@@ -478,7 +585,7 @@ class UnifiedAgentClient {
|
|
|
478
585
|
const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
479
586
|
const messages = this.history.map(m => ({
|
|
480
587
|
role: m.role,
|
|
481
|
-
content: m.content
|
|
588
|
+
content: contentToAnthropicContent(m.content)
|
|
482
589
|
}));
|
|
483
590
|
|
|
484
591
|
const response = await axios.post('https://api.anthropic.com/v1/messages', {
|
|
@@ -504,7 +611,10 @@ class UnifiedAgentClient {
|
|
|
504
611
|
|
|
505
612
|
const messages = [
|
|
506
613
|
{ role: 'system', content: this.systemInstruction },
|
|
507
|
-
...this.history
|
|
614
|
+
...this.history.map(m => ({
|
|
615
|
+
role: m.role,
|
|
616
|
+
content: contentToOpenAIContent(m.content)
|
|
617
|
+
}))
|
|
508
618
|
];
|
|
509
619
|
|
|
510
620
|
const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
@@ -525,13 +635,15 @@ class UnifiedAgentClient {
|
|
|
525
635
|
const model = this.config.geminiModel || DEFAULT_GEMINI_MODEL;
|
|
526
636
|
const ai = new GoogleGenAI({ apiKey });
|
|
527
637
|
|
|
638
|
+
const recentHistory = this.history.slice(-16);
|
|
639
|
+
const priorHistory = recentHistory.slice(0, -1);
|
|
640
|
+
const lastEntry = recentHistory[recentHistory.length - 1] || { content: '' };
|
|
641
|
+
|
|
528
642
|
// Convert history for Gemini, ensuring parts are correctly structured
|
|
529
|
-
const geminiHistory =
|
|
643
|
+
const geminiHistory = priorHistory.map(m => ({
|
|
530
644
|
role: m.role === 'assistant' ? 'model' : 'user',
|
|
531
|
-
parts:
|
|
645
|
+
parts: contentToGeminiParts(m.content)
|
|
532
646
|
}));
|
|
533
|
-
|
|
534
|
-
const lastMessage = String(this.history[this.history.length - 1].content || '');
|
|
535
647
|
|
|
536
648
|
const chat = ai.chats.create({
|
|
537
649
|
model,
|
|
@@ -542,7 +654,7 @@ class UnifiedAgentClient {
|
|
|
542
654
|
history: geminiHistory
|
|
543
655
|
});
|
|
544
656
|
|
|
545
|
-
const response = await chat.sendMessage({ message:
|
|
657
|
+
const response = await chat.sendMessage({ message: contentToGeminiParts(lastEntry.content) });
|
|
546
658
|
return typeof response.text === 'function' ? response.text() : response.text;
|
|
547
659
|
}
|
|
548
660
|
}
|
|
@@ -616,6 +728,7 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
|
616
728
|
const session = readWorkspaceSession(workspaceRoot);
|
|
617
729
|
const gitContext = await getGitContext(workspaceRoot);
|
|
618
730
|
const testCommands = detectTestCommands(workspaceRoot);
|
|
731
|
+
const userContext = memoryStore.getUserContext(task);
|
|
619
732
|
|
|
620
733
|
const contextStr = history.length > 0
|
|
621
734
|
? `Recent Context:\n${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}\n`
|
|
@@ -636,6 +749,8 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
|
636
749
|
session.summary || '(none)',
|
|
637
750
|
`Previous task: ${session.lastTask || '(none)'}`,
|
|
638
751
|
`Previous verification: ${session.lastVerification || '(none)'}`,
|
|
752
|
+
'Long-term user context:',
|
|
753
|
+
userContext || '(none)',
|
|
639
754
|
'If the task is conversational or trivial, finish directly without inspecting the workspace. For code/workspace tasks, inspect before making edits.'
|
|
640
755
|
].join('\n');
|
|
641
756
|
}
|
|
@@ -644,6 +759,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
644
759
|
const workspaceRoot = path.resolve(options.cwd || process.cwd());
|
|
645
760
|
const history = options.history || [];
|
|
646
761
|
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
762
|
+
const onFinalSummary = typeof options.onFinalSummary === 'function' ? options.onFinalSummary : null;
|
|
647
763
|
const requestApproval = typeof options.requestApproval === 'function'
|
|
648
764
|
? options.requestApproval
|
|
649
765
|
: async () => true;
|
|
@@ -656,7 +772,24 @@ async function executeCodeTask(task, options = {}) {
|
|
|
656
772
|
const provider = providerOrder[0];
|
|
657
773
|
const client = new UnifiedAgentClient(provider, config, providerOrder);
|
|
658
774
|
|
|
659
|
-
|
|
775
|
+
const initialObservationText = await buildInitialObservation(task, workspaceRoot, history);
|
|
776
|
+
const relevantMemoryCount = memoryStore.searchInteractions(task, 5).length;
|
|
777
|
+
onProgress({
|
|
778
|
+
phase: 'memory',
|
|
779
|
+
action: 'memory_context',
|
|
780
|
+
message: `Loaded memory: profile + recent history, ${relevantMemoryCount} direct match${relevantMemoryCount === 1 ? '' : 'es'}`
|
|
781
|
+
});
|
|
782
|
+
let observation = options.imageDataUri
|
|
783
|
+
? {
|
|
784
|
+
text: [
|
|
785
|
+
initialObservationText,
|
|
786
|
+
'',
|
|
787
|
+
`[Attached image: ${options.imagePath || 'command-line image'}]`,
|
|
788
|
+
'Use the attached image as visual context when planning and answering.'
|
|
789
|
+
].join('\n'),
|
|
790
|
+
imageDataUri: options.imageDataUri
|
|
791
|
+
}
|
|
792
|
+
: initialObservationText;
|
|
660
793
|
|
|
661
794
|
let finalSummary = '';
|
|
662
795
|
let finalVerification = '';
|
|
@@ -669,6 +802,17 @@ async function executeCodeTask(task, options = {}) {
|
|
|
669
802
|
const decision = await getAgentDecision(client, observation, { onProgress, step });
|
|
670
803
|
const action = decision.action;
|
|
671
804
|
const input = decision.input || {};
|
|
805
|
+
try {
|
|
806
|
+
toolRegistry.validateToolInput(action, input);
|
|
807
|
+
} catch (e) {
|
|
808
|
+
observation = [
|
|
809
|
+
`Previous thought: ${decision.thought || '(none)'}`,
|
|
810
|
+
`Action: ${action || '(none)'}`,
|
|
811
|
+
'Observation:',
|
|
812
|
+
`Error: ${e.message}`
|
|
813
|
+
].join('\n');
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
672
816
|
|
|
673
817
|
// Immediately show the agent's thought/reasoning
|
|
674
818
|
onProgress({
|
|
@@ -682,6 +826,16 @@ async function executeCodeTask(task, options = {}) {
|
|
|
682
826
|
finalSessionSummary = input.sessionSummary || input.summary || task;
|
|
683
827
|
finalSummary = input.summary || 'Task complete.';
|
|
684
828
|
finalVerification = input.verification || 'Not specified.';
|
|
829
|
+
if (onFinalSummary) {
|
|
830
|
+
await onFinalSummary({
|
|
831
|
+
summary: finalSummary,
|
|
832
|
+
verification: finalVerification,
|
|
833
|
+
providerInfo: {
|
|
834
|
+
provider: client.lastSuccessfulProvider || client.provider || provider,
|
|
835
|
+
model: getCodeProviderModel(client.lastSuccessfulProvider || client.provider || provider, config)
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
}
|
|
685
839
|
writeWorkspaceSession(workspaceRoot, {
|
|
686
840
|
summary: finalSessionSummary,
|
|
687
841
|
lastTask: task,
|
|
@@ -785,10 +939,28 @@ async function executeCodeTask(task, options = {}) {
|
|
|
785
939
|
case 'create_folder':
|
|
786
940
|
case 'system_info':
|
|
787
941
|
case 'system_automation': {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
942
|
+
const executorAction = normalizeExecutorAction(action, input);
|
|
943
|
+
const safety = safetyManager.classifyAction(executorAction);
|
|
944
|
+
let allowDangerous = false;
|
|
945
|
+
let allowApproval = false;
|
|
946
|
+
if (safety.tier === safetyManager.TIERS.APPROVAL || safety.tier === safetyManager.TIERS.DANGEROUS) {
|
|
947
|
+
const approved = await requestApproval({
|
|
948
|
+
type: action,
|
|
949
|
+
label: formatActionPreview(action, input),
|
|
950
|
+
preview: `${action}: ${formatActionPreview(action, input)}\nSafety: ${safety.tier} (${safety.reason})`
|
|
951
|
+
});
|
|
952
|
+
if (!approved) {
|
|
953
|
+
toolResult = `User denied ${action}: ${formatActionPreview(action, input)}`;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
allowApproval = safety.tier === safetyManager.TIERS.APPROVAL;
|
|
957
|
+
allowDangerous = safety.tier === safetyManager.TIERS.DANGEROUS;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
toolResult = await executeAction(executorAction, {
|
|
961
|
+
source: 'code_agent',
|
|
962
|
+
allowApproval,
|
|
963
|
+
allowDangerous
|
|
792
964
|
});
|
|
793
965
|
break;
|
|
794
966
|
} default:
|
|
@@ -797,6 +969,22 @@ async function executeCodeTask(task, options = {}) {
|
|
|
797
969
|
toolResult = `Error: ${e.message}`;
|
|
798
970
|
}
|
|
799
971
|
|
|
972
|
+
const evaluation = evaluateActionResult(action, toolResult);
|
|
973
|
+
if (evaluation) {
|
|
974
|
+
onProgress({
|
|
975
|
+
step,
|
|
976
|
+
phase: 'evaluating',
|
|
977
|
+
action: 'evaluator',
|
|
978
|
+
message: `${evaluation.status}: ${evaluation.message}`
|
|
979
|
+
});
|
|
980
|
+
toolResult = [
|
|
981
|
+
toolResult,
|
|
982
|
+
'',
|
|
983
|
+
'Evaluation:',
|
|
984
|
+
`${evaluation.status}: ${evaluation.message}`
|
|
985
|
+
].join('\n');
|
|
986
|
+
}
|
|
987
|
+
|
|
800
988
|
// Log the finished step with result
|
|
801
989
|
let resultSummary = '';
|
|
802
990
|
if (action === 'search_code') {
|
|
@@ -858,6 +1046,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
858
1046
|
}
|
|
859
1047
|
|
|
860
1048
|
if (finalSummary) {
|
|
1049
|
+
memoryStore.recordInteraction(task, finalSummary);
|
|
861
1050
|
const answeredProvider = client.lastSuccessfulProvider || client.provider || provider;
|
|
862
1051
|
return {
|
|
863
1052
|
summary: finalSummary,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const IMAGE_MIME_TYPES = {
|
|
6
|
+
'.png': 'image/png',
|
|
7
|
+
'.jpg': 'image/jpeg',
|
|
8
|
+
'.jpeg': 'image/jpeg',
|
|
9
|
+
'.webp': 'image/webp',
|
|
10
|
+
'.gif': 'image/gif'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function resolveImagePath(imagePath, cwd = process.cwd()) {
|
|
14
|
+
if (!imagePath || typeof imagePath !== 'string') {
|
|
15
|
+
throw new Error('Image path is required.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return path.resolve(cwd, imagePath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getImageMimeType(imagePath) {
|
|
22
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
23
|
+
const mimeType = IMAGE_MIME_TYPES[ext];
|
|
24
|
+
if (!mimeType) {
|
|
25
|
+
throw new Error(`Unsupported image type "${ext || '(none)'}". Supported: ${Object.keys(IMAGE_MIME_TYPES).join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
return mimeType;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadImageAsDataUri(imagePath, cwd = process.cwd()) {
|
|
31
|
+
const resolved = resolveImagePath(imagePath, cwd);
|
|
32
|
+
if (!fs.existsSync(resolved)) {
|
|
33
|
+
throw new Error(`Image file not found: ${imagePath}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stat = fs.statSync(resolved);
|
|
37
|
+
if (!stat.isFile()) {
|
|
38
|
+
throw new Error(`Image path is not a file: ${imagePath}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const mimeType = getImageMimeType(resolved);
|
|
42
|
+
const data = fs.readFileSync(resolved).toString('base64');
|
|
43
|
+
return {
|
|
44
|
+
path: resolved,
|
|
45
|
+
mimeType,
|
|
46
|
+
dataUri: `data:${mimeType};base64,${data}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tryReadClipboardCommand(command, args) {
|
|
51
|
+
try {
|
|
52
|
+
return execFileSync(command, args, {
|
|
53
|
+
encoding: 'buffer',
|
|
54
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
55
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
56
|
+
});
|
|
57
|
+
} catch (_) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadClipboardImageAsDataUri() {
|
|
63
|
+
const attempts = [
|
|
64
|
+
{ command: 'wl-paste', args: ['--type', 'image/png', '--no-newline'] },
|
|
65
|
+
{ command: 'xclip', args: ['-selection', 'clipboard', '-t', 'image/png', '-o'] }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const attempt of attempts) {
|
|
69
|
+
const data = tryReadClipboardCommand(attempt.command, attempt.args);
|
|
70
|
+
if (data && data.length > 0) {
|
|
71
|
+
return {
|
|
72
|
+
path: 'clipboard',
|
|
73
|
+
mimeType: 'image/png',
|
|
74
|
+
dataUri: `data:image/png;base64,${data.toString('base64')}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error('No clipboard image found. On Linux, install wl-clipboard or xclip, then copy an image and try Ctrl+V again.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
loadImageAsDataUri,
|
|
84
|
+
loadClipboardImageAsDataUri,
|
|
85
|
+
_helpers: {
|
|
86
|
+
getImageMimeType,
|
|
87
|
+
resolveImagePath,
|
|
88
|
+
tryReadClipboardCommand
|
|
89
|
+
}
|
|
90
|
+
};
|
package/src/CLI/onboarding.js
CHANGED
|
@@ -4,6 +4,35 @@ const { readConfig, writeConfig } = require('../System/config_manager');
|
|
|
4
4
|
const { installDaemon } = require('../System/daemon_manager');
|
|
5
5
|
const { runGmailAuth } = require('./gmail_auth');
|
|
6
6
|
|
|
7
|
+
const CUSTOM_MODEL_VALUE = '__custom_model__';
|
|
8
|
+
const ANTHROPIC_MODEL_CHOICES = [
|
|
9
|
+
'claude-3-5-sonnet-latest',
|
|
10
|
+
'claude-3-opus-latest',
|
|
11
|
+
'claude-3-5-haiku-latest'
|
|
12
|
+
];
|
|
13
|
+
const OPENAI_MODEL_CHOICES = [
|
|
14
|
+
'gpt-4o',
|
|
15
|
+
'gpt-4o-mini',
|
|
16
|
+
'o1-preview',
|
|
17
|
+
'o1-mini'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function buildModelChoices(models, currentModel) {
|
|
21
|
+
const choices = models.map(model => ({ name: model, value: model }));
|
|
22
|
+
if (currentModel && !models.includes(currentModel)) {
|
|
23
|
+
choices.push({ name: `${currentModel} (current)`, value: currentModel });
|
|
24
|
+
}
|
|
25
|
+
choices.push({ name: 'Custom...', value: CUSTOM_MODEL_VALUE });
|
|
26
|
+
return choices;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveCustomModelSelection(answers, modelKey, customKey, fallbackModel) {
|
|
30
|
+
if (answers[modelKey] !== CUSTOM_MODEL_VALUE) return;
|
|
31
|
+
const customModel = typeof answers[customKey] === 'string' ? answers[customKey].trim() : '';
|
|
32
|
+
answers[modelKey] = customModel || fallbackModel;
|
|
33
|
+
delete answers[customKey];
|
|
34
|
+
}
|
|
35
|
+
|
|
7
36
|
/**
|
|
8
37
|
* Onboarding Wizard for Mint CLI
|
|
9
38
|
*/
|
|
@@ -56,10 +85,10 @@ async function runOnboarding(options = {}) {
|
|
|
56
85
|
{ name: 'Gmail API', value: 'gmail', checked: config.pluginGmailEnabled },
|
|
57
86
|
{ name: 'Notion API', value: 'notion', checked: config.pluginNotionEnabled },
|
|
58
87
|
new inquirer.Separator(),
|
|
59
|
-
{ name: 'Anthropic (Claude)', value: 'anthropic', checked:
|
|
60
|
-
{ name: 'OpenAI (GPT-4o)', value: 'openai', checked:
|
|
61
|
-
{ name: 'Hugging Face', value: 'hf', checked:
|
|
62
|
-
{ name: 'Local AI (LM Studio/Ollama)', value: 'local', checked:
|
|
88
|
+
{ name: 'Anthropic (Claude)', value: 'anthropic', checked: !!config.anthropicApiKey },
|
|
89
|
+
{ name: 'OpenAI (GPT-4o)', value: 'openai', checked: !!config.openaiApiKey },
|
|
90
|
+
{ name: 'Hugging Face', value: 'hf', checked: !!config.hfApiKey },
|
|
91
|
+
{ name: 'Local AI (LM Studio/Ollama)', value: 'local', checked: !!(config.localApiBaseUrl && config.localApiBaseUrl.length > 0) },
|
|
63
92
|
new inquirer.Separator(),
|
|
64
93
|
{ name: 'Google Search API', value: 'google_search', checked: !!config.googleSearchApiKey },
|
|
65
94
|
{ name: 'Brave Search API', value: 'brave_search', checked: !!config.braveSearchApiKey },
|
|
@@ -257,7 +286,23 @@ async function runOnboarding(options = {}) {
|
|
|
257
286
|
type: 'input',
|
|
258
287
|
name: 'anthropicApiKey',
|
|
259
288
|
message: 'Enter Anthropic API Key:',
|
|
260
|
-
default: config.anthropicApiKey
|
|
289
|
+
default: config.anthropicApiKey,
|
|
290
|
+
validate: (input) => input.trim().length > 0 ? true : 'Anthropic API Key is required when Anthropic is selected.'
|
|
291
|
+
});
|
|
292
|
+
dynamicQuestions.push({
|
|
293
|
+
type: 'list',
|
|
294
|
+
name: 'anthropicModel',
|
|
295
|
+
message: 'Select Anthropic model:',
|
|
296
|
+
choices: buildModelChoices(ANTHROPIC_MODEL_CHOICES, config.anthropicModel),
|
|
297
|
+
default: config.anthropicModel || 'claude-3-5-sonnet-latest'
|
|
298
|
+
});
|
|
299
|
+
dynamicQuestions.push({
|
|
300
|
+
type: 'input',
|
|
301
|
+
name: 'anthropicModelCustom',
|
|
302
|
+
message: 'Enter custom Anthropic model:',
|
|
303
|
+
default: config.anthropicModel || 'claude-3-5-sonnet-latest',
|
|
304
|
+
when: (answers) => answers.anthropicModel === CUSTOM_MODEL_VALUE,
|
|
305
|
+
validate: (input) => input.trim().length > 0 ? true : 'Model name is required.'
|
|
261
306
|
});
|
|
262
307
|
}
|
|
263
308
|
|
|
@@ -266,7 +311,23 @@ async function runOnboarding(options = {}) {
|
|
|
266
311
|
type: 'input',
|
|
267
312
|
name: 'openaiApiKey',
|
|
268
313
|
message: 'Enter OpenAI API Key:',
|
|
269
|
-
default: config.openaiApiKey
|
|
314
|
+
default: config.openaiApiKey,
|
|
315
|
+
validate: (input) => input.trim().length > 0 ? true : 'OpenAI API Key is required when OpenAI is selected.'
|
|
316
|
+
});
|
|
317
|
+
dynamicQuestions.push({
|
|
318
|
+
type: 'list',
|
|
319
|
+
name: 'openaiModel',
|
|
320
|
+
message: 'Select OpenAI model:',
|
|
321
|
+
choices: buildModelChoices(OPENAI_MODEL_CHOICES, config.openaiModel),
|
|
322
|
+
default: config.openaiModel || 'gpt-4o'
|
|
323
|
+
});
|
|
324
|
+
dynamicQuestions.push({
|
|
325
|
+
type: 'input',
|
|
326
|
+
name: 'openaiModelCustom',
|
|
327
|
+
message: 'Enter custom OpenAI model:',
|
|
328
|
+
default: config.openaiModel || 'gpt-4o',
|
|
329
|
+
when: (answers) => answers.openaiModel === CUSTOM_MODEL_VALUE,
|
|
330
|
+
validate: (input) => input.trim().length > 0 ? true : 'Model name is required.'
|
|
270
331
|
});
|
|
271
332
|
}
|
|
272
333
|
|
|
@@ -297,19 +358,15 @@ async function runOnboarding(options = {}) {
|
|
|
297
358
|
|
|
298
359
|
if (dynamicQuestions.length > 0) {
|
|
299
360
|
const extraAnswers = await inquirer.prompt(dynamicQuestions);
|
|
361
|
+
resolveCustomModelSelection(extraAnswers, 'anthropicModel', 'anthropicModelCustom', config.anthropicModel || 'claude-3-5-sonnet-latest');
|
|
362
|
+
resolveCustomModelSelection(extraAnswers, 'openaiModel', 'openaiModelCustom', config.openaiModel || 'gpt-4o');
|
|
300
363
|
config = { ...config, ...extraAnswers };
|
|
301
364
|
|
|
302
365
|
}
|
|
303
366
|
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
if (selections.includes('anthropic')) config.aiProvider = 'anthropic';
|
|
308
|
-
else if (selections.includes('openai')) config.aiProvider = 'openai';
|
|
309
|
-
else if (selections.includes('hf')) config.aiProvider = 'huggingface';
|
|
310
|
-
else if (selections.includes('local')) config.aiProvider = 'local_openai';
|
|
311
|
-
else config.aiProvider = 'gemini';
|
|
312
|
-
}
|
|
367
|
+
// Onboarding treats Gemini as the primary AI. Other providers are optional
|
|
368
|
+
// configured backends that become available only when credentials are set.
|
|
369
|
+
config.aiProvider = 'gemini';
|
|
313
370
|
|
|
314
371
|
// Save configuration
|
|
315
372
|
writeConfig(config);
|
package/src/CLI/updater.js
CHANGED
|
@@ -74,7 +74,8 @@ function shouldRunAutoUpdate(config = {}, now = Date.now()) {
|
|
|
74
74
|
|
|
75
75
|
async function getLatestVersion(packageName = pkg.name) {
|
|
76
76
|
const { stdout } = await execFilePromise(NPM_COMMAND, ['view', packageName, 'version', '--json'], {
|
|
77
|
-
maxBuffer: 1024 * 1024
|
|
77
|
+
maxBuffer: 1024 * 1024,
|
|
78
|
+
timeout: 30000
|
|
78
79
|
});
|
|
79
80
|
return normalizeNpmVersionOutput(stdout);
|
|
80
81
|
}
|
|
@@ -86,7 +87,8 @@ async function installLatest(packageName = pkg.name, options = {}) {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
return await execFilePromise(NPM_COMMAND, args, {
|
|
89
|
-
maxBuffer: 1024 * 1024 * 8
|
|
90
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
91
|
+
timeout: 5 * 60 * 1000
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -182,14 +184,14 @@ async function runStartupAutoUpdate(config, writeConfig, options = {}) {
|
|
|
182
184
|
};
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
const result = await runUpdate({ checkOnly: false });
|
|
185
188
|
if (typeof writeConfig === 'function') {
|
|
186
189
|
writeConfig({
|
|
187
190
|
...config,
|
|
188
191
|
lastUpdateCheckAt: new Date(now).toISOString()
|
|
189
192
|
});
|
|
190
193
|
}
|
|
191
|
-
|
|
192
|
-
return await runUpdate({ checkOnly: false });
|
|
194
|
+
return result;
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
module.exports = {
|