kiku-agent 1.3.2 → 1.3.4
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 +69 -12
- package/package.json +2 -1
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -0
- package/server/agent/eval/modules/evaluation/runner.js +4 -14
- package/server/agent/eval/modules/evaluation/scoring.js +15 -3
- package/server/agent/eval/modules/server/evalApi.js +5 -5
- package/server/agent/eval/modules/ui/evalUi.js +7 -4
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentFollowUps.test.js +128 -2
- package/server/agent/tests/agentMusicControl.test.js +245 -0
- package/server/agent/tests/apiRouter.test.js +1 -1
- package/server/agent/tests/autoContext.test.js +176 -0
- package/server/agent/tests/bilibiliEmbed.test.js +45 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
- package/server/agent/tests/clarifyingQuestions.test.js +33 -0
- package/server/agent/tests/cliAutomation.test.js +38 -1
- package/server/agent/tests/cloudProviders.test.js +91 -3
- package/server/agent/tests/commands.test.js +92 -3
- package/server/agent/tests/contextIntelligence.test.js +50 -3
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +32 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +82 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -0
- package/server/agent/tests/envCommand.test.js +69 -0
- package/server/agent/tests/evalMetrics.test.js +8 -0
- package/server/agent/tests/fetchApi.test.js +99 -0
- package/server/agent/tests/hybridWebSearch.test.js +2 -0
- package/server/agent/tests/lanDiscovery.test.js +5 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +32 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -0
- package/server/agent/tests/nextGenArchitecture.test.js +4 -2
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +10 -5
- package/server/agent/tests/quickActionsAndModals.test.js +180 -0
- package/server/agent/tests/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +16 -9
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
- package/server/agent/tests/securityHardening.test.js +4 -4
- package/server/agent/tests/sessionAndCopy.test.js +39 -4
- package/server/agent/tests/sidebarControls.test.js +34 -0
- package/server/agent/tests/statefulMemory.test.js +8 -1
- package/server/agent/tests/textProtocolLoop.test.js +71 -0
- package/server/agent/tests/textToolParsing.test.js +48 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +41 -0
- package/server/agent/tests/unifiedEndpoints.test.js +13 -6
- package/server/agent/tests/v110Features.test.js +1 -1
- package/server/agent/tests/vramEstimator.test.js +75 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +256 -5
- package/server/agent/tests/webChatMcp.test.js +94 -0
- package/server/agent/tests/webExtract.test.js +23 -0
- package/server/agent/tests/webSources.test.js +46 -12
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +14 -8
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +18 -18
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
package/server/agent/commands.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { promptInteractiveSuggestion } from './suggestionTui.js';
|
|
11
11
|
import { DebateOrchestrator } from './debate.js';
|
|
12
12
|
import { timelineData } from '../../src/data/timelineData.js';
|
|
13
|
-
import { COMMAND_NAMES, getCommandDetails, findCommandDefinition } from './commandRegistry.js';
|
|
13
|
+
import { COMMAND_NAMES, COMMAND_ALIASES, getCommandDetails, findCommandDefinition } from './commandRegistry.js';
|
|
14
14
|
import { THEMES, THEME_NAMES, ansiBackground, ansiForeground, getThemeName, setTheme } from './theme.js';
|
|
15
15
|
import { getGpuMetrics, getSharedMemoryMetrics, formatProgressBar } from './gpuMetrics.js';
|
|
16
16
|
import { computeVramEstimate, getHardwareTarget, probeLmStudioRuntime, resolveSystemHardwareTier } from './vramService.js';
|
|
@@ -23,6 +23,7 @@ import path from 'node:path';
|
|
|
23
23
|
import http from 'node:http';
|
|
24
24
|
import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
|
|
25
25
|
import { QUIZ_QUESTIONS, QUIZ_I18N, LETTER_LABELS, shuffleQuizQuestions } from './quizData.js';
|
|
26
|
+
import { MCP_QUIZ_QUESTIONS, MCP_QUIZ_I18N, shuffleMcpQuizQuestions } from './mcpQuizData.js';
|
|
26
27
|
import crypto from 'node:crypto';
|
|
27
28
|
import { isAbortError } from './errors.js';
|
|
28
29
|
import { featureCommandHandlers, aiNarration, languageHint } from './commandFeatures.js';
|
|
@@ -43,7 +44,16 @@ import {
|
|
|
43
44
|
} from './envManager.js';
|
|
44
45
|
import { cleanUrl } from './research.js';
|
|
45
46
|
|
|
46
|
-
function askEnvQuestion(question, defaultValue = '') {
|
|
47
|
+
function askEnvQuestion(ctx, question, defaultValue = '') {
|
|
48
|
+
if (ctx && typeof ctx.readInput === 'function') {
|
|
49
|
+
|
|
50
|
+
return Promise.resolve(ctx.readInput({
|
|
51
|
+
prompt: `${question}${defaultValue ? ` [${defaultValue}]` : ''}`,
|
|
52
|
+
placeholder: ' ',
|
|
53
|
+
history: false,
|
|
54
|
+
cancelValue: '',
|
|
55
|
+
})).then(answer => String(answer ?? '').trim());
|
|
56
|
+
}
|
|
47
57
|
return new Promise((resolve) => {
|
|
48
58
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
49
59
|
const suffix = defaultValue ? ` ${C.dim}[${defaultValue}]${C.reset}` : '';
|
|
@@ -157,13 +167,13 @@ function zoneBadge(zone) {
|
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
function resolveVramModelTarget(ctx, positional = []) {
|
|
160
|
-
const target = positional.join(' ');
|
|
161
|
-
if (!target) return { modelName: ctx.agent?.client?.model ||
|
|
170
|
+
const target = positional.join(' ').trim();
|
|
171
|
+
if (!target || /^<.*>$/.test(target)) return { modelName: ctx.agent?.client?.model || 'google/gemma-4-e4b', modelPath: null };
|
|
162
172
|
if (/\.gguf$/i.test(target)) return { modelName: null, modelPath: target };
|
|
163
173
|
return { modelName: target, modelPath: null };
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
|
|
176
|
+
|
|
167
177
|
async function printVramLivePanel(ctx, { overrides, positional }) {
|
|
168
178
|
const client = ctx.agent?.client || null;
|
|
169
179
|
const { modelName, modelPath } = resolveVramModelTarget(ctx, positional);
|
|
@@ -1439,9 +1449,20 @@ export const handlers = {
|
|
|
1439
1449
|
const msgCount = sess.messages.length;
|
|
1440
1450
|
const userMsgs = sess.messages.filter(m => m.role === 'user').length;
|
|
1441
1451
|
const assistantMsgs = sess.messages.filter(m => m.role === 'assistant').length;
|
|
1442
|
-
const
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1452
|
+
const toolStats = typeof sess.getToolStats === 'function' ? sess.getToolStats() : {};
|
|
1453
|
+
const toolCalls = typeof sess.getTotalToolCalls === 'function'
|
|
1454
|
+
? sess.getTotalToolCalls()
|
|
1455
|
+
: Math.max(sess.stats?.totalToolCalls || 0, Object.values(toolStats).reduce((sum, s) => sum + (s.count || 0), 0));
|
|
1456
|
+
const totalDurMs = Math.max(
|
|
1457
|
+
sess.stats?.totalDurationMs || 0,
|
|
1458
|
+
Object.values(toolStats).reduce((sum, s) => sum + (s.totalDurationMs || 0), 0)
|
|
1459
|
+
);
|
|
1460
|
+
const totalDur = (totalDurMs > 0 && totalDurMs < 1000)
|
|
1461
|
+
? (totalDurMs / 1000).toFixed(2)
|
|
1462
|
+
: ((totalDurMs || 0) / 1000).toFixed(1);
|
|
1463
|
+
const createdAt = sess.createdAt && !isNaN(new Date(sess.createdAt).getTime())
|
|
1464
|
+
? new Date(sess.createdAt).toLocaleString()
|
|
1465
|
+
: 'unknown';
|
|
1445
1466
|
|
|
1446
1467
|
const lines = [
|
|
1447
1468
|
`${C.gold}Session ID:${C.reset} ${C.white}${sess.id}${C.reset}`,
|
|
@@ -1453,7 +1474,6 @@ export const handlers = {
|
|
|
1453
1474
|
`${C.gold}Thinking Mode:${C.reset} ${C.bold}${C.gold}${sess.thinkingMode.toUpperCase()}${C.reset}`
|
|
1454
1475
|
];
|
|
1455
1476
|
|
|
1456
|
-
const toolStats = typeof sess.getToolStats === 'function' ? sess.getToolStats() : {};
|
|
1457
1477
|
const toolEntries = Object.entries(toolStats);
|
|
1458
1478
|
if (toolEntries.length > 0) {
|
|
1459
1479
|
lines.push('', `${C.bold}${C.lavender}Tool Executions Breakdown:${C.reset}`);
|
|
@@ -2039,14 +2059,12 @@ export const handlers = {
|
|
|
2039
2059
|
if (ctx.tuiPrompt) {
|
|
2040
2060
|
ctx.tuiPrompt.viewportLines = [...getWelcomeHeroLines()];
|
|
2041
2061
|
ctx.tuiPrompt.scrollOffset = 0;
|
|
2042
|
-
ctx.tuiPrompt.addViewportLines(` ${C.emerald}[OK] Fresh start.${C.reset}\n`);
|
|
2043
2062
|
ctx.tuiPrompt.render();
|
|
2044
2063
|
} else {
|
|
2045
2064
|
console.clear();
|
|
2046
2065
|
if (typeof ctx.printWelcome === 'function') {
|
|
2047
2066
|
ctx.printWelcome();
|
|
2048
2067
|
}
|
|
2049
|
-
console.log(` ${C.emerald}[OK] Fresh start.${C.reset}\n`);
|
|
2050
2068
|
}
|
|
2051
2069
|
return 'HANDLED';
|
|
2052
2070
|
},
|
|
@@ -2057,16 +2075,57 @@ export const handlers = {
|
|
|
2057
2075
|
},
|
|
2058
2076
|
|
|
2059
2077
|
'/lan': async (ctx) => {
|
|
2060
|
-
const { scanLan, mapServerTypeToProvider } = await import('./lanDiscovery.js');
|
|
2078
|
+
const { scanLan, mapServerTypeToProvider, listLanInterfaces, describeLanInterface, parseLanScanArgs, classifyScanToken, loadLanState, saveLanState, describeLanScanAge, pickLanEndpointModel } = await import('./lanDiscovery.js');
|
|
2061
2079
|
const { savePersistedProviderBaseUrl } = await import('./config.js');
|
|
2062
2080
|
const action = (ctx.sub || 'scan').toLowerCase();
|
|
2063
2081
|
const parts = ctx.parts || [];
|
|
2064
2082
|
|
|
2083
|
+
if (action === 'ifs' || action === 'interfaces' || action === 'iface') {
|
|
2084
|
+
const rest = parts.slice(2).join(' ').trim();
|
|
2085
|
+
if (rest && !['list', 'ls', 'show'].includes(rest.toLowerCase())) {
|
|
2086
|
+
console.log(`\n ${C.amber}Usage: /lan ifs (list selectable network interfaces)${C.reset}`);
|
|
2087
|
+
console.log(` ${C.dim}Scan one of them with: /lan scan --iface <n|name>${C.reset}\n`);
|
|
2088
|
+
return 'HANDLED';
|
|
2089
|
+
}
|
|
2090
|
+
console.log(`\n ${C.cyberCyan}Detecting network interfaces...${C.reset}`);
|
|
2091
|
+
let interfaces;
|
|
2092
|
+
try {
|
|
2093
|
+
interfaces = await listLanInterfaces({});
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
console.log(`\n ${C.red}[LAN ERROR] ${err.message}${C.reset}\n`);
|
|
2096
|
+
return 'HANDLED';
|
|
2097
|
+
}
|
|
2098
|
+
const lanState = await loadLanState().catch(() => null);
|
|
2099
|
+
const lastIface = lanState?.lastIface ? String(lanState.lastIface).toLowerCase() : '';
|
|
2100
|
+
const lines = [
|
|
2101
|
+
'',
|
|
2102
|
+
...interfaces.map(i => {
|
|
2103
|
+
const isLast = lastIface && (i.name.toLowerCase() === lastIface || String(i.index) === lastIface);
|
|
2104
|
+
return ` ${C.white}${describeLanInterface(i)}${C.reset}${isLast ? ` ${C.gold}• last${C.reset}` : ''}`;
|
|
2105
|
+
}),
|
|
2106
|
+
'',
|
|
2107
|
+
];
|
|
2108
|
+
lines.push(`${C.dim}Scan a single LAN: /lan scan --iface <n|name> (e.g. /lan scan --iface Wi-Fi)${C.reset}`);
|
|
2109
|
+
lines.push(`${C.dim}Explicit ranges still win: /lan scan 192.168.1.0/24 --ports 1234,8000${C.reset}`);
|
|
2110
|
+
printSolidPanel({ title: `LAN Interfaces (${interfaces.length})`, lines });
|
|
2111
|
+
return 'HANDLED';
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2065
2114
|
if (action === 'use') {
|
|
2066
2115
|
const arg = (parts[2] || ctx.extra || '').trim();
|
|
2067
|
-
const
|
|
2116
|
+
const modelArg = parts.slice(3).join(' ').trim() || null;
|
|
2117
|
+
let endpoints = lastLanScan?.endpoints || [];
|
|
2118
|
+
let scanAgeNote = '';
|
|
2119
|
+
if (endpoints.length === 0) {
|
|
2120
|
+
const disk = await loadLanState().catch(() => null);
|
|
2121
|
+
const diskEndpoints = disk?.lastScan?.endpoints;
|
|
2122
|
+
if (Array.isArray(diskEndpoints) && diskEndpoints.length > 0) {
|
|
2123
|
+
endpoints = diskEndpoints;
|
|
2124
|
+
scanAgeNote = ` (from saved scan ${describeLanScanAge(disk.lastScan.savedAt)} — rescan if stale)`;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2068
2127
|
if (!arg) {
|
|
2069
|
-
console.log(`\n ${C.amber}Usage: /lan use <n> (run /lan first to discover endpoints)${C.reset}\n`);
|
|
2128
|
+
console.log(`\n ${C.amber}Usage: /lan use <n> [model-index-or-name] (run /lan first to discover endpoints)${C.reset}\n`);
|
|
2070
2129
|
return 'HANDLED';
|
|
2071
2130
|
}
|
|
2072
2131
|
const endpoint = /^\d+$/.test(arg)
|
|
@@ -2079,7 +2138,15 @@ export const handlers = {
|
|
|
2079
2138
|
}
|
|
2080
2139
|
|
|
2081
2140
|
const provider = mapServerTypeToProvider(endpoint.serverType);
|
|
2082
|
-
const
|
|
2141
|
+
const picked = pickLanEndpointModel(endpoint, modelArg);
|
|
2142
|
+
if (!picked.matched) {
|
|
2143
|
+
console.log(`\n ${C.red}[ERROR] Model "${modelArg}" not on ${endpoint.host}:${endpoint.port}.${C.reset}`);
|
|
2144
|
+
const available = (endpoint.models || []).map((m, i) => ` [${i + 1}] ${m}`).join('\n') || ' (no models reported)';
|
|
2145
|
+
console.log(` ${C.slate}Available:${C.reset}\n${available}`);
|
|
2146
|
+
console.log(` ${C.dim}Usage: /lan use <n> [model-index-or-name]${C.reset}\n`);
|
|
2147
|
+
return 'HANDLED';
|
|
2148
|
+
}
|
|
2149
|
+
const model = picked.model;
|
|
2083
2150
|
const cleanUrl = savePersistedProviderBaseUrl(provider, endpoint.url);
|
|
2084
2151
|
const options = { baseUrl: cleanUrl, ...(model ? { model } : {}) };
|
|
2085
2152
|
try {
|
|
@@ -2103,12 +2170,12 @@ export const handlers = {
|
|
|
2103
2170
|
}
|
|
2104
2171
|
}
|
|
2105
2172
|
ctx.baseUrl = info.baseUrl;
|
|
2106
|
-
console.log(`\n ${C.emerald}[OK] Connected to LAN endpoint ${C.bold}${endpoint.host}:${endpoint.port}${C.reset}`);
|
|
2173
|
+
console.log(`\n ${C.emerald}[OK] Connected to LAN endpoint ${C.bold}${endpoint.host}:${endpoint.port}${C.reset}${scanAgeNote}`);
|
|
2107
2174
|
console.log(` ${C.slate}• Server:${C.reset} ${C.white}${endpoint.serverName}${C.reset}`);
|
|
2108
2175
|
console.log(` ${C.slate}• Provider:${C.reset} ${C.cyberCyan}${info.provider}${C.reset} → ${C.cyberCyan}${info.baseUrl}${C.reset}`);
|
|
2109
2176
|
console.log(` ${C.slate}• Model:${C.reset} ${C.white}${info.model}${C.reset}`);
|
|
2110
|
-
if (endpoint.models.length > 1) {
|
|
2111
|
-
console.log(` ${C.dim}• ${endpoint.models.length} models available —
|
|
2177
|
+
if (!modelArg && endpoint.models.length > 1) {
|
|
2178
|
+
console.log(` ${C.dim}• ${endpoint.models.length} models available — pick with /lan use <n> [model] or /model use <id>${C.reset}`);
|
|
2112
2179
|
}
|
|
2113
2180
|
console.log('');
|
|
2114
2181
|
} catch (err) {
|
|
@@ -2119,31 +2186,47 @@ export const handlers = {
|
|
|
2119
2186
|
|
|
2120
2187
|
console.log(`\n ${C.cyberCyan}Scanning local network for LM servers (LM Studio :1234, vLLM :8000, Ollama :11434, :8080)...${C.reset}`);
|
|
2121
2188
|
const restTokens = parts.slice(2).map(t => String(t || '').trim()).filter(Boolean);
|
|
2122
|
-
if (action !== 'scan' && action !== 'json' && action !== 'use') {
|
|
2189
|
+
if (action !== 'scan' && action !== 'json' && action !== 'use' && action !== 'ifs' && action !== 'interfaces' && action !== 'iface') {
|
|
2123
2190
|
restTokens.unshift(action);
|
|
2124
2191
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
if (rawPorts.length === 0) {
|
|
2131
|
-
console.log(`\n ${C.amber}Usage: /lan scan <targets> --ports <p1,p2,...>${C.reset}`);
|
|
2132
|
-
console.log(` ${C.dim}Example: /lan scan 192.168.1.0/24 --ports 1234,8000${C.reset}\n`);
|
|
2133
|
-
return 'HANDLED';
|
|
2134
|
-
}
|
|
2135
|
-
scanPorts = rawPorts;
|
|
2136
|
-
restTokens.splice(portsFlagIdx, 2);
|
|
2192
|
+
const parsed = parseLanScanArgs(restTokens);
|
|
2193
|
+
if (parsed.error === 'ports') {
|
|
2194
|
+
console.log(`\n ${C.amber}Usage: /lan scan <targets> [--iface <n|name>] [--ports <p1,p2,...>] [--force]${C.reset}`);
|
|
2195
|
+
console.log(` ${C.dim}Example: /lan scan --iface Wi-Fi --ports 1234,8000${C.reset}\n`);
|
|
2196
|
+
return 'HANDLED';
|
|
2137
2197
|
}
|
|
2138
|
-
if (
|
|
2139
|
-
|
|
2198
|
+
if (parsed.error === 'iface') {
|
|
2199
|
+
console.log(`\n ${C.amber}Usage: /lan scan --iface <n|name> (see /lan ifs for the list)${C.reset}\n`);
|
|
2200
|
+
return 'HANDLED';
|
|
2201
|
+
}
|
|
2202
|
+
const scanTargets = parsed.subnets;
|
|
2203
|
+
const scanPorts = parsed.ports;
|
|
2204
|
+
let scanIface = parsed.iface;
|
|
2205
|
+
|
|
2206
|
+
if (!scanIface && scanTargets) {
|
|
2207
|
+
// Bare adapter names work without the flag: `/lan scan Wi-Fi`.
|
|
2208
|
+
const names = scanTargets.filter(t => classifyScanToken(t) === 'name');
|
|
2209
|
+
if (names.length > 0) {
|
|
2210
|
+
if (names.length > 1 || scanTargets.length > 1) {
|
|
2211
|
+
console.log(`\n ${C.amber}Mixed targets and interface names — pick one:${C.reset}`);
|
|
2212
|
+
console.log(` ${C.dim}• One LAN: /lan scan --iface <n|name> (see /lan ifs)${C.reset}`);
|
|
2213
|
+
console.log(` ${C.dim}• Ranges: /lan scan 192.168.1.0/24 --ports 1234,8000${C.reset}\n`);
|
|
2214
|
+
return 'HANDLED';
|
|
2215
|
+
}
|
|
2216
|
+
scanIface = names[0];
|
|
2217
|
+
}
|
|
2140
2218
|
}
|
|
2219
|
+
const effectiveTargets = scanIface
|
|
2220
|
+
? null
|
|
2221
|
+
: (scanTargets || []).filter(t => classifyScanToken(t) !== 'name');
|
|
2141
2222
|
|
|
2142
2223
|
let result;
|
|
2143
2224
|
try {
|
|
2144
2225
|
result = await scanLan({
|
|
2145
|
-
...(
|
|
2226
|
+
...(effectiveTargets && effectiveTargets.length > 0 ? { subnets: effectiveTargets } : {}),
|
|
2227
|
+
...(scanIface ? { iface: scanIface } : {}),
|
|
2146
2228
|
...(scanPorts ? { ports: scanPorts } : {}),
|
|
2229
|
+
...(parsed.force ? { force: true } : {}),
|
|
2147
2230
|
signal: ctx.signal,
|
|
2148
2231
|
});
|
|
2149
2232
|
} catch (err) {
|
|
@@ -2159,6 +2242,16 @@ export const handlers = {
|
|
|
2159
2242
|
return 'HANDLED';
|
|
2160
2243
|
}
|
|
2161
2244
|
lastLanScan = result;
|
|
2245
|
+
try {
|
|
2246
|
+
await saveLanState({
|
|
2247
|
+
lastScan: {
|
|
2248
|
+
savedAt: result.scannedAt || new Date().toISOString(),
|
|
2249
|
+
subnets: result.subnets || [],
|
|
2250
|
+
endpoints: result.endpoints || [],
|
|
2251
|
+
},
|
|
2252
|
+
...(scanIface ? { lastIface: scanIface } : {}),
|
|
2253
|
+
});
|
|
2254
|
+
} catch {}
|
|
2162
2255
|
|
|
2163
2256
|
if (action === 'json') {
|
|
2164
2257
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2194,6 +2287,13 @@ export const handlers = {
|
|
|
2194
2287
|
lines.push('');
|
|
2195
2288
|
lines.push(`${C.dim}Connect: /lan use <n> (persists the Base URL for this provider)${C.reset}`);
|
|
2196
2289
|
}
|
|
2290
|
+
if (Array.isArray(result.skippedSubnets) && result.skippedSubnets.length > 0) {
|
|
2291
|
+
lines.push('');
|
|
2292
|
+
for (const s of result.skippedSubnets) {
|
|
2293
|
+
const slice = String(s.cidr || '').split('/')[0].split('.').slice(0, 3).join('.');
|
|
2294
|
+
lines.push(` ${C.dim}• Skipped ${s.cidr} (${s.interfaceName}, ${Number(s.hostCount || 0).toLocaleString()} hosts, over budget) — slice it: /lan scan ${slice}.0/24${C.reset}`);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2197
2297
|
printSolidPanel({ title: `LAN LM Server Discovery (${endpoints.length} found)`, lines });
|
|
2198
2298
|
return 'HANDLED';
|
|
2199
2299
|
},
|
|
@@ -2490,6 +2590,21 @@ export const handlers = {
|
|
|
2490
2590
|
|
|
2491
2591
|
'/url': async (ctx) => handlers['/provider']({ ...ctx, sub: 'url', parts: ['/provider', 'url', ...(ctx.parts?.slice(1) || [])] }),
|
|
2492
2592
|
'/endpoint': async (ctx) => handlers['/provider']({ ...ctx, sub: 'url', parts: ['/provider', 'url', ...(ctx.parts?.slice(1) || [])] }),
|
|
2593
|
+
'/unload': async (ctx) => {
|
|
2594
|
+
const { unloadModelFromEngine } = await import('./engineService.js');
|
|
2595
|
+
const { getActiveLocalEngine } = await import('./config.js');
|
|
2596
|
+
const targetArg = (ctx.sub || '').trim().toLowerCase();
|
|
2597
|
+
const targetEngine = targetArg === 'vllm' ? 'vllm' : (targetArg === 'lms' || targetArg === 'lmstudio' ? 'lms' : getActiveLocalEngine() || 'lms');
|
|
2598
|
+
const currentModel = ctx.client?.model || ctx.modelManager?.currentModel || null;
|
|
2599
|
+
ctx.setIsProcessing?.(true, `Unloading model on ${targetEngine}…`);
|
|
2600
|
+
const res = await unloadModelFromEngine(targetEngine, currentModel);
|
|
2601
|
+
if (res.ok) {
|
|
2602
|
+
console.log(`\n ${C.emerald}[OK] Model successfully unloaded from ${targetEngine}. GPU VRAM released.${C.reset}\n`);
|
|
2603
|
+
} else {
|
|
2604
|
+
console.log(`\n ${C.amber}[NOTICE] Unload result: ${res.reason || res.message || 'Model could not be unloaded'}.${C.reset}\n`);
|
|
2605
|
+
}
|
|
2606
|
+
return 'HANDLED';
|
|
2607
|
+
},
|
|
2493
2608
|
|
|
2494
2609
|
'/model': async (ctx) => {
|
|
2495
2610
|
const rawSub = (ctx.sub || '').trim();
|
|
@@ -2497,6 +2612,723 @@ export const handlers = {
|
|
|
2497
2612
|
const providerName = ctx.client.providerName || 'Model';
|
|
2498
2613
|
const { getRecommendedModels, CLOUD_PROVIDERS } = await import('./config.js');
|
|
2499
2614
|
|
|
2615
|
+
if (action === 'load') {
|
|
2616
|
+
const {
|
|
2617
|
+
checkEngineCompatibility,
|
|
2618
|
+
loadModelOnEngine,
|
|
2619
|
+
} = await import('./engineService.js');
|
|
2620
|
+
const {
|
|
2621
|
+
getActiveLocalEngine,
|
|
2622
|
+
setActiveLocalEngine,
|
|
2623
|
+
getPersistedModelLoadSettings,
|
|
2624
|
+
savePersistedModelLoadSettings,
|
|
2625
|
+
} = await import('./config.js');
|
|
2626
|
+
|
|
2627
|
+
const rawArgs = (ctx.extra || '').trim();
|
|
2628
|
+
const tokens = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
|
|
2629
|
+
let targetModel = null;
|
|
2630
|
+
let engine = null;
|
|
2631
|
+
let gpu = undefined;
|
|
2632
|
+
let threads = undefined;
|
|
2633
|
+
let memUtil = undefined;
|
|
2634
|
+
let maxModelLen = undefined;
|
|
2635
|
+
let tp = undefined;
|
|
2636
|
+
let dtype = undefined;
|
|
2637
|
+
let enforceEager = undefined;
|
|
2638
|
+
let enablePrefixCaching = undefined;
|
|
2639
|
+
let splitMode = undefined;
|
|
2640
|
+
let tensorSplit = undefined;
|
|
2641
|
+
let mainGpu = undefined;
|
|
2642
|
+
let flashAttn = undefined;
|
|
2643
|
+
let batchSize = undefined;
|
|
2644
|
+
let ubatchSize = undefined;
|
|
2645
|
+
let kvCacheDtype = undefined;
|
|
2646
|
+
|
|
2647
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2648
|
+
const tok = tokens[i];
|
|
2649
|
+
if (tok === '--engine' && i + 1 < tokens.length) {
|
|
2650
|
+
engine = tokens[++i].toLowerCase();
|
|
2651
|
+
} else if (tok === '--gpu' && i + 1 < tokens.length) {
|
|
2652
|
+
gpu = tokens[++i];
|
|
2653
|
+
} else if (tok === '--threads' && i + 1 < tokens.length) {
|
|
2654
|
+
threads = Number(tokens[++i]);
|
|
2655
|
+
} else if (tok === '--split-mode' && i + 1 < tokens.length) {
|
|
2656
|
+
splitMode = tokens[++i].toLowerCase();
|
|
2657
|
+
} else if ((tok === '--tensor-split' || tok === '-ts') && i + 1 < tokens.length) {
|
|
2658
|
+
tensorSplit = tokens[++i];
|
|
2659
|
+
} else if ((tok === '--main-gpu' || tok === '-mg') && i + 1 < tokens.length) {
|
|
2660
|
+
mainGpu = Number(tokens[++i]);
|
|
2661
|
+
} else if (tok === '--fa' || tok === '--flash-attn') {
|
|
2662
|
+
flashAttn = true;
|
|
2663
|
+
} else if (tok === '--no-fa' || tok === '--no-flash-attn') {
|
|
2664
|
+
flashAttn = false;
|
|
2665
|
+
} else if ((tok === '-b' || tok === '--batch-size') && i + 1 < tokens.length) {
|
|
2666
|
+
batchSize = Number(tokens[++i]);
|
|
2667
|
+
} else if ((tok === '-ub' || tok === '--ubatch-size') && i + 1 < tokens.length) {
|
|
2668
|
+
ubatchSize = Number(tokens[++i]);
|
|
2669
|
+
} else if ((tok === '--ctk' || tok === '--kv-cache') && i + 1 < tokens.length) {
|
|
2670
|
+
kvCacheDtype = tokens[++i].toLowerCase();
|
|
2671
|
+
} else if ((tok === '--mem-util' || tok === '--gpu-mem-util') && i + 1 < tokens.length) {
|
|
2672
|
+
memUtil = Number(tokens[++i]);
|
|
2673
|
+
} else if (tok === '--max-model-len' && i + 1 < tokens.length) {
|
|
2674
|
+
maxModelLen = Number(tokens[++i]);
|
|
2675
|
+
} else if (tok === '--tp' && i + 1 < tokens.length) {
|
|
2676
|
+
tp = Number(tokens[++i]);
|
|
2677
|
+
} else if (tok === '--dtype' && i + 1 < tokens.length) {
|
|
2678
|
+
dtype = tokens[++i].toLowerCase();
|
|
2679
|
+
} else if (tok === '--enforce-eager') {
|
|
2680
|
+
enforceEager = true;
|
|
2681
|
+
} else if (tok === '--prefix-caching') {
|
|
2682
|
+
enablePrefixCaching = true;
|
|
2683
|
+
} else if (tok === '--no-prefix-caching') {
|
|
2684
|
+
enablePrefixCaching = false;
|
|
2685
|
+
} else if (!tok.startsWith('--') && !targetModel) {
|
|
2686
|
+
targetModel = tok;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
if (!engine) {
|
|
2691
|
+
if (ctx.client.provider === 'vllm') {
|
|
2692
|
+
engine = 'vllm';
|
|
2693
|
+
} else if (ctx.client.provider === 'lmstudio') {
|
|
2694
|
+
engine = 'lms';
|
|
2695
|
+
} else {
|
|
2696
|
+
engine = getActiveLocalEngine() || 'lms';
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
const compat = checkEngineCompatibility(engine);
|
|
2701
|
+
if (!compat.supported) {
|
|
2702
|
+
printRichCard({
|
|
2703
|
+
title: `Engine Incompatible: ${engine.toUpperCase()}`,
|
|
2704
|
+
status: 'error',
|
|
2705
|
+
lines: [
|
|
2706
|
+
`${C.amber}Operating System:${C.reset} ${process.platform} ${C.dim}(${compat.platform || 'unknown'})${C.reset}`,
|
|
2707
|
+
`${C.amber}Compatibility:${C.reset} ${C.red}Not supported${C.reset}`,
|
|
2708
|
+
'',
|
|
2709
|
+
`${C.white}${compat.reason || 'This engine is not supported on your current operating system.'}${C.reset}`,
|
|
2710
|
+
'',
|
|
2711
|
+
compat.installHint ? `${C.cyan}Hint:${C.reset} ${compat.installHint}` : '',
|
|
2712
|
+
`${C.dim}Tip: LM Studio (lms) runs universally on Windows, macOS, and Linux.${C.reset}`,
|
|
2713
|
+
].filter(Boolean),
|
|
2714
|
+
});
|
|
2715
|
+
return 'HANDLED';
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
const model = targetModel || ctx.client.model || (engine === 'vllm' ? 'google/gemma-4-e4b' : 'google/gemma-4-e4b');
|
|
2719
|
+
|
|
2720
|
+
setActiveLocalEngine(engine);
|
|
2721
|
+
const persistUpdate = { activeEngine: engine };
|
|
2722
|
+
if (engine === 'lms') {
|
|
2723
|
+
if (gpu !== undefined) persistUpdate.gpuOffload = gpu;
|
|
2724
|
+
if (threads !== undefined) persistUpdate.cpuThreads = threads;
|
|
2725
|
+
if (splitMode !== undefined) persistUpdate.splitMode = splitMode;
|
|
2726
|
+
if (tensorSplit !== undefined) persistUpdate.tensorSplit = tensorSplit;
|
|
2727
|
+
if (mainGpu !== undefined) persistUpdate.mainGpu = mainGpu;
|
|
2728
|
+
if (flashAttn !== undefined) persistUpdate.flashAttn = flashAttn;
|
|
2729
|
+
if (batchSize !== undefined) persistUpdate.batchSize = batchSize;
|
|
2730
|
+
if (ubatchSize !== undefined) persistUpdate.ubatchSize = ubatchSize;
|
|
2731
|
+
if (kvCacheDtype !== undefined) persistUpdate.kvCacheDtype = kvCacheDtype;
|
|
2732
|
+
} else if (engine === 'vllm') {
|
|
2733
|
+
persistUpdate.vllm = {};
|
|
2734
|
+
if (memUtil !== undefined) persistUpdate.vllm.gpuMemoryUtilization = memUtil;
|
|
2735
|
+
if (maxModelLen !== undefined) persistUpdate.vllm.maxModelLen = maxModelLen;
|
|
2736
|
+
if (tp !== undefined) persistUpdate.vllm.tensorParallelSize = tp;
|
|
2737
|
+
if (dtype !== undefined) persistUpdate.vllm.dtype = dtype;
|
|
2738
|
+
if (enforceEager !== undefined) persistUpdate.vllm.enforceEager = enforceEager;
|
|
2739
|
+
if (enablePrefixCaching !== undefined) persistUpdate.vllm.enablePrefixCaching = enablePrefixCaching;
|
|
2740
|
+
if (kvCacheDtype !== undefined) persistUpdate.vllm.kvCacheDtype = kvCacheDtype;
|
|
2741
|
+
}
|
|
2742
|
+
savePersistedModelLoadSettings(persistUpdate);
|
|
2743
|
+
|
|
2744
|
+
const currentSettings = getPersistedModelLoadSettings();
|
|
2745
|
+
|
|
2746
|
+
ctx.setIsProcessing?.(true, `Loading model "${model}" on ${compat.name}…`);
|
|
2747
|
+
console.log(`\n ${C.cyberCyan}• Starting model load on engine:${C.reset} ${C.bold}${compat.name}${C.reset}`);
|
|
2748
|
+
console.log(` ${C.dim}Target Model:${C.reset} ${C.white}${model}${C.reset}`);
|
|
2749
|
+
if (engine === 'lms') {
|
|
2750
|
+
console.log(` ${C.dim}Parameters:${C.reset} GPU Offload=${currentSettings.gpuOffload || 'auto'}, Threads=${currentSettings.cpuThreads || 'auto'}, SplitMode=${currentSettings.splitMode || 'layer'}, TensorSplit=${currentSettings.tensorSplit || 'auto'}, FlashAttn=${currentSettings.flashAttn !== false ? 'on' : 'off'}`);
|
|
2751
|
+
} else {
|
|
2752
|
+
console.log(` ${C.dim}Parameters:${C.reset} Mem Util=${currentSettings.vllm.gpuMemoryUtilization}, Max Model Len=${currentSettings.vllm.maxModelLen || 'auto'}, TP=${currentSettings.vllm.tensorParallelSize}`);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
const loadResult = await loadModelOnEngine(engine, model, {
|
|
2756
|
+
gpuOffload: gpu !== undefined ? gpu : currentSettings.gpuOffload,
|
|
2757
|
+
cpuThreads: threads !== undefined ? threads : currentSettings.cpuThreads,
|
|
2758
|
+
splitMode: splitMode !== undefined ? splitMode : currentSettings.splitMode,
|
|
2759
|
+
tensorSplit: tensorSplit !== undefined ? tensorSplit : currentSettings.tensorSplit,
|
|
2760
|
+
mainGpu: mainGpu !== undefined ? mainGpu : currentSettings.mainGpu,
|
|
2761
|
+
flashAttn: flashAttn !== undefined ? flashAttn : currentSettings.flashAttn,
|
|
2762
|
+
batchSize: batchSize !== undefined ? batchSize : currentSettings.batchSize,
|
|
2763
|
+
ubatchSize: ubatchSize !== undefined ? ubatchSize : currentSettings.ubatchSize,
|
|
2764
|
+
gpuMemoryUtilization: memUtil !== undefined ? memUtil : currentSettings.vllm.gpuMemoryUtilization,
|
|
2765
|
+
maxModelLen: maxModelLen !== undefined ? maxModelLen : currentSettings.vllm.maxModelLen,
|
|
2766
|
+
tensorParallelSize: tp !== undefined ? tp : currentSettings.vllm.tensorParallelSize,
|
|
2767
|
+
dtype: dtype !== undefined ? dtype : currentSettings.vllm.dtype,
|
|
2768
|
+
enforceEager: enforceEager !== undefined ? enforceEager : currentSettings.vllm.enforceEager,
|
|
2769
|
+
enablePrefixCaching: enablePrefixCaching !== undefined ? enablePrefixCaching : currentSettings.vllm.enablePrefixCaching,
|
|
2770
|
+
kvCacheDtype: kvCacheDtype !== undefined ? kvCacheDtype : (engine === 'vllm' ? currentSettings.vllm.kvCacheDtype : currentSettings.kvCacheDtype),
|
|
2771
|
+
});
|
|
2772
|
+
|
|
2773
|
+
if (!loadResult.ok) {
|
|
2774
|
+
printRichCard({
|
|
2775
|
+
title: `Engine Load Failed: ${compat.name}`,
|
|
2776
|
+
status: 'error',
|
|
2777
|
+
lines: [
|
|
2778
|
+
`${C.red}Error:${C.reset} ${loadResult.reason || 'Failed to start or load model.'}`,
|
|
2779
|
+
loadResult.rawCommand ? `${C.dim}Command:${C.reset} ${C.cyan}${loadResult.rawCommand}${C.reset}` : '',
|
|
2780
|
+
].filter(Boolean),
|
|
2781
|
+
});
|
|
2782
|
+
return 'HANDLED';
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
const resolvedLoadedModel = loadResult.loadedModel || model;
|
|
2786
|
+
if (engine === 'vllm' && ctx.client.provider !== 'vllm') {
|
|
2787
|
+
try {
|
|
2788
|
+
ctx.client.switchProvider?.('vllm', { model: resolvedLoadedModel });
|
|
2789
|
+
if (ctx.agent?.client) ctx.agent.client.switchProvider?.('vllm', { model: resolvedLoadedModel });
|
|
2790
|
+
} catch {}
|
|
2791
|
+
} else if (engine === 'lms' && ctx.client.provider !== 'lmstudio') {
|
|
2792
|
+
try {
|
|
2793
|
+
ctx.client.switchProvider?.('lmstudio', { model: resolvedLoadedModel });
|
|
2794
|
+
if (ctx.agent?.client) ctx.agent.client.switchProvider?.('lmstudio', { model: resolvedLoadedModel });
|
|
2795
|
+
} catch {}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
if (ctx.modelManager) {
|
|
2799
|
+
ctx.modelManager.select(resolvedLoadedModel, [resolvedLoadedModel]);
|
|
2800
|
+
}
|
|
2801
|
+
ctx.client.setModel(resolvedLoadedModel);
|
|
2802
|
+
ctx.setCatalogOnlyMode?.(false);
|
|
2803
|
+
if (ctx.tuiPrompt) {
|
|
2804
|
+
ctx.tuiPrompt.modelName = resolvedLoadedModel;
|
|
2805
|
+
ctx.tuiPrompt.isConnected = true;
|
|
2806
|
+
ctx.tuiPrompt.render();
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
printSolidPanel({
|
|
2810
|
+
title: `Model Ready • ${compat.name}`,
|
|
2811
|
+
lines: [
|
|
2812
|
+
`${C.gold}Engine:${C.reset} ${C.white}${compat.name}${C.reset} ${C.dim}(${compat.targetLevel === 'enterprise' ? 'Enterprise Data Center' : 'Universal 3-OS'})${C.reset}`,
|
|
2813
|
+
`${C.gold}Model:${C.reset} ${C.bold}${C.emerald}${resolvedLoadedModel}${C.reset}`,
|
|
2814
|
+
`${C.gold}Port:${C.reset} ${C.cyberCyan}${loadResult.engine === 'vllm' ? 8000 : 1234}${C.reset}`,
|
|
2815
|
+
`${C.gold}Status:${C.reset} ${C.emerald}● Active & Ready${C.reset}${loadResult.awakened ? ` ${C.dim}(Server auto-started)${C.reset}` : ''}`,
|
|
2816
|
+
'',
|
|
2817
|
+
`${C.dim}Run "/model status" to view detailed diagnostics or "/chat" to converse.${C.reset}`,
|
|
2818
|
+
],
|
|
2819
|
+
headerBg: '\x1b[48;2;30;60;50m',
|
|
2820
|
+
bg: '\x1b[48;2;20;35;30m',
|
|
2821
|
+
});
|
|
2822
|
+
return 'HANDLED';
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
if (action === 'stop') {
|
|
2826
|
+
const { stopEngine } = await import('./engineService.js');
|
|
2827
|
+
const target = (ctx.extra || '').trim().toLowerCase() || 'vllm';
|
|
2828
|
+
ctx.setIsProcessing?.(true, `Stopping ${target} engine…`);
|
|
2829
|
+
const res = await stopEngine(target);
|
|
2830
|
+
if (res.ok) {
|
|
2831
|
+
console.log(`\n ${C.emerald}[OK] Engine "${target}" background process stopped successfully.${C.reset}\n`);
|
|
2832
|
+
} else {
|
|
2833
|
+
console.log(`\n ${C.amber}[NOTICE] Engine "${target}" stop result: ${res.reason || 'Process not running'}.${C.reset}\n`);
|
|
2834
|
+
}
|
|
2835
|
+
return 'HANDLED';
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
if (action === 'unload') {
|
|
2839
|
+
const { unloadModelFromEngine } = await import('./engineService.js');
|
|
2840
|
+
const { getActiveLocalEngine } = await import('./config.js');
|
|
2841
|
+
const targetArg = (ctx.extra || '').trim().toLowerCase();
|
|
2842
|
+
const targetEngine = targetArg === 'vllm' ? 'vllm' : (targetArg === 'lms' || targetArg === 'lmstudio' ? 'lms' : getActiveLocalEngine() || 'lms');
|
|
2843
|
+
const currentModel = ctx.client?.model || ctx.modelManager?.currentModel || null;
|
|
2844
|
+
ctx.setIsProcessing?.(true, `Unloading model on ${targetEngine}…`);
|
|
2845
|
+
const res = await unloadModelFromEngine(targetEngine, currentModel);
|
|
2846
|
+
if (res.ok) {
|
|
2847
|
+
console.log(`\n ${C.emerald}[OK] Successfully unloaded model on ${targetEngine}. VRAM released.${C.reset}\n`);
|
|
2848
|
+
} else {
|
|
2849
|
+
console.log(`\n ${C.amber}[NOTICE] Unload result: ${res.reason || res.message || 'Model could not be unloaded'}.${C.reset}\n`);
|
|
2850
|
+
}
|
|
2851
|
+
return 'HANDLED';
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
if (action === 'engine') {
|
|
2855
|
+
const { checkEngineCompatibility } = await import('./engineService.js');
|
|
2856
|
+
const { getActiveLocalEngine, setActiveLocalEngine } = await import('./config.js');
|
|
2857
|
+
|
|
2858
|
+
const targetArg = (ctx.extra || '').trim().toLowerCase();
|
|
2859
|
+
if (targetArg) {
|
|
2860
|
+
let validEngine = null;
|
|
2861
|
+
if (targetArg === 'lms' || targetArg === 'lmstudio') validEngine = 'lms';
|
|
2862
|
+
else if (targetArg === 'vllm') validEngine = 'vllm';
|
|
2863
|
+
|
|
2864
|
+
if (!validEngine) {
|
|
2865
|
+
printRichCard({
|
|
2866
|
+
title: 'Invalid Engine',
|
|
2867
|
+
status: 'error',
|
|
2868
|
+
lines: [
|
|
2869
|
+
`${C.red}Unknown engine "${targetArg}".${C.reset}`,
|
|
2870
|
+
`${C.dim}Supported local engines: "lms" (LM Studio) or "vllm" (vLLM on WSL2/Linux).${C.reset}`,
|
|
2871
|
+
],
|
|
2872
|
+
});
|
|
2873
|
+
return 'HANDLED';
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
const compat = checkEngineCompatibility(validEngine);
|
|
2877
|
+
if (!compat.supported) {
|
|
2878
|
+
printRichCard({
|
|
2879
|
+
title: `Engine Incompatible: ${validEngine.toUpperCase()}`,
|
|
2880
|
+
status: 'error',
|
|
2881
|
+
lines: [
|
|
2882
|
+
`${C.amber}Operating System:${C.reset} ${process.platform} ${C.dim}(${compat.platform || 'unknown'})${C.reset}`,
|
|
2883
|
+
`${C.amber}Compatibility:${C.reset} ${C.red}Not supported${C.reset}`,
|
|
2884
|
+
'',
|
|
2885
|
+
`${C.white}${compat.reason || 'This engine is not supported on your current operating system.'}${C.reset}`,
|
|
2886
|
+
compat.installHint ? `${C.cyan}Hint:${C.reset} ${compat.installHint}` : '',
|
|
2887
|
+
].filter(Boolean),
|
|
2888
|
+
});
|
|
2889
|
+
return 'HANDLED';
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
setActiveLocalEngine(validEngine);
|
|
2893
|
+
if (validEngine === 'vllm' && ctx.client.provider !== 'vllm') {
|
|
2894
|
+
try {
|
|
2895
|
+
ctx.client.switchProvider?.('vllm');
|
|
2896
|
+
if (ctx.agent?.client) ctx.agent.client.switchProvider?.('vllm');
|
|
2897
|
+
} catch {}
|
|
2898
|
+
} else if (validEngine === 'lms' && ctx.client.provider !== 'lmstudio') {
|
|
2899
|
+
try {
|
|
2900
|
+
ctx.client.switchProvider?.('lmstudio');
|
|
2901
|
+
if (ctx.agent?.client) ctx.agent.client.switchProvider?.('lmstudio');
|
|
2902
|
+
} catch {}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
printRichCard({
|
|
2906
|
+
title: 'Active Engine Switched',
|
|
2907
|
+
status: 'ok',
|
|
2908
|
+
lines: [
|
|
2909
|
+
`${C.gold}Active Engine:${C.reset} ${C.bold}${C.emerald}${compat.name}${C.reset} ${C.dim}(${validEngine})${C.reset}`,
|
|
2910
|
+
`${C.gold}Platform:${C.reset} ${C.white}${compat.targetLevel === 'enterprise' ? 'Enterprise Data Center (Linux / WSL2)' : 'Universal 3-OS (Windows, macOS, Linux)'}${C.reset}`,
|
|
2911
|
+
`${C.gold}Default Port:${C.reset} ${C.cyberCyan}${validEngine === 'vllm' ? 8000 : 1234}${C.reset}`,
|
|
2912
|
+
'',
|
|
2913
|
+
`${C.dim}Tip: Run "/model load <model>" to load model or "/model config" to adjust parameters.${C.reset}`,
|
|
2914
|
+
],
|
|
2915
|
+
});
|
|
2916
|
+
return 'HANDLED';
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
const activeEngine = getActiveLocalEngine() || 'lms';
|
|
2920
|
+
const lmsCompat = checkEngineCompatibility('lms');
|
|
2921
|
+
const vllmCompat = checkEngineCompatibility('vllm');
|
|
2922
|
+
|
|
2923
|
+
const probePort = async (url) => {
|
|
2924
|
+
try {
|
|
2925
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(600) });
|
|
2926
|
+
return res.ok || res.status < 500;
|
|
2927
|
+
} catch {
|
|
2928
|
+
return false;
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
ctx.setIsProcessing?.(true, 'Checking local engine ports…');
|
|
2933
|
+
const [lmsReachable, vllmReachable] = await Promise.all([
|
|
2934
|
+
probePort('http://127.0.0.1:1234/v1/models'),
|
|
2935
|
+
probePort('http://127.0.0.1:8000/v1/models'),
|
|
2936
|
+
]);
|
|
2937
|
+
|
|
2938
|
+
printSolidPanel({
|
|
2939
|
+
title: 'Local Inference Engines & Routing',
|
|
2940
|
+
lines: [
|
|
2941
|
+
`${C.gold}Active Local Engine:${C.reset} ${C.bold}${C.emerald}${activeEngine === 'vllm' ? vllmCompat.name : lmsCompat.name}${C.reset} ${C.dim}(${activeEngine})${C.reset}`,
|
|
2942
|
+
'',
|
|
2943
|
+
`${C.bold}${C.lavender}Available Engines:${C.reset}`,
|
|
2944
|
+
` ${activeEngine === 'lms' ? C.gold + '[x]' : C.dim + '[ ]'} ${C.white}LM Studio (llama.cpp)${C.reset} — Port 1234: ${lmsReachable ? C.emerald + '● Online' : C.dim + '○ Offline'}${C.reset} ${C.dim}(Universal 3-OS)${C.reset}`,
|
|
2945
|
+
` ${activeEngine === 'vllm' ? C.gold + '[x]' : C.dim + '[ ]'} ${C.white}vLLM (PagedAttention)${C.reset} — Port 8000: ${vllmReachable ? C.emerald + '● Online' : C.dim + '○ Offline'}${C.reset} ${C.dim}(${vllmCompat.supported ? 'Supported on ' + (process.platform === 'win32' ? 'WSL2' : 'Linux') : 'Not supported on this OS'})${C.reset}`,
|
|
2946
|
+
'',
|
|
2947
|
+
`${C.dim}Commands: /model engine lms | /model engine vllm | /model config | /model load <model>${C.reset}`,
|
|
2948
|
+
],
|
|
2949
|
+
headerBg: '\x1b[48;2;30;60;50m',
|
|
2950
|
+
bg: '\x1b[48;2;20;35;30m',
|
|
2951
|
+
});
|
|
2952
|
+
return 'HANDLED';
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
if (action === 'config' || action === 'params') {
|
|
2956
|
+
const {
|
|
2957
|
+
getActiveLocalEngine,
|
|
2958
|
+
getPersistedModelLoadSettings,
|
|
2959
|
+
savePersistedModelLoadSettings,
|
|
2960
|
+
MODEL_LOAD_DEFAULTS,
|
|
2961
|
+
} = await import('./config.js');
|
|
2962
|
+
|
|
2963
|
+
const rawArgs = (ctx.extra || '').trim();
|
|
2964
|
+
const tokens = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
|
|
2965
|
+
|
|
2966
|
+
if (tokens.includes('--reset')) {
|
|
2967
|
+
savePersistedModelLoadSettings(MODEL_LOAD_DEFAULTS);
|
|
2968
|
+
console.log(`\n ${C.emerald}[OK] Model load parameters reset to factory defaults.${C.reset}\n`);
|
|
2969
|
+
} else if (tokens.length > 0) {
|
|
2970
|
+
let engine = undefined;
|
|
2971
|
+
let gpu = undefined;
|
|
2972
|
+
let threads = undefined;
|
|
2973
|
+
let memUtil = undefined;
|
|
2974
|
+
let maxModelLen = undefined;
|
|
2975
|
+
let tp = undefined;
|
|
2976
|
+
let dtype = undefined;
|
|
2977
|
+
let enforceEager = undefined;
|
|
2978
|
+
let enablePrefixCaching = undefined;
|
|
2979
|
+
let splitMode = undefined;
|
|
2980
|
+
let tensorSplit = undefined;
|
|
2981
|
+
let mainGpu = undefined;
|
|
2982
|
+
let flashAttn = undefined;
|
|
2983
|
+
let batchSize = undefined;
|
|
2984
|
+
let ubatchSize = undefined;
|
|
2985
|
+
let kvCacheDtype = undefined;
|
|
2986
|
+
|
|
2987
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2988
|
+
const tok = tokens[i];
|
|
2989
|
+
if (tok === '--engine' && i + 1 < tokens.length) {
|
|
2990
|
+
const e = tokens[++i].toLowerCase();
|
|
2991
|
+
if (e === 'lms' || e === 'vllm' || e === 'lmstudio') engine = e === 'lmstudio' ? 'lms' : e;
|
|
2992
|
+
} else if (tok === '--gpu' && i + 1 < tokens.length) {
|
|
2993
|
+
gpu = tokens[++i];
|
|
2994
|
+
} else if (tok === '--threads' && i + 1 < tokens.length) {
|
|
2995
|
+
threads = Number(tokens[++i]);
|
|
2996
|
+
} else if (tok === '--split-mode' && i + 1 < tokens.length) {
|
|
2997
|
+
splitMode = tokens[++i].toLowerCase();
|
|
2998
|
+
} else if ((tok === '--tensor-split' || tok === '-ts') && i + 1 < tokens.length) {
|
|
2999
|
+
tensorSplit = tokens[++i];
|
|
3000
|
+
} else if ((tok === '--main-gpu' || tok === '-mg') && i + 1 < tokens.length) {
|
|
3001
|
+
mainGpu = Number(tokens[++i]);
|
|
3002
|
+
} else if (tok === '--fa' || tok === '--flash-attn') {
|
|
3003
|
+
flashAttn = true;
|
|
3004
|
+
} else if (tok === '--no-fa' || tok === '--no-flash-attn') {
|
|
3005
|
+
flashAttn = false;
|
|
3006
|
+
} else if ((tok === '-b' || tok === '--batch-size') && i + 1 < tokens.length) {
|
|
3007
|
+
batchSize = Number(tokens[++i]);
|
|
3008
|
+
} else if ((tok === '-ub' || tok === '--ubatch-size') && i + 1 < tokens.length) {
|
|
3009
|
+
ubatchSize = Number(tokens[++i]);
|
|
3010
|
+
} else if ((tok === '--ctk' || tok === '--kv-cache') && i + 1 < tokens.length) {
|
|
3011
|
+
kvCacheDtype = tokens[++i].toLowerCase();
|
|
3012
|
+
} else if ((tok === '--mem-util' || tok === '--gpu-mem-util') && i + 1 < tokens.length) {
|
|
3013
|
+
memUtil = Number(tokens[++i]);
|
|
3014
|
+
} else if (tok === '--max-model-len' && i + 1 < tokens.length) {
|
|
3015
|
+
maxModelLen = Number(tokens[++i]);
|
|
3016
|
+
} else if (tok === '--tp' && i + 1 < tokens.length) {
|
|
3017
|
+
tp = Number(tokens[++i]);
|
|
3018
|
+
} else if (tok === '--dtype' && i + 1 < tokens.length) {
|
|
3019
|
+
dtype = tokens[++i].toLowerCase();
|
|
3020
|
+
} else if (tok === '--enforce-eager') {
|
|
3021
|
+
enforceEager = true;
|
|
3022
|
+
} else if (tok === '--no-enforce-eager') {
|
|
3023
|
+
enforceEager = false;
|
|
3024
|
+
} else if (tok === '--prefix-caching') {
|
|
3025
|
+
enablePrefixCaching = true;
|
|
3026
|
+
} else if (tok === '--no-prefix-caching') {
|
|
3027
|
+
enablePrefixCaching = false;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
const updatePayload = {};
|
|
3032
|
+
if (engine !== undefined) updatePayload.activeEngine = engine;
|
|
3033
|
+
if (gpu !== undefined) updatePayload.gpuOffload = gpu;
|
|
3034
|
+
if (threads !== undefined) updatePayload.cpuThreads = threads;
|
|
3035
|
+
if (splitMode !== undefined) updatePayload.splitMode = splitMode;
|
|
3036
|
+
if (tensorSplit !== undefined) updatePayload.tensorSplit = tensorSplit;
|
|
3037
|
+
if (mainGpu !== undefined) updatePayload.mainGpu = mainGpu;
|
|
3038
|
+
if (flashAttn !== undefined) updatePayload.flashAttn = flashAttn;
|
|
3039
|
+
if (batchSize !== undefined) updatePayload.batchSize = batchSize;
|
|
3040
|
+
if (ubatchSize !== undefined) updatePayload.ubatchSize = ubatchSize;
|
|
3041
|
+
if (kvCacheDtype !== undefined) updatePayload.kvCacheDtype = kvCacheDtype;
|
|
3042
|
+
|
|
3043
|
+
if (memUtil !== undefined || maxModelLen !== undefined || tp !== undefined || dtype !== undefined || enforceEager !== undefined || enablePrefixCaching !== undefined || (kvCacheDtype !== undefined && updatePayload.activeEngine === 'vllm')) {
|
|
3044
|
+
updatePayload.vllm = {};
|
|
3045
|
+
if (memUtil !== undefined) updatePayload.vllm.gpuMemoryUtilization = memUtil;
|
|
3046
|
+
if (maxModelLen !== undefined) updatePayload.vllm.maxModelLen = maxModelLen;
|
|
3047
|
+
if (tp !== undefined) updatePayload.vllm.tensorParallelSize = tp;
|
|
3048
|
+
if (dtype !== undefined) updatePayload.vllm.dtype = dtype;
|
|
3049
|
+
if (enforceEager !== undefined) updatePayload.vllm.enforceEager = enforceEager;
|
|
3050
|
+
if (enablePrefixCaching !== undefined) updatePayload.vllm.enablePrefixCaching = enablePrefixCaching;
|
|
3051
|
+
if (kvCacheDtype !== undefined) updatePayload.vllm.kvCacheDtype = kvCacheDtype;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
savePersistedModelLoadSettings(updatePayload);
|
|
3055
|
+
console.log(`\n ${C.emerald}[OK] Model load parameters updated successfully.${C.reset}\n`);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
const activeEngine = getActiveLocalEngine() || 'lms';
|
|
3059
|
+
const settings = getPersistedModelLoadSettings();
|
|
3060
|
+
|
|
3061
|
+
printSolidPanel({
|
|
3062
|
+
title: 'Local Inference Engine Parameters',
|
|
3063
|
+
lines: [
|
|
3064
|
+
`${C.gold}Active Engine:${C.reset} ${C.bold}${C.white}${activeEngine.toUpperCase()}${C.reset}`,
|
|
3065
|
+
'',
|
|
3066
|
+
`${C.bold}${C.lavender}LM Studio & Multi-GPU Configuration (llama.cpp):${C.reset}`,
|
|
3067
|
+
` ${C.dim}• GPU Offload (--gpu):${C.reset} ${C.white}${settings.gpuOffload || 'auto'}${C.reset}`,
|
|
3068
|
+
` ${C.dim}• CPU Threads (--threads):${C.reset} ${C.white}${settings.cpuThreads !== null ? settings.cpuThreads : 'auto (4)'}${C.reset}`,
|
|
3069
|
+
` ${C.dim}• Flash Attention (--fa):${C.reset} ${settings.flashAttn !== false ? C.emerald + 'Enabled' : C.amber + 'Disabled'}${C.reset}`,
|
|
3070
|
+
` ${C.dim}• Split Mode (--split-mode):${C.reset} ${C.white}${settings.splitMode || 'layer'}${C.reset}`,
|
|
3071
|
+
` ${C.dim}• Tensor Split (-ts):${C.reset} ${C.white}${settings.tensorSplit || 'auto (equal)'}${C.reset}`,
|
|
3072
|
+
` ${C.dim}• Main GPU (--main-gpu):${C.reset} ${C.white}${settings.mainGpu ?? 0}${C.reset}`,
|
|
3073
|
+
` ${C.dim}• Batch / uBatch (-b / -ub):${C.reset} ${C.white}${settings.batchSize || 2048} / ${settings.ubatchSize || 512}${C.reset}`,
|
|
3074
|
+
` ${C.dim}• KV Cache Type (--ctk):${C.reset} ${C.white}${settings.kvCacheDtype || 'auto'}${C.reset}`,
|
|
3075
|
+
'',
|
|
3076
|
+
`${C.bold}${C.lavender}vLLM Configuration (PagedAttention):${C.reset}`,
|
|
3077
|
+
` ${C.dim}• GPU Memory Util (--mem-util):${C.reset} ${C.white}${settings.vllm.gpuMemoryUtilization} (${Math.round(settings.vllm.gpuMemoryUtilization * 100)}%)${C.reset}`,
|
|
3078
|
+
` ${C.dim}• Max Model Len (--max-model-len):${C.reset} ${C.white}${settings.vllm.maxModelLen || 'auto (model default)'}${C.reset}`,
|
|
3079
|
+
` ${C.dim}• Tensor Parallel (--tp):${C.reset} ${C.white}${settings.vllm.tensorParallelSize}${C.reset}`,
|
|
3080
|
+
` ${C.dim}• Dtype (--dtype):${C.reset} ${C.white}${settings.vllm.dtype || 'auto'}${C.reset}`,
|
|
3081
|
+
` ${C.dim}• Enforce Eager:${C.reset} ${settings.vllm.enforceEager ? C.emerald + 'Enabled' : C.dim + 'Disabled'}${C.reset}`,
|
|
3082
|
+
` ${C.dim}• Prefix Caching:${C.reset} ${settings.vllm.enablePrefixCaching ? C.emerald + 'Enabled' : C.dim + 'Disabled'}${C.reset}`,
|
|
3083
|
+
'',
|
|
3084
|
+
`${C.dim}Update: /model config --gpu max --threads 8 | /model config --split-mode layer --tensor-split 20,12 --fa | /model config --reset${C.reset}`,
|
|
3085
|
+
],
|
|
3086
|
+
headerBg: '\x1b[48;2;40;48;68m',
|
|
3087
|
+
bg: '\x1b[48;2;28;34;48m',
|
|
3088
|
+
});
|
|
3089
|
+
return 'HANDLED';
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
if (action === 'preset' || action === 'presets') {
|
|
3093
|
+
const {
|
|
3094
|
+
savePersistedProviderBaseUrl,
|
|
3095
|
+
} = await import('./config.js');
|
|
3096
|
+
|
|
3097
|
+
const PRESETS = [
|
|
3098
|
+
{ id: 'gemma4', name: 'Local LM Studio (Gemma 4 E4B)', provider: 'lmstudio', model: 'google/gemma-4-e4b', baseUrl: 'http://127.0.0.1:1234/v1', desc: 'Universal local inference via llama.cpp (Port 1234)' },
|
|
3099
|
+
{ id: 'vllm', name: 'vLLM WSL2 (Gemma 4 E4B)', provider: 'vllm', model: 'google/gemma-4-e4b', baseUrl: 'http://127.0.0.1:8000/v1', desc: 'Enterprise PagedAttention engine on WSL2/Linux (Port 8000)' },
|
|
3100
|
+
{ id: 'qwen', name: 'Ollama Local (Qwen 2.5 7B)', provider: 'custom', model: 'qwen2.5:7b', baseUrl: 'http://127.0.0.1:11434/v1', desc: 'Local Ollama instance on port 11434' },
|
|
3101
|
+
{ id: 'gemini', name: 'Google Gemini 3.7 Flash', provider: 'gemini', model: 'gemini-3.7-flash', baseUrl: '', desc: 'High-speed cloud multimodal reasoning' },
|
|
3102
|
+
{ id: 'groq', name: 'Groq GPT-OSS 120B', provider: 'groq', model: 'openai/gpt-oss-120b', baseUrl: '', desc: 'Ultra-fast LPU inference' },
|
|
3103
|
+
{ id: 'deepseek', name: 'DeepSeek V4 Flash', provider: 'deepseek', model: 'deepseek-v4-flash', baseUrl: '', desc: 'State-of-the-art cost-effective reasoning' },
|
|
3104
|
+
{ id: 'luna', name: 'OpenAI GPT-5.6 Luna', provider: 'openai', model: 'gpt-5.6-luna', baseUrl: '', desc: 'Flagship reasoning model' },
|
|
3105
|
+
{ id: 'openrouter', name: 'OpenRouter Auto', provider: 'openrouter', model: 'openrouter/auto', baseUrl: '', desc: 'Unified multi-provider fallback gateway' },
|
|
3106
|
+
];
|
|
3107
|
+
|
|
3108
|
+
const targetPresetId = (ctx.extra || '').trim().toLowerCase();
|
|
3109
|
+
if (targetPresetId) {
|
|
3110
|
+
const found = PRESETS.find(p => p.id === targetPresetId || p.id.includes(targetPresetId) || p.provider === targetPresetId);
|
|
3111
|
+
if (!found) {
|
|
3112
|
+
printRichCard({
|
|
3113
|
+
title: 'Preset Not Found',
|
|
3114
|
+
status: 'error',
|
|
3115
|
+
lines: [
|
|
3116
|
+
`${C.red}Unknown preset "${targetPresetId}".${C.reset}`,
|
|
3117
|
+
`${C.dim}Available presets: ${PRESETS.map(p => p.id).join(', ')}${C.reset}`,
|
|
3118
|
+
],
|
|
3119
|
+
});
|
|
3120
|
+
return 'HANDLED';
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
try {
|
|
3124
|
+
if (found.baseUrl) {
|
|
3125
|
+
savePersistedProviderBaseUrl(found.provider, found.baseUrl);
|
|
3126
|
+
}
|
|
3127
|
+
if (ctx.client.switchProvider) {
|
|
3128
|
+
ctx.client.switchProvider(found.provider, { model: found.model, baseUrl: found.baseUrl || undefined });
|
|
3129
|
+
}
|
|
3130
|
+
if (ctx.agent?.client?.switchProvider) {
|
|
3131
|
+
ctx.agent.client.switchProvider(found.provider, { model: found.model, baseUrl: found.baseUrl || undefined });
|
|
3132
|
+
}
|
|
3133
|
+
if (ctx.modelManager) {
|
|
3134
|
+
ctx.modelManager.select(found.model, [found.model]);
|
|
3135
|
+
}
|
|
3136
|
+
ctx.setCatalogOnlyMode?.(false);
|
|
3137
|
+
if (ctx.tuiPrompt) {
|
|
3138
|
+
ctx.tuiPrompt.modelName = found.model;
|
|
3139
|
+
ctx.tuiPrompt.isConnected = true;
|
|
3140
|
+
ctx.tuiPrompt.render();
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
printSolidPanel({
|
|
3144
|
+
title: `Preset Applied • ${found.name}`,
|
|
3145
|
+
lines: [
|
|
3146
|
+
`${C.gold}Provider:${C.reset} ${C.white}${found.provider}${C.reset}`,
|
|
3147
|
+
`${C.gold}Model:${C.reset} ${C.bold}${C.emerald}${found.model}${C.reset}`,
|
|
3148
|
+
`${C.gold}Endpoint:${C.reset} ${found.baseUrl ? C.cyberCyan + found.baseUrl : C.dim + '(Cloud Default)'}${C.reset}`,
|
|
3149
|
+
`${C.gold}Notes:${C.reset} ${C.dim}${found.desc}${C.reset}`,
|
|
3150
|
+
'',
|
|
3151
|
+
`${C.dim}Tip: Run "/model ping" or "/chat" to start.${C.reset}`,
|
|
3152
|
+
],
|
|
3153
|
+
headerBg: '\x1b[48;2;30;60;50m',
|
|
3154
|
+
bg: '\x1b[48;2;20;35;30m',
|
|
3155
|
+
});
|
|
3156
|
+
} catch (err) {
|
|
3157
|
+
console.log(`\n ${C.red}[PRESET ERROR] ${err.message || String(err)}${C.reset}\n`);
|
|
3158
|
+
}
|
|
3159
|
+
return 'HANDLED';
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
printSolidPanel({
|
|
3163
|
+
title: 'Curated Model Presets',
|
|
3164
|
+
lines: [
|
|
3165
|
+
`${C.dim}Quick 1-click model and provider setups matching Web Chat:${C.reset}`,
|
|
3166
|
+
'',
|
|
3167
|
+
...PRESETS.map(p => {
|
|
3168
|
+
const isCurrent = ctx.client.provider === p.provider && (ctx.client.model === p.model || (ctx.client.model && ctx.client.model.includes(p.model)));
|
|
3169
|
+
const marker = isCurrent ? `${C.gold}[x]${C.reset}` : `${C.dim}[ ]${C.reset}`;
|
|
3170
|
+
return ` ${marker} ${C.bold}${C.white}${p.id.padEnd(12)}${C.reset} ${C.emerald}${p.model.padEnd(26)}${C.reset} ${C.dim}${p.desc}${C.reset}`;
|
|
3171
|
+
}),
|
|
3172
|
+
'',
|
|
3173
|
+
`${C.dim}Apply preset: /model preset <id> (e.g. /model preset gemma4, /model preset luna)${C.reset}`,
|
|
3174
|
+
],
|
|
3175
|
+
headerBg: '\x1b[48;2;48;40;60m',
|
|
3176
|
+
bg: '\x1b[48;2;34;28;42m',
|
|
3177
|
+
});
|
|
3178
|
+
return 'HANDLED';
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
if (action === 'files' || action === 'gguf') {
|
|
3182
|
+
const { listInstalledGgufs } = await import('./modelConfig.js');
|
|
3183
|
+
ctx.setIsProcessing?.(true, 'Scanning local storage for installed GGUF models…');
|
|
3184
|
+
const models = listInstalledGgufs(true);
|
|
3185
|
+
|
|
3186
|
+
if (models.length === 0) {
|
|
3187
|
+
printRichCard({
|
|
3188
|
+
title: 'Installed GGUF Models (0 Discovered)',
|
|
3189
|
+
status: 'info',
|
|
3190
|
+
lines: [
|
|
3191
|
+
`${C.amber}No local .gguf models discovered.${C.reset}`,
|
|
3192
|
+
'',
|
|
3193
|
+
`${C.dim}Searched standard directories:${C.reset}`,
|
|
3194
|
+
` • ~/.lmstudio/models`,
|
|
3195
|
+
` • ~/.cache/lm-studio/models`,
|
|
3196
|
+
` • Custom LM Studio model directory`,
|
|
3197
|
+
'',
|
|
3198
|
+
`${C.dim}Tip: Download models via LM Studio or copy .gguf files to ~/.lmstudio/models${C.reset}`,
|
|
3199
|
+
],
|
|
3200
|
+
});
|
|
3201
|
+
return 'HANDLED';
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
const lines = models.slice(0, 30).map((m, idx) => {
|
|
3205
|
+
const num = String(idx + 1).padStart(2, ' ');
|
|
3206
|
+
const isCurrent = ctx.client.model && ctx.client.model.toLowerCase().includes(m.fileName.toLowerCase());
|
|
3207
|
+
const marker = isCurrent ? `${C.gold}[x]${C.reset}` : `${C.dim}[ ]${C.reset}`;
|
|
3208
|
+
return ` ${marker} ${C.dim}${num}.${C.reset} ${C.bold}${C.white}${m.fileName}${C.reset} ${C.slate}${m.quant || 'GGUF'}${C.reset} ${C.cyberCyan}${m.sizeGb} GB${C.reset}`;
|
|
3209
|
+
});
|
|
3210
|
+
|
|
3211
|
+
printSolidPanel({
|
|
3212
|
+
title: `Installed GGUF Models (${models.length} on disk)`,
|
|
3213
|
+
lines: [
|
|
3214
|
+
...lines,
|
|
3215
|
+
'',
|
|
3216
|
+
`${C.dim}Load model: /model load <filename> (e.g. /model load ${models[0]?.fileName || 'model.gguf'})${C.reset}`,
|
|
3217
|
+
],
|
|
3218
|
+
headerBg: '\x1b[48;2;36;52;68m',
|
|
3219
|
+
bg: '\x1b[48;2;24;36;48m',
|
|
3220
|
+
});
|
|
3221
|
+
return 'HANDLED';
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
if (action === 'temp' || action === 'temperature') {
|
|
3225
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3226
|
+
const valArg = (ctx.extra || '').trim();
|
|
3227
|
+
if (valArg) {
|
|
3228
|
+
const num = Number(valArg);
|
|
3229
|
+
if (Number.isFinite(num) && num >= 0 && num <= 2) {
|
|
3230
|
+
savePersistedInferenceSettings({ temperature: num });
|
|
3231
|
+
console.log(`\n ${C.emerald}[OK] Inference Temperature updated to ${num.toFixed(2)}.${C.reset}\n`);
|
|
3232
|
+
} else {
|
|
3233
|
+
console.log(`\n ${C.amber}[NOTICE] Temperature must be between 0.0 and 2.0 (e.g. /model temp 0.7).${C.reset}\n`);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
const current = getPersistedInferenceSettings();
|
|
3237
|
+
console.log(` ${C.gold}Current Temperature:${C.reset} ${C.bold}${C.white}${current.temperature}${C.reset} ${C.dim}(0.20 Precise, 0.70 Balanced, 0.95 Creative)${C.reset}\n`);
|
|
3238
|
+
return 'HANDLED';
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
if (action === 'topp') {
|
|
3242
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3243
|
+
const valArg = (ctx.extra || '').trim();
|
|
3244
|
+
if (valArg) {
|
|
3245
|
+
const num = Number(valArg);
|
|
3246
|
+
if (Number.isFinite(num) && num >= 0.01 && num <= 1) {
|
|
3247
|
+
savePersistedInferenceSettings({ topP: num });
|
|
3248
|
+
console.log(`\n ${C.emerald}[OK] Inference Top-P updated to ${num.toFixed(2)}.${C.reset}\n`);
|
|
3249
|
+
} else {
|
|
3250
|
+
console.log(`\n ${C.amber}[NOTICE] Top-P must be between 0.01 and 1.0 (e.g. /model topp 0.95).${C.reset}\n`);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
const current = getPersistedInferenceSettings();
|
|
3254
|
+
console.log(` ${C.gold}Current Top-P:${C.reset} ${C.bold}${C.white}${current.topP}${C.reset} ${C.dim}(Nucleus Sampling cutoff)${C.reset}\n`);
|
|
3255
|
+
return 'HANDLED';
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
if (action === 'minp') {
|
|
3259
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3260
|
+
const valArg = (ctx.extra || '').trim();
|
|
3261
|
+
if (valArg) {
|
|
3262
|
+
const num = Number(valArg);
|
|
3263
|
+
if (Number.isFinite(num) && num >= 0 && num <= 1) {
|
|
3264
|
+
savePersistedInferenceSettings({ minP: num });
|
|
3265
|
+
console.log(`\n ${C.emerald}[OK] Inference Min-P updated to ${num.toFixed(2)}.${C.reset}\n`);
|
|
3266
|
+
} else {
|
|
3267
|
+
console.log(`\n ${C.amber}[NOTICE] Min-P must be between 0.00 and 1.00 (e.g. /model minp 0.08).${C.reset}\n`);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
const current = getPersistedInferenceSettings();
|
|
3271
|
+
console.log(` ${C.gold}Current Min-P:${C.reset} ${C.bold}${C.white}${current.minP}${C.reset} ${C.dim}(Dynamic probability threshold filter, recommended: 0.05 - 0.10)${C.reset}\n`);
|
|
3272
|
+
return 'HANDLED';
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
if (action === 'repeat' || action === 'repeatpenalty' || action === 'rep') {
|
|
3276
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3277
|
+
const valArg = (ctx.extra || '').trim();
|
|
3278
|
+
if (valArg) {
|
|
3279
|
+
const num = Number(valArg);
|
|
3280
|
+
if (Number.isFinite(num) && num >= 1.0 && num <= 2.0) {
|
|
3281
|
+
savePersistedInferenceSettings({ repeatPenalty: num });
|
|
3282
|
+
console.log(`\n ${C.emerald}[OK] Inference Repeat Penalty updated to ${num.toFixed(2)}.${C.reset}\n`);
|
|
3283
|
+
} else {
|
|
3284
|
+
console.log(`\n ${C.amber}[NOTICE] Repeat Penalty must be between 1.00 and 2.00 (e.g. /model repeat 1.10).${C.reset}\n`);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
const current = getPersistedInferenceSettings();
|
|
3288
|
+
console.log(` ${C.gold}Current Repeat Penalty:${C.reset} ${C.bold}${C.white}${current.repeatPenalty}${C.reset} ${C.dim}(1.00 Disabled, 1.10 Balanced, 1.20 Strong suppression)${C.reset}\n`);
|
|
3289
|
+
return 'HANDLED';
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
if (action === 'ctx' || action === 'context') {
|
|
3293
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3294
|
+
const valArg = (ctx.extra || '').trim().toLowerCase();
|
|
3295
|
+
if (valArg) {
|
|
3296
|
+
let parsed = Number(valArg);
|
|
3297
|
+
if (valArg === '4k') parsed = 4096;
|
|
3298
|
+
else if (valArg === '8k') parsed = 8192;
|
|
3299
|
+
else if (valArg === '16k') parsed = 16384;
|
|
3300
|
+
else if (valArg === '32k') parsed = 32768;
|
|
3301
|
+
else if (valArg === '64k') parsed = 65536;
|
|
3302
|
+
else if (valArg === '128k') parsed = 131072;
|
|
3303
|
+
|
|
3304
|
+
if (Number.isFinite(parsed) && parsed >= 1024 && parsed <= 131072) {
|
|
3305
|
+
savePersistedInferenceSettings({ contextLimit: parsed });
|
|
3306
|
+
console.log(`\n ${C.emerald}[OK] Context Window Limit updated to ${parsed.toLocaleString()} tokens.${C.reset}\n`);
|
|
3307
|
+
} else {
|
|
3308
|
+
console.log(`\n ${C.amber}[NOTICE] Context limit must be between 1024 and 131072 (e.g. 4k, 8k, 16k, 32k, 64k, 128k).${C.reset}\n`);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
const current = getPersistedInferenceSettings();
|
|
3312
|
+
console.log(` ${C.gold}Current Context Window Limit:${C.reset} ${C.bold}${C.white}${current.contextLimit.toLocaleString()} tokens${C.reset}\n`);
|
|
3313
|
+
return 'HANDLED';
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
if (action === 'thinking') {
|
|
3317
|
+
const { getPersistedInferenceSettings, savePersistedInferenceSettings } = await import('./config.js');
|
|
3318
|
+
const valArg = (ctx.extra || '').trim().toLowerCase();
|
|
3319
|
+
if (valArg) {
|
|
3320
|
+
if (['show', 'collapse', 'hide'].includes(valArg)) {
|
|
3321
|
+
savePersistedInferenceSettings({ thinking: valArg });
|
|
3322
|
+
console.log(`\n ${C.emerald}[OK] Thinking mode updated to "${valArg}".${C.reset}\n`);
|
|
3323
|
+
} else {
|
|
3324
|
+
console.log(`\n ${C.amber}[NOTICE] Thinking mode must be one of: show, collapse, hide.${C.reset}\n`);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
const current = getPersistedInferenceSettings();
|
|
3328
|
+
console.log(` ${C.gold}Current Thinking Mode:${C.reset} ${C.bold}${C.white}${current.thinking}${C.reset} ${C.dim}(show: Full CoT, collapse: Compact, hide: Direct Answer)${C.reset}\n`);
|
|
3329
|
+
return 'HANDLED';
|
|
3330
|
+
}
|
|
3331
|
+
|
|
2500
3332
|
if (action === 'ping') {
|
|
2501
3333
|
try {
|
|
2502
3334
|
const start = Date.now();
|
|
@@ -2610,8 +3442,14 @@ export const handlers = {
|
|
|
2610
3442
|
return 'HANDLED';
|
|
2611
3443
|
}
|
|
2612
3444
|
|
|
3445
|
+
const nonModelActions = new Set([
|
|
3446
|
+
'status', 'diag', 'diagnostics', 'engine', 'config', 'params',
|
|
3447
|
+
'preset', 'presets', 'files', 'gguf', 'temp', 'temperature',
|
|
3448
|
+
'topp', 'minp', 'repeat', 'repeatpenalty', 'rep', 'ctx', 'context',
|
|
3449
|
+
'thinking', 'unload', 'stop', 'ping', 'reconnect', 'recommend', 'rec', 'list'
|
|
3450
|
+
]);
|
|
2613
3451
|
const isDirectUse = action === 'use';
|
|
2614
|
-
const targetModel = isDirectUse ? (ctx.extra?.trim() || '') : (!
|
|
3452
|
+
const targetModel = isDirectUse ? (ctx.extra?.trim() || '') : (!nonModelActions.has(action) ? rawSub : '');
|
|
2615
3453
|
|
|
2616
3454
|
if (targetModel) {
|
|
2617
3455
|
try {
|
|
@@ -2665,17 +3503,31 @@ export const handlers = {
|
|
|
2665
3503
|
return 'HANDLED';
|
|
2666
3504
|
}
|
|
2667
3505
|
|
|
3506
|
+
const {
|
|
3507
|
+
getActiveLocalEngine,
|
|
3508
|
+
getPersistedModelLoadSettings,
|
|
3509
|
+
getPersistedInferenceSettings,
|
|
3510
|
+
} = await import('./config.js');
|
|
3511
|
+
const activeEngine = getActiveLocalEngine() || 'lms';
|
|
3512
|
+
const loadSettings = getPersistedModelLoadSettings();
|
|
3513
|
+
const infSettings = getPersistedInferenceSettings();
|
|
3514
|
+
|
|
2668
3515
|
printSolidPanel({
|
|
2669
3516
|
title: `${providerName} Diagnostics`,
|
|
2670
3517
|
lines: [
|
|
2671
|
-
`${C.gold}Provider:${C.reset}
|
|
2672
|
-
`${C.gold}Status:${C.reset}
|
|
2673
|
-
`${C.gold}State:${C.reset}
|
|
2674
|
-
`${C.gold}Model:${C.reset}
|
|
2675
|
-
`${C.gold}Available:${C.reset}
|
|
2676
|
-
`${C.gold}URL:${C.reset}
|
|
3518
|
+
`${C.gold}Provider:${C.reset} ${C.white}${ctx.client.providerName || 'LM Studio'}${C.reset}`,
|
|
3519
|
+
`${C.gold}Status:${C.reset} ${liveCheck.connected ? C.emerald + '● Online' : C.red + '○ Offline'}${C.reset}${liveCheck.latencyMs ? ` ${C.dim}(${liveCheck.latencyMs}ms)${C.reset}` : ''}`,
|
|
3520
|
+
`${C.gold}State:${C.reset} ${C.white}${liveCheck.state || 'ready'}${C.reset}`,
|
|
3521
|
+
`${C.gold}Model:${C.reset} ${C.bold}${C.white}${ctx.client.model || 'None'}${C.reset}`,
|
|
3522
|
+
`${C.gold}Available:${C.reset} ${C.white}${liveCheck.models?.length || 0} model(s)${C.reset}`,
|
|
3523
|
+
`${C.gold}URL:${C.reset} ${C.cyberCyan}${ctx.baseUrl}${C.reset}`,
|
|
2677
3524
|
'',
|
|
2678
|
-
`${C.
|
|
3525
|
+
`${C.bold}${C.lavender}Engine & Inference Settings:${C.reset}`,
|
|
3526
|
+
` ${C.dim}• Active Local Engine:${C.reset} ${C.white}${activeEngine.toUpperCase()}${C.reset} ${C.dim}(LM Studio :1234 | vLLM :8000)${C.reset}`,
|
|
3527
|
+
` ${C.dim}• Load Parameters:${C.reset} ${activeEngine === 'lms' ? `GPU Offload=${loadSettings.gpuOffload}, Threads=${loadSettings.cpuThreads !== null ? loadSettings.cpuThreads : 'auto'}, SplitMode=${loadSettings.splitMode || 'layer'}, FlashAttn=${loadSettings.flashAttn !== false ? 'on' : 'off'}` : `Mem Util=${loadSettings.vllm.gpuMemoryUtilization}, MaxLen=${loadSettings.vllm.maxModelLen || 'auto'}, TP=${loadSettings.vllm.tensorParallelSize}`}`,
|
|
3528
|
+
` ${C.dim}• Inference Settings:${C.reset} Temp=${infSettings.temperature}, Top-P=${infSettings.topP}, Min-P=${infSettings.minP}, RepeatPenalty=${infSettings.repeatPenalty}, CtxLimit=${infSettings.contextLimit.toLocaleString()}, Thinking=${infSettings.thinking}`,
|
|
3529
|
+
'',
|
|
3530
|
+
`${C.dim}Commands: /model engine | /model config | /model preset | /model files | /model list | /model use <id>${C.reset}`
|
|
2679
3531
|
],
|
|
2680
3532
|
headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
|
|
2681
3533
|
});
|
|
@@ -3107,6 +3959,55 @@ export const handlers = {
|
|
|
3107
3959
|
return 'HANDLED';
|
|
3108
3960
|
},
|
|
3109
3961
|
|
|
3962
|
+
'/process': async (ctx) => {
|
|
3963
|
+
const { listBackgroundProcesses, stopBackgroundProcess } = await import('./processManager.js');
|
|
3964
|
+
const sub = (ctx.sub || 'list').toLowerCase();
|
|
3965
|
+
if (sub === 'kill' || sub === 'stop') {
|
|
3966
|
+
const target = (ctx.extra || '').trim();
|
|
3967
|
+
if (!target) {
|
|
3968
|
+
console.log(`\n ${C.amber}Usage: /process kill <name|pid>${C.reset}\n`);
|
|
3969
|
+
return 'HANDLED';
|
|
3970
|
+
}
|
|
3971
|
+
const res = stopBackgroundProcess(target);
|
|
3972
|
+
if (res.ok) {
|
|
3973
|
+
console.log(`\n ${C.emerald}[OK] Process "${target}" (PID: ${res.pid}) terminated cleanly.${C.reset}\n`);
|
|
3974
|
+
} else {
|
|
3975
|
+
console.log(`\n ${C.amber}[FAILED] ${res.reason || `Could not kill process "${target}"`}.${C.reset}\n`);
|
|
3976
|
+
}
|
|
3977
|
+
return 'HANDLED';
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
const procs = listBackgroundProcesses();
|
|
3981
|
+
if (procs.length === 0) {
|
|
3982
|
+
printSolidPanel({
|
|
3983
|
+
title: 'Background Process Manager',
|
|
3984
|
+
lines: [
|
|
3985
|
+
`${C.dim}No background processes currently tracked.${C.reset}`,
|
|
3986
|
+
`${C.slate}PIDs are automatically registered when starting engines (vLLM, LM Studio) or background tasks.${C.reset}`,
|
|
3987
|
+
],
|
|
3988
|
+
});
|
|
3989
|
+
return 'HANDLED';
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
const lines = [];
|
|
3993
|
+
for (const p of procs) {
|
|
3994
|
+
const statusStr = p.isAlive ? `${C.emerald}RUNNING${C.reset}` : `${C.roseGlow}STOPPED${C.reset}`;
|
|
3995
|
+
lines.push(`${C.gold}Name:${C.reset} ${C.white}${p.name}${C.reset} (PID: ${C.bold}${p.pid}${C.reset}) • ${statusStr}`);
|
|
3996
|
+
lines.push(`${C.gold}Uptime:${C.reset} ${p.uptimeSeconds}s`);
|
|
3997
|
+
if (p.command) lines.push(`${C.gold}Command:${C.reset} ${C.dim}${p.command}${C.reset}`);
|
|
3998
|
+
lines.push('');
|
|
3999
|
+
}
|
|
4000
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
|
|
4001
|
+
|
|
4002
|
+
printSolidPanel({
|
|
4003
|
+
title: `Tracked Background Processes (${procs.length})`,
|
|
4004
|
+
lines,
|
|
4005
|
+
headerBg: '\x1b[48;2;48;54;70m',
|
|
4006
|
+
bg: '\x1b[48;2;36;40;52m',
|
|
4007
|
+
});
|
|
4008
|
+
return 'HANDLED';
|
|
4009
|
+
},
|
|
4010
|
+
|
|
3110
4011
|
'/gpu': async (ctx) => {
|
|
3111
4012
|
ctx.setIsProcessing?.(true, 'Probing hardware…');
|
|
3112
4013
|
const gpuData = await getGpuMetrics().catch(() => ({ available: false }));
|
|
@@ -3226,14 +4127,15 @@ export const handlers = {
|
|
|
3226
4127
|
const argTokens = String(sub === 'check' ? (extra || '') : [sub, extra].filter(Boolean).join(' '))
|
|
3227
4128
|
.trim().split(/\s+/).filter(Boolean);
|
|
3228
4129
|
const { overrides, positional, force } = parseVramArgs(argTokens);
|
|
3229
|
-
const target = positional.join(' ');
|
|
4130
|
+
const target = positional.join(' ').trim();
|
|
3230
4131
|
let modelName = null;
|
|
3231
4132
|
let modelPath = null;
|
|
3232
|
-
|
|
4133
|
+
const isPlaceholder = !target || /^<.*>$/.test(target);
|
|
4134
|
+
if (!isPlaceholder) {
|
|
3233
4135
|
if (/\.gguf$/i.test(target)) modelPath = target;
|
|
3234
4136
|
else modelName = target;
|
|
3235
4137
|
} else {
|
|
3236
|
-
modelName = ctx.agent?.client?.model ||
|
|
4138
|
+
modelName = ctx.agent?.client?.model || 'google/gemma-4-e4b';
|
|
3237
4139
|
}
|
|
3238
4140
|
|
|
3239
4141
|
const result = await computeVramEstimate({ model: modelName, modelPath, ...overrides });
|
|
@@ -3603,11 +4505,21 @@ export const handlers = {
|
|
|
3603
4505
|
|
|
3604
4506
|
'/quiz': async (ctx) => {
|
|
3605
4507
|
const { sub, extra } = ctx;
|
|
3606
|
-
const
|
|
4508
|
+
const rawArgs = [sub, extra].filter(Boolean).join(' ').toLowerCase();
|
|
4509
|
+
const isMcp = rawArgs.includes('mcp');
|
|
4510
|
+
const langTokens = rawArgs.split(/\s+/).filter(t => ['th', 'en', 'zh'].includes(t));
|
|
4511
|
+
const langArg = (sub === 'lang' ? extra : langTokens[0] || sub || '').toLowerCase().trim();
|
|
3607
4512
|
const quizLang = ['th', 'en', 'zh'].includes(langArg) ? langArg : (ctx.agent?.memory?.preferences?.language || 'en');
|
|
3608
|
-
const i18n = QUIZ_I18N[quizLang] || QUIZ_I18N.en;
|
|
3609
4513
|
|
|
3610
|
-
const
|
|
4514
|
+
const i18n = isMcp ? (MCP_QUIZ_I18N[quizLang] || MCP_QUIZ_I18N.en) : (QUIZ_I18N[quizLang] || QUIZ_I18N.en);
|
|
4515
|
+
const questionsSource = isMcp ? MCP_QUIZ_QUESTIONS : QUIZ_QUESTIONS;
|
|
4516
|
+
const shuffled = isMcp ? shuffleMcpQuizQuestions(MCP_QUIZ_QUESTIONS) : shuffleQuizQuestions(QUIZ_QUESTIONS);
|
|
4517
|
+
const targetScore = questionsSource.length;
|
|
4518
|
+
const challengeEndpoint = isMcp ? '/api/v1/mcp/quiz/challenge' : '/api/v1/quiz/challenge';
|
|
4519
|
+
const claimEndpoint = isMcp ? '/api/v1/mcp/keys/claim' : '/api/v1/keys/claim';
|
|
4520
|
+
const keyPref = isMcp ? 'mcpKey' : 'vipKey';
|
|
4521
|
+
const keyPrefix = isMcp ? 'kiku_mcp_sk_' : 'kiku_vip_sk_';
|
|
4522
|
+
|
|
3611
4523
|
let score = 0;
|
|
3612
4524
|
let challengeToken = null;
|
|
3613
4525
|
let isOnline = false;
|
|
@@ -3616,17 +4528,16 @@ export const handlers = {
|
|
|
3616
4528
|
title: i18n.title,
|
|
3617
4529
|
lines: [
|
|
3618
4530
|
`${C.bold}${C.white}${i18n.subtitle}${C.reset}`,
|
|
3619
|
-
`${C.dim}Language: ${quizLang.toUpperCase()} | Questions:
|
|
4531
|
+
`${C.dim}Language: ${quizLang.toUpperCase()} | Questions: ${targetScore} | Target: ${targetScore}/${targetScore}${C.reset}`,
|
|
3620
4532
|
],
|
|
3621
|
-
bg: '\x1b[48;2;48;40;60m'
|
|
4533
|
+
bg: isMcp ? '\x1b[48;2;60;36;68m' : '\x1b[48;2;48;40;60m'
|
|
3622
4534
|
});
|
|
3623
4535
|
|
|
3624
|
-
|
|
3625
4536
|
const baseUrl = ctx.baseUrl || process.env.KIKU_API_URL || 'http://localhost:3000';
|
|
3626
4537
|
const controller = new AbortController();
|
|
3627
4538
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
3628
4539
|
try {
|
|
3629
|
-
const res = await fetch(`${baseUrl}
|
|
4540
|
+
const res = await fetch(`${baseUrl}${challengeEndpoint}`, { signal: controller.signal });
|
|
3630
4541
|
if (res.ok) {
|
|
3631
4542
|
const json = await res.json();
|
|
3632
4543
|
challengeToken = json.data?.challengeToken;
|
|
@@ -3662,8 +4573,8 @@ export const handlers = {
|
|
|
3662
4573
|
const askQuestion = () => {
|
|
3663
4574
|
if (typeof ctx.readInput === 'function') {
|
|
3664
4575
|
return ctx.readInput({
|
|
3665
|
-
prompt: i18n.answerPrompt,
|
|
3666
|
-
placeholder: i18n.answerPlaceholder,
|
|
4576
|
+
prompt: i18n.answerPrompt || 'Answer ❯ ',
|
|
4577
|
+
placeholder: i18n.answerPlaceholder || 'A, B, C, D (or h for hint)',
|
|
3667
4578
|
history: false,
|
|
3668
4579
|
cancelValue: 'q',
|
|
3669
4580
|
});
|
|
@@ -3676,7 +4587,7 @@ export const handlers = {
|
|
|
3676
4587
|
for (let i = 0; i < shuffled.length; i++) {
|
|
3677
4588
|
const q = shuffled[i];
|
|
3678
4589
|
const numberedText = q.q[quizLang] || q.q.en;
|
|
3679
|
-
const qText = numberedText.replace(/^\s*\d+[.)]\s*/, '');
|
|
4590
|
+
const qText = typeof numberedText === 'string' ? numberedText.replace(/^\s*\d+[.)]\s*/, '') : numberedText;
|
|
3680
4591
|
const opts = q.options[quizLang] || q.options.en;
|
|
3681
4592
|
|
|
3682
4593
|
printSolidPanel({
|
|
@@ -3686,7 +4597,7 @@ export const handlers = {
|
|
|
3686
4597
|
'',
|
|
3687
4598
|
...opts.map((opt, idx) => ` ${C.bold}${C.cyberCyan}${LETTER_LABELS[idx]})${C.reset} ${C.white}${opt}${C.reset}`),
|
|
3688
4599
|
],
|
|
3689
|
-
bg: '\x1b[48;2;36;40;52m'
|
|
4600
|
+
bg: isMcp ? '\x1b[48;2;48;32;58m' : '\x1b[48;2;36;40;52m'
|
|
3690
4601
|
});
|
|
3691
4602
|
|
|
3692
4603
|
let answeredCorrectly = false;
|
|
@@ -3708,7 +4619,7 @@ export const handlers = {
|
|
|
3708
4619
|
else if (['1', '2', '3', '4'].includes(input)) idx = Number.parseInt(input, 10) - 1;
|
|
3709
4620
|
|
|
3710
4621
|
if (idx < 0 || idx >= opts.length) {
|
|
3711
|
-
console.log(` ${C.amber}${i18n.invalidAnswer}${C.reset}`);
|
|
4622
|
+
console.log(` ${C.amber}${i18n.invalidAnswer || 'Please answer A, B, C, or D (or h for hint)'}${C.reset}`);
|
|
3712
4623
|
continue;
|
|
3713
4624
|
}
|
|
3714
4625
|
|
|
@@ -3737,17 +4648,17 @@ export const handlers = {
|
|
|
3737
4648
|
}
|
|
3738
4649
|
|
|
3739
4650
|
if (cancelled) {
|
|
3740
|
-
console.log(` ${C.amber}${i18n.cancelled}${C.reset}`);
|
|
4651
|
+
console.log(` ${C.amber}${i18n.cancelled || 'Quiz cancelled.'}${C.reset}`);
|
|
3741
4652
|
return 'HANDLED';
|
|
3742
4653
|
}
|
|
3743
4654
|
|
|
3744
4655
|
printSolidPanel({
|
|
3745
4656
|
title: `${i18n.score(score, shuffled.length)}`,
|
|
3746
|
-
lines: [`${C.bold}${score ===
|
|
4657
|
+
lines: [`${C.bold}${score === targetScore ? C.emerald : C.amber}${score === targetScore ? 'Perfect Score!' : `${score}/${targetScore} — Need ${targetScore}/${targetScore} to claim a ${isMcp ? 'MCP' : 'VIP'} Key.`}${C.reset}`],
|
|
3747
4658
|
});
|
|
3748
4659
|
|
|
3749
|
-
if (score <
|
|
3750
|
-
console.log(`\n ${C.dim}Run ${C.cyberCyan}/quiz${C.reset}${C.dim} to try again.${C.reset}\n`);
|
|
4660
|
+
if (score < targetScore) {
|
|
4661
|
+
console.log(`\n ${C.dim}Run ${C.cyberCyan}/quiz${isMcp ? ' mcp' : ''}${C.reset}${C.dim} to try again.${C.reset}\n`);
|
|
3751
4662
|
return 'HANDLED';
|
|
3752
4663
|
}
|
|
3753
4664
|
|
|
@@ -3755,10 +4666,9 @@ export const handlers = {
|
|
|
3755
4666
|
|
|
3756
4667
|
let key = null;
|
|
3757
4668
|
|
|
3758
|
-
|
|
3759
4669
|
if (isOnline && challengeToken) {
|
|
3760
4670
|
try {
|
|
3761
|
-
const res = await fetch(`${baseUrl}
|
|
4671
|
+
const res = await fetch(`${baseUrl}${claimEndpoint}`, {
|
|
3762
4672
|
method: 'POST',
|
|
3763
4673
|
headers: { 'Content-Type': 'application/json' },
|
|
3764
4674
|
body: JSON.stringify({ score, answers, challengeToken })
|
|
@@ -3772,17 +4682,24 @@ export const handlers = {
|
|
|
3772
4682
|
}
|
|
3773
4683
|
}
|
|
3774
4684
|
|
|
3775
|
-
|
|
3776
4685
|
if (!key) {
|
|
3777
4686
|
const token = crypto.randomBytes(16).toString('hex');
|
|
3778
|
-
key =
|
|
4687
|
+
key = `${keyPrefix}${token}`;
|
|
3779
4688
|
}
|
|
3780
4689
|
|
|
3781
|
-
ctx.agent?.memory?.setPreference(
|
|
4690
|
+
ctx.agent?.memory?.setPreference(keyPref, key);
|
|
3782
4691
|
|
|
3783
4692
|
printSolidPanel({
|
|
3784
4693
|
title: `${i18n.congratsTitle}`,
|
|
3785
|
-
lines: [
|
|
4694
|
+
lines: isMcp ? [
|
|
4695
|
+
`${C.bold}${C.white}${i18n.congratsDesc}${C.reset}`,
|
|
4696
|
+
'',
|
|
4697
|
+
`${C.bold}${C.roseGlow}${i18n.keyLabel}:${C.reset}`,
|
|
4698
|
+
` ${C.gold}${key}${C.reset}`,
|
|
4699
|
+
'',
|
|
4700
|
+
`${C.dim}Usage: header 'X-API-Key: ${key}' or 'Authorization: Bearer ${key}' on POST /mcp${C.reset}`,
|
|
4701
|
+
`${C.dim}Key saved. Run ${C.cyberCyan}/mcp-key config${C.reset}${C.dim} to view Claude Desktop & Cursor JSON configuration.${C.reset}`,
|
|
4702
|
+
] : [
|
|
3786
4703
|
`${C.bold}${C.white}${i18n.congratsDesc}${C.reset}`,
|
|
3787
4704
|
'',
|
|
3788
4705
|
`${C.bold}${C.roseGlow}${i18n.keyLabel}:${C.reset}`,
|
|
@@ -3791,7 +4708,7 @@ export const handlers = {
|
|
|
3791
4708
|
`${C.dim}Usage: header 'X-API-Key: ${key}' or query '?apiKey=${key}'${C.reset}`,
|
|
3792
4709
|
`${C.dim}Key saved automatically. Use ${C.cyberCyan}/key show${C.reset}${C.dim} to view or ${C.cyberCyan}/key clear${C.reset}${C.dim} to remove.${C.reset}`,
|
|
3793
4710
|
],
|
|
3794
|
-
bg: '\x1b[48;2;48;40;60m'
|
|
4711
|
+
bg: isMcp ? '\x1b[48;2;60;36;68m' : '\x1b[48;2;48;40;60m'
|
|
3795
4712
|
});
|
|
3796
4713
|
|
|
3797
4714
|
return 'HANDLED';
|
|
@@ -3809,7 +4726,7 @@ export const handlers = {
|
|
|
3809
4726
|
}
|
|
3810
4727
|
|
|
3811
4728
|
if (action === 'apply') {
|
|
3812
|
-
const key = extra.trim();
|
|
4729
|
+
const key = (extra || '').trim();
|
|
3813
4730
|
if (!key) {
|
|
3814
4731
|
console.log(`\n ${C.amber}Usage: /key apply <kiku_vip_sk_...>${C.reset}\n`);
|
|
3815
4732
|
return 'HANDLED';
|
|
@@ -3843,6 +4760,68 @@ export const handlers = {
|
|
|
3843
4760
|
return 'HANDLED';
|
|
3844
4761
|
},
|
|
3845
4762
|
|
|
4763
|
+
'/mcp-key': async (ctx) => {
|
|
4764
|
+
const { sub, extra } = ctx;
|
|
4765
|
+
const action = (sub || 'show').toLowerCase();
|
|
4766
|
+
const mem = ctx.agent?.memory;
|
|
4767
|
+
|
|
4768
|
+
if (action === 'clear') {
|
|
4769
|
+
mem?.setPreference('mcpKey', null);
|
|
4770
|
+
console.log(`\n ${C.emerald}[OK] MCP API Key cleared from preferences.${C.reset}\n`);
|
|
4771
|
+
return 'HANDLED';
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
if (action === 'apply') {
|
|
4775
|
+
const key = (extra || '').trim();
|
|
4776
|
+
if (!key) {
|
|
4777
|
+
console.log(`\n ${C.amber}Usage: /mcp-key apply <kiku_mcp_sk_...>${C.reset}\n`);
|
|
4778
|
+
return 'HANDLED';
|
|
4779
|
+
}
|
|
4780
|
+
mem?.setPreference('mcpKey', key);
|
|
4781
|
+
printSolidPanel({
|
|
4782
|
+
title: 'MCP API Key Applied',
|
|
4783
|
+
lines: [
|
|
4784
|
+
`${C.emerald}[OK] MCP Key saved and ready to use!${C.reset}`,
|
|
4785
|
+
`${C.gold}Key:${C.reset} ${C.white}${key}${C.reset}`,
|
|
4786
|
+
],
|
|
4787
|
+
});
|
|
4788
|
+
return 'HANDLED';
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
const storedKey = mem?.preferences?.mcpKey;
|
|
4792
|
+
if (storedKey) {
|
|
4793
|
+
printSolidPanel({
|
|
4794
|
+
title: 'Ju Jingyi MCP API Key',
|
|
4795
|
+
lines: [
|
|
4796
|
+
`${C.gold}Key:${C.reset} ${C.bold}${C.white}${storedKey}${C.reset}`,
|
|
4797
|
+
`${C.gold}Tier:${C.reset} ${C.emerald}MCP VIP (1,000 req/min)${C.reset}`,
|
|
4798
|
+
`${C.gold}Usage:${C.reset} ${C.dim}X-API-Key: ${storedKey} or Authorization: Bearer ${storedKey}${C.reset}`,
|
|
4799
|
+
`${C.gold}Config:${C.reset} ${C.dim}Run /mcp-key config to view Claude Desktop & Cursor JSON${C.reset}`,
|
|
4800
|
+
'',
|
|
4801
|
+
`${C.dim}Clear: /mcp-key clear | Apply new: /mcp-key apply <key>${C.reset}`,
|
|
4802
|
+
],
|
|
4803
|
+
});
|
|
4804
|
+
if (action === 'config') {
|
|
4805
|
+
const baseUrl = ctx.baseUrl || process.env.KIKU_API_URL || 'http://localhost:3000';
|
|
4806
|
+
const configJson = JSON.stringify({
|
|
4807
|
+
mcpServers: {
|
|
4808
|
+
"kiku-remote": {
|
|
4809
|
+
url: `${baseUrl}/mcp`,
|
|
4810
|
+
headers: {
|
|
4811
|
+
"X-API-Key": storedKey
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
}, null, 2);
|
|
4816
|
+
console.log(`\n${C.bold}${C.cyberCyan}Claude Desktop & Cursor Config:${C.reset}`);
|
|
4817
|
+
console.log(configJson + '\n');
|
|
4818
|
+
}
|
|
4819
|
+
} else {
|
|
4820
|
+
console.log(`\n ${C.amber}No MCP API Key stored. Run ${C.cyberCyan}/quiz mcp${C.amber} to complete the Ju Jingyi challenge and claim one.${C.reset}\n`);
|
|
4821
|
+
}
|
|
4822
|
+
return 'HANDLED';
|
|
4823
|
+
},
|
|
4824
|
+
|
|
3846
4825
|
'/env': async (ctx) => {
|
|
3847
4826
|
const sub = String(ctx.sub || '').toLowerCase().trim();
|
|
3848
4827
|
|
|
@@ -3957,7 +4936,7 @@ export const handlers = {
|
|
|
3957
4936
|
});
|
|
3958
4937
|
return 'HANDLED';
|
|
3959
4938
|
}
|
|
3960
|
-
const answer = await askEnvQuestion(`.env already exists at ${targetPath}. Overwrite? (y/N)`, 'N');
|
|
4939
|
+
const answer = await askEnvQuestion(ctx, `.env already exists at ${targetPath}. Overwrite? (y/N)`, 'N');
|
|
3961
4940
|
if (!/^y(es)?$/i.test(answer)) {
|
|
3962
4941
|
console.log(`\n ${C.dim}Aborted — file left untouched.${C.reset}\n`);
|
|
3963
4942
|
return 'HANDLED';
|
|
@@ -3971,18 +4950,18 @@ export const handlers = {
|
|
|
3971
4950
|
ctx.setIsProcessing?.(false);
|
|
3972
4951
|
console.log(`\n ${C.bold}${C.cyberCyan}Kiku environment setup${C.reset} ${C.dim}— Enter accepts the default; everything not asked stays optional.${C.reset}\n`);
|
|
3973
4952
|
values = {};
|
|
3974
|
-
const baseUrl = await askEnvQuestion('LM Studio endpoint URL', 'http://127.0.0.1:1234/v1');
|
|
4953
|
+
const baseUrl = await askEnvQuestion(ctx, 'LM Studio endpoint URL', 'http://127.0.0.1:1234/v1');
|
|
3975
4954
|
values.LMSTUDIO_BASE_URL = baseUrl || 'http://127.0.0.1:1234/v1';
|
|
3976
4955
|
if (detected.loadedModel) {
|
|
3977
4956
|
console.log(` ${C.dim}Detected loaded model: ${detected.loadedModel}${C.reset}`);
|
|
3978
4957
|
}
|
|
3979
|
-
const contextAnswer = await askEnvQuestion('Context window for token telemetry (tokens)', detected.contextLength ? String(detected.contextLength) : '');
|
|
4958
|
+
const contextAnswer = await askEnvQuestion(ctx, 'Context window for token telemetry (tokens)', detected.contextLength ? String(detected.contextLength) : '');
|
|
3980
4959
|
if (contextAnswer && Number(contextAnswer) > 0) values.KIKU_CONTEXT_LIMIT = String(Math.trunc(Number(contextAnswer)));
|
|
3981
|
-
const dirAnswer = await askEnvQuestion('LM Studio models directory', detected.modelsDir || '');
|
|
4960
|
+
const dirAnswer = await askEnvQuestion(ctx, 'LM Studio models directory', detected.modelsDir || '');
|
|
3982
4961
|
if (dirAnswer) values.LMSTUDIO_MODELS_DIR = dirAnswer;
|
|
3983
|
-
const wantCloud = await askEnvQuestion('Configure a cloud provider API key now? (y/N)', 'N');
|
|
4962
|
+
const wantCloud = await askEnvQuestion(ctx, 'Configure a cloud provider API key now? (y/N)', 'N');
|
|
3984
4963
|
if (/^y(es)?$/i.test(wantCloud)) {
|
|
3985
|
-
const provider = (await askEnvQuestion('Provider (openai/gemini/groq/deepseek/openrouter)', 'openai')).toUpperCase();
|
|
4964
|
+
const provider = (await askEnvQuestion(ctx, 'Provider (openai/gemini/groq/deepseek/openrouter)', 'openai')).toUpperCase();
|
|
3986
4965
|
const keyMap = {
|
|
3987
4966
|
OPENAI: 'OPENAI_API_KEY',
|
|
3988
4967
|
GEMINI: 'GEMINI_API_KEY',
|
|
@@ -3992,7 +4971,7 @@ export const handlers = {
|
|
|
3992
4971
|
};
|
|
3993
4972
|
const keyName = keyMap[provider];
|
|
3994
4973
|
if (keyName) {
|
|
3995
|
-
const apiKey = await askEnvQuestion(`${provider} API key`, '');
|
|
4974
|
+
const apiKey = await askEnvQuestion(ctx, `${provider} API key`, '');
|
|
3996
4975
|
if (apiKey) values[keyName] = apiKey;
|
|
3997
4976
|
}
|
|
3998
4977
|
}
|
|
@@ -4321,55 +5300,7 @@ export const handlers = {
|
|
|
4321
5300
|
},
|
|
4322
5301
|
};
|
|
4323
5302
|
|
|
4324
|
-
const ALIASES =
|
|
4325
|
-
'/quit': '/exit', '/q': '/exit',
|
|
4326
|
-
'/web-chat': '/web',
|
|
4327
|
-
'/abort': '/cancel',
|
|
4328
|
-
'/cp': '/copy', '/clip': '/copy', '/clipboard': '/copy',
|
|
4329
|
-
'/?': '/help', '/commands': '/help', '/menu': '/help',
|
|
4330
|
-
'/hist': '/history', '/h': '/history',
|
|
4331
|
-
'/sess': '/session', '/sessions': '/session',
|
|
4332
|
-
'/load-session': '/session', '/load': '/session', '/restore': '/session',
|
|
4333
|
-
'/think': '/thinking', '/th': '/thinking',
|
|
4334
|
-
'/detail': '/details', '/d': '/details',
|
|
4335
|
-
'/language': '/lang',
|
|
4336
|
-
'/themes': '/theme',
|
|
4337
|
-
'/suggest': '/suggestion', '/suggestions': '/suggestion',
|
|
4338
|
-
'/exec': '/tool', '/call': '/tool',
|
|
4339
|
-
'/toollist': '/tools',
|
|
4340
|
-
'/lookup': '/find', '/findall': '/find',
|
|
4341
|
-
'/cmp': '/compare',
|
|
4342
|
-
'/fav': '/favorites', '/favs': '/favorites',
|
|
4343
|
-
'/latest': '/news',
|
|
4344
|
-
'/job': '/jobs',
|
|
4345
|
-
'/random': '/discover',
|
|
4346
|
-
'/metrics': '/stats', '/catalog': '/stats',
|
|
4347
|
-
'/llm': '/model', '/engine': '/model',
|
|
4348
|
-
'/health': '/ping',
|
|
4349
|
-
'/settings': '/config', '/conf': '/config',
|
|
4350
|
-
'/persona': '/prompt', '/sysprompt': '/prompt',
|
|
4351
|
-
'/ver': '/about', '/v': '/about', '/version': '/about',
|
|
4352
|
-
'/sys': '/system', '/os': '/system',
|
|
4353
|
-
'/gpus': '/gpu',
|
|
4354
|
-
'/resource': '/vram',
|
|
4355
|
-
'/benchmark': '/eval', '/evaluation': '/eval',
|
|
4356
|
-
'/center': '/hub',
|
|
4357
|
-
'/app': '/portal', '/webportal': '/portal',
|
|
4358
|
-
'/info': '/about',
|
|
4359
|
-
'/sponsor': '/ads', '/ad': '/ads',
|
|
4360
|
-
'/image': '/gallery', '/photo': '/gallery',
|
|
4361
|
-
'/cls': '/clear', '/reset': '/clear',
|
|
4362
|
-
'/export': '/save', '/markdown': '/save',
|
|
4363
|
-
'/src': '/sources', '/srcs': '/sources',
|
|
4364
|
-
'/r': '/retry',
|
|
4365
|
-
'/kvip': '/key',
|
|
4366
|
-
'/switch': '/provider',
|
|
4367
|
-
'/providers': '/provider',
|
|
4368
|
-
'/profiles': '/profile',
|
|
4369
|
-
'/preset': '/profile',
|
|
4370
|
-
'/presets': '/profile',
|
|
4371
|
-
'/ingestion': '/ingest',
|
|
4372
|
-
};
|
|
5303
|
+
const ALIASES = COMMAND_ALIASES;
|
|
4373
5304
|
|
|
4374
5305
|
export async function routeCommand(rawInput, ctx) {
|
|
4375
5306
|
const trimmed = rawInput.trim();
|