@pheem49/mint 1.5.0 → 1.5.2
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 +35 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +201 -500
- 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 +40 -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 +15 -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 +40 -17
- 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/AI_Brain/proactive_engine.js +12 -2
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +42 -0
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +583 -52
- package/src/CLI/cli_colors.js +32 -0
- package/src/CLI/cli_formatters.js +89 -0
- package/src/CLI/code_agent.js +369 -71
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +479 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/CLI/updater.js +6 -4
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/action_executor.js +59 -10
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/optional_require.js +23 -0
- 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 +566 -0
- package/src/UI/renderer.js +339 -21
- package/src/UI/settings.css +655 -420
- package/src/UI/settings.html +478 -432
- package/src/UI/settings.js +10 -8
- package/src/UI/styles.css +516 -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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { colors, exitWithGoodbye } = require('./cli_colors');
|
|
7
|
+
const { formatMemoryInteractions } = require('./cli_formatters');
|
|
8
|
+
const { learnSkillFile } = require('./skill_manager');
|
|
9
|
+
const { loadImageAsDataUri, loadClipboardImageAsDataUri } = require('./image_input');
|
|
10
|
+
const { runChatRoutedTask } = require('./chat_router');
|
|
11
|
+
const { getChatTranscript, resetChat } = require('../AI_Brain/Gemini_API');
|
|
12
|
+
const memoryStore = require('../AI_Brain/memory_store');
|
|
13
|
+
const agentOrchestrator = require('../AI_Brain/agent_orchestrator');
|
|
14
|
+
const { readConfig, writeConfig } = require('../System/config_manager');
|
|
15
|
+
const pkg = require('../../package.json');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handles all slash commands entered inside the interactive TUI.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} input Full slash command string (e.g. "/memory list")
|
|
21
|
+
* @param {Function} appendMessage
|
|
22
|
+
* @param {Function} updateStatusModel
|
|
23
|
+
* @param {Function} copyLastResponse
|
|
24
|
+
* @param {Function} setThinking
|
|
25
|
+
* @param {Function} requestApproval
|
|
26
|
+
* @param {Function} setMode
|
|
27
|
+
* @param {Function} appendCodeStep
|
|
28
|
+
* @param {Function} updateWorkspace
|
|
29
|
+
* @param {object} helpers Extra helpers injected from interactive_chat
|
|
30
|
+
* @returns {Promise<object|undefined>} May return { lastResponseText } for some commands
|
|
31
|
+
*/
|
|
32
|
+
async function handleSlashCommandUI(
|
|
33
|
+
input,
|
|
34
|
+
appendMessage,
|
|
35
|
+
updateStatusModel,
|
|
36
|
+
copyLastResponse,
|
|
37
|
+
setThinking,
|
|
38
|
+
requestApproval,
|
|
39
|
+
setMode,
|
|
40
|
+
appendCodeStep,
|
|
41
|
+
updateWorkspace,
|
|
42
|
+
helpers = {}
|
|
43
|
+
) {
|
|
44
|
+
const parts = input.split(' ');
|
|
45
|
+
const command = parts[0].toLowerCase();
|
|
46
|
+
const args = parts.slice(1);
|
|
47
|
+
|
|
48
|
+
switch (command) {
|
|
49
|
+
// ------------------------------------------------------------------ /help
|
|
50
|
+
case '/help':
|
|
51
|
+
case '/?':
|
|
52
|
+
appendMessage('system', [
|
|
53
|
+
'Mint Slash Commands:',
|
|
54
|
+
' /image <path> [prompt] — Attach an image from your computer',
|
|
55
|
+
' /paste [prompt] — Attach an image from your clipboard',
|
|
56
|
+
' /fast [on|off] — Hide or show thinking/progress output',
|
|
57
|
+
' /summarize [path] [--json] — Summarize repository structure',
|
|
58
|
+
' /symbols [path] [--json] [--limit n] — Build a source symbol index',
|
|
59
|
+
' /semantic-code index|search <query> — Embed and search code semantically',
|
|
60
|
+
' /learn <path> — Remember a .md/.txt file as a Mint skill',
|
|
61
|
+
' /code <task> — Force workspace Code Mode',
|
|
62
|
+
' /cd <path> — Change current working directory',
|
|
63
|
+
' /models [name] — List or switch Gemini models',
|
|
64
|
+
' /memory [cmd] — Manage long-term memory',
|
|
65
|
+
' /config — Show current configuration',
|
|
66
|
+
' /copy — Copy last response to clipboard',
|
|
67
|
+
' /clear — Clear conversation history',
|
|
68
|
+
' /reset — Reset conversation history',
|
|
69
|
+
' /exit — Exit Mint'
|
|
70
|
+
].join('\n'));
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
// ------------------------------------------------------------------ /fast
|
|
74
|
+
case '/fast': {
|
|
75
|
+
if (!helpers.toggleFastMode || !helpers.setFastMode || !helpers.getFastMode) {
|
|
76
|
+
appendMessage('error', 'Fast mode is not available in this UI.');
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const option = (args[0] || '').toLowerCase();
|
|
80
|
+
let enabled;
|
|
81
|
+
if (option === 'on' || option === 'true' || option === '1') enabled = helpers.setFastMode(true);
|
|
82
|
+
else if (option === 'off' || option === 'false' || option === '0') enabled = helpers.setFastMode(false);
|
|
83
|
+
else if (option === 'status') enabled = helpers.getFastMode();
|
|
84
|
+
else enabled = helpers.toggleFastMode();
|
|
85
|
+
|
|
86
|
+
appendMessage('system', `Fast mode: ${enabled ? 'ON' : 'OFF'}`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ------------------------------------------------------------------ /summarize
|
|
91
|
+
case '/summarize':
|
|
92
|
+
case '/summary': {
|
|
93
|
+
if (typeof helpers.sendRepoSummaryMessage !== 'function') {
|
|
94
|
+
appendMessage('error', 'Repository summary is not available in this UI.');
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
const responseText = await helpers.sendRepoSummaryMessage({
|
|
98
|
+
rawArgs: input.slice(command.length).trim(),
|
|
99
|
+
appendMessage,
|
|
100
|
+
streamMessage: helpers.streamMessage,
|
|
101
|
+
setThinking
|
|
102
|
+
});
|
|
103
|
+
return { lastResponseText: responseText };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ------------------------------------------------------------------ /symbols
|
|
107
|
+
case '/symbols':
|
|
108
|
+
case '/symbol-index': {
|
|
109
|
+
if (typeof helpers.sendSymbolIndexMessage !== 'function') {
|
|
110
|
+
appendMessage('error', 'Symbol index is not available in this UI.');
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const responseText = await helpers.sendSymbolIndexMessage({
|
|
114
|
+
rawArgs: input.slice(command.length).trim(),
|
|
115
|
+
appendMessage,
|
|
116
|
+
streamMessage: helpers.streamMessage,
|
|
117
|
+
setThinking
|
|
118
|
+
});
|
|
119
|
+
return { lastResponseText: responseText };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ------------------------------------------------------------------ /semantic-code
|
|
123
|
+
case '/semantic-code':
|
|
124
|
+
case '/semantic': {
|
|
125
|
+
if (typeof helpers.sendSemanticCodeMessage !== 'function') {
|
|
126
|
+
appendMessage('error', 'Semantic code search is not available in this UI.');
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const responseText = await helpers.sendSemanticCodeMessage({
|
|
130
|
+
rawArgs: input.slice(command.length).trim(),
|
|
131
|
+
appendMessage,
|
|
132
|
+
streamMessage: helpers.streamMessage,
|
|
133
|
+
setThinking,
|
|
134
|
+
appendCodeStep
|
|
135
|
+
});
|
|
136
|
+
return { lastResponseText: responseText };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ------------------------------------------------------------------ /learn
|
|
140
|
+
case '/learn': {
|
|
141
|
+
const filePath = input.slice(command.length).trim();
|
|
142
|
+
if (!filePath) {
|
|
143
|
+
appendMessage('system', 'Usage: /learn <path-to-skill.md>');
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const learned = learnSkillFile(filePath);
|
|
148
|
+
appendMessage('system', [
|
|
149
|
+
`✓ Learned skill: ${learned.name}`,
|
|
150
|
+
`Path: ${learned.source_path}`,
|
|
151
|
+
learned.stored_length < learned.content_length
|
|
152
|
+
? `Stored first ${learned.stored_length} of ${learned.content_length} characters.`
|
|
153
|
+
: `Stored ${learned.stored_length} characters.`
|
|
154
|
+
].join('\n'));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
appendMessage('error', err && err.message ? err.message : String(err || 'Unknown error'));
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ------------------------------------------------------------------ /image
|
|
162
|
+
case '/image': {
|
|
163
|
+
if (args.length === 0) {
|
|
164
|
+
appendMessage('system', 'Usage: /image <path> [prompt]');
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
const imagePath = args[0];
|
|
168
|
+
const prompt = args.slice(1).join(' ').trim();
|
|
169
|
+
try {
|
|
170
|
+
const image = loadImageAsDataUri(imagePath);
|
|
171
|
+
if (helpers.attachImage) {
|
|
172
|
+
helpers.attachImage({ label: image.path, image });
|
|
173
|
+
if (prompt && helpers.setInputText) helpers.setInputText(prompt);
|
|
174
|
+
appendMessage('system', 'Attached image. Press Enter to send.');
|
|
175
|
+
} else {
|
|
176
|
+
appendMessage('error', 'Image attachment is not available in this UI.');
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
appendMessage('error', err && err.message ? err.message : String(err || 'Unknown error'));
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ------------------------------------------------------------------ /paste
|
|
185
|
+
case '/paste': {
|
|
186
|
+
try {
|
|
187
|
+
const image = loadClipboardImageAsDataUri();
|
|
188
|
+
if (helpers.attachImage) {
|
|
189
|
+
helpers.attachImage({ label: image.path, image });
|
|
190
|
+
const prompt = args.join(' ').trim();
|
|
191
|
+
if (prompt && helpers.setInputText) helpers.setInputText(prompt);
|
|
192
|
+
appendMessage('system', 'Attached clipboard image. Press Enter to send.');
|
|
193
|
+
} else {
|
|
194
|
+
appendMessage('error', 'Image attachment is not available in this UI.');
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
const msg = helpers.formatErrorMessage
|
|
198
|
+
? helpers.formatErrorMessage(err)
|
|
199
|
+
: (err && err.message ? err.message : String(err || 'Unknown error'));
|
|
200
|
+
appendMessage('error', msg);
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ------------------------------------------------------------------ /memory
|
|
206
|
+
case '/memory': {
|
|
207
|
+
const subCommand = (args[0] || 'list').toLowerCase();
|
|
208
|
+
const query = args.slice(1).join(' ').trim();
|
|
209
|
+
|
|
210
|
+
if (subCommand === 'help') {
|
|
211
|
+
appendMessage('system', [
|
|
212
|
+
'Memory Commands:',
|
|
213
|
+
' /memory list [n] — Show recent remembered interactions',
|
|
214
|
+
' /memory search <query> — Search remembered interactions',
|
|
215
|
+
' /memory skills — Show learned skill files',
|
|
216
|
+
' /memory skills delete <id|path|name> — Delete a learned skill',
|
|
217
|
+
' /memory profile — Show remembered profile fields',
|
|
218
|
+
' /memory context [q] — Show context Mint injects into prompts',
|
|
219
|
+
' /memory delete <id> — Delete one remembered interaction',
|
|
220
|
+
' /memory export [path] — Export memory snapshot as JSON',
|
|
221
|
+
' /memory clear — Clear episodic interaction memories'
|
|
222
|
+
].join('\n'));
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (subCommand === 'profile') {
|
|
227
|
+
const profile = memoryStore.getAllProfile();
|
|
228
|
+
appendMessage('system', Object.keys(profile).length
|
|
229
|
+
? JSON.stringify(profile, null, 2)
|
|
230
|
+
: 'No profile memory stored yet.');
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (subCommand === 'skills') {
|
|
235
|
+
if ((args[1] || '').toLowerCase() === 'delete') {
|
|
236
|
+
const identifier = args.slice(2).join(' ').trim();
|
|
237
|
+
if (!identifier) {
|
|
238
|
+
appendMessage('system', 'Usage: /memory skills delete <id|path|name>');
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
const deleted = memoryStore.deleteLearnedSkill(identifier);
|
|
242
|
+
appendMessage('system', deleted > 0
|
|
243
|
+
? `Deleted learned skill: ${identifier}`
|
|
244
|
+
: `Learned skill not found: ${identifier}`);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
const skills = memoryStore.getLearnedSkills(20);
|
|
248
|
+
appendMessage('system', skills.length
|
|
249
|
+
? ['Learned skills:', ...skills.map(s => `#${s.id} ${s.name}\n ${s.source_path}`)].join('\n')
|
|
250
|
+
: 'No learned skills stored yet.');
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (subCommand === 'context') {
|
|
255
|
+
const ctx = memoryStore.getUserContext(query);
|
|
256
|
+
appendMessage('system', ctx || 'No memory context stored yet.');
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (subCommand === 'search') {
|
|
261
|
+
if (!query) { appendMessage('system', 'Usage: /memory search <query>'); break; }
|
|
262
|
+
const results = memoryStore.searchInteractions(query, 10);
|
|
263
|
+
appendMessage('system', formatMemoryInteractions(results, `Search results for "${query}"`));
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (subCommand === 'export') {
|
|
268
|
+
const exportPath = query
|
|
269
|
+
? path.resolve(process.cwd(), query)
|
|
270
|
+
: path.join(process.cwd(), `mint-memory-export-${Date.now()}.json`);
|
|
271
|
+
fs.writeFileSync(exportPath, JSON.stringify(memoryStore.exportMemorySnapshot(), null, 2), 'utf8');
|
|
272
|
+
appendMessage('system', `Memory exported to: ${exportPath}`);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (subCommand === 'delete') {
|
|
277
|
+
const id = Number.parseInt(args[1] || '', 10);
|
|
278
|
+
if (!Number.isFinite(id)) { appendMessage('system', 'Usage: /memory delete <id>'); break; }
|
|
279
|
+
const deleted = memoryStore.deleteInteractionMemory(id);
|
|
280
|
+
appendMessage('system', deleted ? `Deleted memory #${id}.` : `Memory #${id} was not found.`);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (subCommand === 'clear') {
|
|
285
|
+
memoryStore.clearInteractionMemories();
|
|
286
|
+
appendMessage('system', 'Cleared episodic interaction memories. Profile memory is unchanged.');
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Default: list recent
|
|
291
|
+
const limit = Number.parseInt(args[0] || '10', 10);
|
|
292
|
+
const interactions = memoryStore.getRecentInteractions(Number.isFinite(limit) ? limit : 10);
|
|
293
|
+
appendMessage('system', formatMemoryInteractions(interactions, 'Recent remembered interactions'));
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ------------------------------------------------------------------ /cd
|
|
298
|
+
case '/cd':
|
|
299
|
+
if (args.length === 0) {
|
|
300
|
+
appendMessage('system', `Current Directory: ${process.cwd()}`);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const newPath = path.resolve(process.cwd(), args[0]);
|
|
305
|
+
if (fs.existsSync(newPath) && fs.lstatSync(newPath).isDirectory()) {
|
|
306
|
+
process.chdir(newPath);
|
|
307
|
+
if (updateWorkspace) updateWorkspace(newPath);
|
|
308
|
+
appendMessage('system', `✓ Directory changed to: ${newPath}`);
|
|
309
|
+
} else {
|
|
310
|
+
appendMessage('error', `Directory not found: ${newPath}`);
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
appendMessage('error', `Error: ${err.message}`);
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
// ------------------------------------------------------------------ /models
|
|
318
|
+
case '/model':
|
|
319
|
+
case '/models': {
|
|
320
|
+
const config = readConfig();
|
|
321
|
+
if (args.length === 0) {
|
|
322
|
+
appendMessage('system', [
|
|
323
|
+
`Current Provider: ${config.aiProvider}`,
|
|
324
|
+
`Current Gemini Model: ${config.geminiModel}`,
|
|
325
|
+
'Available Providers/Presets:',
|
|
326
|
+
' - gemini-2.5-flash (Default Gemini)',
|
|
327
|
+
' - ollama (Local provider)',
|
|
328
|
+
' - anthropic (Claude)',
|
|
329
|
+
' - openai (GPT)',
|
|
330
|
+
' - huggingface (Inference API)',
|
|
331
|
+
' - local (LM Studio / OpenAI Compatible)',
|
|
332
|
+
'Usage: /models <name> to switch'
|
|
333
|
+
].join('\n'));
|
|
334
|
+
} else {
|
|
335
|
+
const newModel = args[0];
|
|
336
|
+
let newProvider = 'gemini';
|
|
337
|
+
|
|
338
|
+
if (newModel === 'ollama') newProvider = 'ollama';
|
|
339
|
+
else if (newModel === 'anthropic') newProvider = 'anthropic';
|
|
340
|
+
else if (newModel === 'openai') newProvider = 'openai';
|
|
341
|
+
else if (newModel === 'huggingface') newProvider = 'huggingface';
|
|
342
|
+
else if (newModel === 'local' || newModel === 'local_openai') newProvider = 'local_openai';
|
|
343
|
+
else if (newModel.startsWith('gpt-')) { newProvider = 'openai'; config.openaiModel = newModel; }
|
|
344
|
+
else if (newModel.startsWith('claude-')) { newProvider = 'anthropic'; config.anthropicModel = newModel; }
|
|
345
|
+
else { newProvider = 'gemini'; config.geminiModel = newModel; }
|
|
346
|
+
|
|
347
|
+
config.aiProvider = newProvider;
|
|
348
|
+
writeConfig(config);
|
|
349
|
+
appendMessage('system', `✅ Switched to: ${newProvider} ${newProvider === 'gemini' ? `(${newModel})` : ''}`);
|
|
350
|
+
if (updateStatusModel) updateStatusModel(newProvider === 'gemini' ? newModel : newProvider);
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ------------------------------------------------------------------ /code
|
|
356
|
+
case '/code':
|
|
357
|
+
if (args.length === 0) {
|
|
358
|
+
appendMessage('system', 'Usage: /code <task>');
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
await runChatRoutedTask(`/code ${args.join(' ')}`, {
|
|
362
|
+
appendMessage,
|
|
363
|
+
setThinking,
|
|
364
|
+
requestApproval,
|
|
365
|
+
appendCodeStep,
|
|
366
|
+
setMode,
|
|
367
|
+
streamAssistantSentences: helpers.streamAssistantSentences,
|
|
368
|
+
streamMessage: helpers.streamMessage,
|
|
369
|
+
askUser: () => Promise.resolve(''),
|
|
370
|
+
history: await getChatTranscript()
|
|
371
|
+
});
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
// ------------------------------------------------------------------ /config
|
|
375
|
+
case '/config': {
|
|
376
|
+
const currentCfg = readConfig();
|
|
377
|
+
appendMessage('system', [
|
|
378
|
+
'Current Configuration:',
|
|
379
|
+
` Version : v${pkg.version}`,
|
|
380
|
+
` Provider : ${currentCfg.aiProvider}`,
|
|
381
|
+
` Model : ${currentCfg.geminiModel}`,
|
|
382
|
+
` Ollama : ${currentCfg.ollamaModel}`,
|
|
383
|
+
` Voice : ${currentCfg.enableVoiceReply ? 'ON' : 'OFF'}`,
|
|
384
|
+
` Language : ${currentCfg.language}`,
|
|
385
|
+
` API Key : ${currentCfg.apiKey ? 'SET (****)' : 'NOT SET'}`
|
|
386
|
+
].join('\n'));
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ------------------------------------------------------------------ /copy
|
|
391
|
+
case '/copy':
|
|
392
|
+
if (copyLastResponse && copyLastResponse()) {
|
|
393
|
+
appendMessage('system', '✓ Last response copied to clipboard.');
|
|
394
|
+
} else {
|
|
395
|
+
appendMessage('system', '✖ Nothing to copy, or xclip/xsel not installed.');
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
|
|
399
|
+
// ------------------------------------------------------------------ /clear /reset
|
|
400
|
+
case '/clear':
|
|
401
|
+
case '/reset':
|
|
402
|
+
resetChat();
|
|
403
|
+
appendMessage('system', 'Conversation history cleared.');
|
|
404
|
+
break;
|
|
405
|
+
|
|
406
|
+
// ------------------------------------------------------------------ /exit
|
|
407
|
+
case '/exit':
|
|
408
|
+
case '/quit':
|
|
409
|
+
exitWithGoodbye(0);
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
// ------------------------------------------------------------------ default
|
|
413
|
+
default:
|
|
414
|
+
appendMessage('system', `Unknown command: ${command}. Type /help for options.`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
module.exports = { handleSlashCommandUI };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const IGNORED_DIRS = new Set([
|
|
5
|
+
'.git',
|
|
6
|
+
'.cache',
|
|
7
|
+
'.next',
|
|
8
|
+
'.nuxt',
|
|
9
|
+
'coverage',
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'out',
|
|
13
|
+
'node_modules'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const LANGUAGE_BY_EXT = {
|
|
17
|
+
'.cjs': 'JavaScript',
|
|
18
|
+
'.js': 'JavaScript',
|
|
19
|
+
'.jsx': 'JavaScript',
|
|
20
|
+
'.mjs': 'JavaScript',
|
|
21
|
+
'.py': 'Python',
|
|
22
|
+
'.rs': 'Rust',
|
|
23
|
+
'.ts': 'TypeScript',
|
|
24
|
+
'.tsx': 'TypeScript'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SOURCE_EXTENSIONS = new Set(Object.keys(LANGUAGE_BY_EXT));
|
|
28
|
+
|
|
29
|
+
function walkSourceFiles(root, options = {}) {
|
|
30
|
+
const maxFiles = options.maxFiles || 2500;
|
|
31
|
+
const files = [];
|
|
32
|
+
|
|
33
|
+
function visit(dir) {
|
|
34
|
+
let entries = [];
|
|
35
|
+
try {
|
|
36
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
37
|
+
} catch (_) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (files.length >= maxFiles) return;
|
|
44
|
+
const fullPath = path.join(dir, entry.name);
|
|
45
|
+
const relativePath = path.relative(root, fullPath);
|
|
46
|
+
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
49
|
+
visit(fullPath);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
54
|
+
files.push(relativePath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
visit(root);
|
|
60
|
+
return files;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function makeSymbol({ name, kind, file, line, column, language, signature }) {
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
kind,
|
|
67
|
+
file,
|
|
68
|
+
line,
|
|
69
|
+
column,
|
|
70
|
+
language,
|
|
71
|
+
signature: signature.trim()
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function scanPattern(lines, file, language, pattern, kind, symbols) {
|
|
76
|
+
lines.forEach((lineText, index) => {
|
|
77
|
+
const match = lineText.match(pattern);
|
|
78
|
+
if (!match) return;
|
|
79
|
+
|
|
80
|
+
const name = match.groups?.name || match[1];
|
|
81
|
+
if (!name) return;
|
|
82
|
+
|
|
83
|
+
symbols.push(makeSymbol({
|
|
84
|
+
name,
|
|
85
|
+
kind,
|
|
86
|
+
file,
|
|
87
|
+
line: index + 1,
|
|
88
|
+
column: lineText.indexOf(name) + 1,
|
|
89
|
+
language,
|
|
90
|
+
signature: lineText.trim()
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function indexJavaScriptLike(content, file, language) {
|
|
96
|
+
const lines = content.split('\n');
|
|
97
|
+
const symbols = [];
|
|
98
|
+
|
|
99
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?(?:async\s+)?function\*?\s+(?<name>[A-Za-z_$][\w$]*)\s*\(/, 'function', symbols);
|
|
100
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?class\s+(?<name>[A-Za-z_$][\w$]*)\b/, 'class', symbols);
|
|
101
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?(?:const|let|var)\s+(?<name>[A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/, 'function', symbols);
|
|
102
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?(?:const|let|var)\s+(?<name>[A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\b/, 'function', symbols);
|
|
103
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?interface\s+(?<name>[A-Za-z_$][\w$]*)\b/, 'interface', symbols);
|
|
104
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?type\s+(?<name>[A-Za-z_$][\w$]*)\b/, 'type', symbols);
|
|
105
|
+
scanPattern(lines, file, language, /^\s*(?:export\s+)?enum\s+(?<name>[A-Za-z_$][\w$]*)\b/, 'enum', symbols);
|
|
106
|
+
scanPattern(lines, file, language, /^\s*(?:module\.)?exports\.(?<name>[A-Za-z_$][\w$]*)\s*=/, 'export', symbols);
|
|
107
|
+
|
|
108
|
+
return symbols;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function indexPython(content, file) {
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
const symbols = [];
|
|
114
|
+
|
|
115
|
+
scanPattern(lines, file, 'Python', /^\s*def\s+(?<name>[A-Za-z_]\w*)\s*\(/, 'function', symbols);
|
|
116
|
+
scanPattern(lines, file, 'Python', /^\s*async\s+def\s+(?<name>[A-Za-z_]\w*)\s*\(/, 'function', symbols);
|
|
117
|
+
scanPattern(lines, file, 'Python', /^\s*class\s+(?<name>[A-Za-z_]\w*)\b/, 'class', symbols);
|
|
118
|
+
|
|
119
|
+
return symbols;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function indexRust(content, file) {
|
|
123
|
+
const lines = content.split('\n');
|
|
124
|
+
const symbols = [];
|
|
125
|
+
|
|
126
|
+
scanPattern(lines, file, 'Rust', /^\s*(?:pub\s+)?(?:async\s+)?fn\s+(?<name>[A-Za-z_]\w*)\s*\(/, 'function', symbols);
|
|
127
|
+
scanPattern(lines, file, 'Rust', /^\s*(?:pub\s+)?struct\s+(?<name>[A-Za-z_]\w*)\b/, 'struct', symbols);
|
|
128
|
+
scanPattern(lines, file, 'Rust', /^\s*(?:pub\s+)?enum\s+(?<name>[A-Za-z_]\w*)\b/, 'enum', symbols);
|
|
129
|
+
scanPattern(lines, file, 'Rust', /^\s*(?:pub\s+)?trait\s+(?<name>[A-Za-z_]\w*)\b/, 'trait', symbols);
|
|
130
|
+
scanPattern(lines, file, 'Rust', /^\s*impl(?:\s+\w+)?\s+for\s+(?<name>[A-Za-z_]\w*)\b/, 'impl', symbols);
|
|
131
|
+
|
|
132
|
+
return symbols;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function indexFileSymbols(root, relativePath) {
|
|
136
|
+
const fullPath = path.join(root, relativePath);
|
|
137
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
138
|
+
const language = LANGUAGE_BY_EXT[ext] || 'Other';
|
|
139
|
+
let content = '';
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
content = fs.readFileSync(fullPath, 'utf8');
|
|
143
|
+
} catch (_) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (language === 'Python') return indexPython(content, relativePath);
|
|
148
|
+
if (language === 'Rust') return indexRust(content, relativePath);
|
|
149
|
+
return indexJavaScriptLike(content, relativePath, language);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function countBy(items, key) {
|
|
153
|
+
const counts = new Map();
|
|
154
|
+
for (const item of items) {
|
|
155
|
+
const value = item[key] || 'unknown';
|
|
156
|
+
counts.set(value, (counts.get(value) || 0) + 1);
|
|
157
|
+
}
|
|
158
|
+
return Array.from(counts.entries())
|
|
159
|
+
.map(([name, count]) => ({ name, count }))
|
|
160
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildSymbolIndex(targetPath = process.cwd(), options = {}) {
|
|
164
|
+
const root = path.resolve(targetPath);
|
|
165
|
+
const stat = fs.statSync(root);
|
|
166
|
+
if (!stat.isDirectory()) {
|
|
167
|
+
throw new Error(`Symbol index path is not a directory: ${root}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const files = walkSourceFiles(root, options);
|
|
171
|
+
const symbols = files.flatMap(file => indexFileSymbols(root, file));
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
root,
|
|
175
|
+
fileCount: files.length,
|
|
176
|
+
indexedFiles: [...new Set(symbols.map(symbol => symbol.file))].length,
|
|
177
|
+
symbolCount: symbols.length,
|
|
178
|
+
kindCounts: countBy(symbols, 'kind'),
|
|
179
|
+
languageCounts: countBy(symbols, 'language'),
|
|
180
|
+
symbols: symbols.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name))
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function formatSymbolIndex(index, options = {}) {
|
|
185
|
+
const limit = Number.isFinite(options.limit) ? options.limit : 80;
|
|
186
|
+
const shown = index.symbols.slice(0, limit);
|
|
187
|
+
const lines = [];
|
|
188
|
+
|
|
189
|
+
lines.push('# Symbol Index');
|
|
190
|
+
lines.push('');
|
|
191
|
+
lines.push(`Root: ${index.root}`);
|
|
192
|
+
lines.push(`Source files scanned: ${index.fileCount}`);
|
|
193
|
+
lines.push(`Files with symbols: ${index.indexedFiles}`);
|
|
194
|
+
lines.push(`Symbols found: ${index.symbolCount}`);
|
|
195
|
+
|
|
196
|
+
lines.push('');
|
|
197
|
+
lines.push('## By Kind');
|
|
198
|
+
lines.push(index.kindCounts.length
|
|
199
|
+
? index.kindCounts.map(item => `- ${item.name}: ${item.count}`).join('\n')
|
|
200
|
+
: '- (none)');
|
|
201
|
+
|
|
202
|
+
lines.push('');
|
|
203
|
+
lines.push('## By Language');
|
|
204
|
+
lines.push(index.languageCounts.length
|
|
205
|
+
? index.languageCounts.map(item => `- ${item.name}: ${item.count}`).join('\n')
|
|
206
|
+
: '- (none)');
|
|
207
|
+
|
|
208
|
+
lines.push('');
|
|
209
|
+
lines.push(`## Symbols${index.symbolCount > shown.length ? ` (first ${shown.length})` : ''}`);
|
|
210
|
+
if (shown.length === 0) {
|
|
211
|
+
lines.push('- (none)');
|
|
212
|
+
} else {
|
|
213
|
+
shown.forEach(symbol => {
|
|
214
|
+
lines.push(`- ${symbol.kind} ${symbol.name} (${symbol.file}:${symbol.line})`);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
buildSymbolIndex,
|
|
223
|
+
formatSymbolIndex,
|
|
224
|
+
_helpers: {
|
|
225
|
+
walkSourceFiles,
|
|
226
|
+
indexFileSymbols,
|
|
227
|
+
indexJavaScriptLike,
|
|
228
|
+
indexPython,
|
|
229
|
+
indexRust
|
|
230
|
+
}
|
|
231
|
+
};
|
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 = {
|