kiku-agent 1.0.9 → 1.0.11
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 +1 -1
- package/package.json +7 -6
- package/server/agent/agentCore.js +116 -66
- package/server/agent/appPaths.js +2 -0
- package/server/agent/cli.js +457 -248
- package/server/agent/commandRegistry.js +370 -41
- package/server/agent/commands.js +217 -45
- package/server/agent/config.js +367 -216
- package/server/agent/doctor.js +1 -1
- package/server/agent/errors.js +205 -0
- package/server/agent/llmClient.js +369 -351
- package/server/agent/queryDetails.js +234 -182
- package/server/agent/session.js +410 -244
- package/server/agent/terminalImage.js +108 -2
- package/server/agent/ui.js +54 -44
- package/src/assets/images/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/src/assets/images/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/src/assets/images/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/src/assets/images/0040jbadgy1igdxrcipx4j61k422wtpp02.jpg +0 -0
- package/src/assets/images/0040jbadgy1igdxrg3cq5j62bw340u0x02.jpg +0 -0
- package/src/assets/images/0040jbadgy1igdy9hpg8aj62c0340hdt02.jpg +0 -0
package/server/agent/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
import { stdin as input } from 'node:process';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
import './youtubeAuth.js';
|
|
8
|
-
import { KikuAgent } from './agentCore.js';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { stdin as input } from 'node:process';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import './youtubeAuth.js';
|
|
8
|
+
import { KikuAgent } from './agentCore.js';
|
|
9
9
|
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
10
10
|
import { toolsSchema, toolHandlers, executeTool } from './tools.js';
|
|
11
11
|
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
@@ -14,12 +14,12 @@ import { routeCommand } from './commands.js';
|
|
|
14
14
|
import { eventBus, Events } from './eventBus.js';
|
|
15
15
|
import { AgentStates } from './agentState.js';
|
|
16
16
|
import { sanitizeQuery } from './sanitizer.js';
|
|
17
|
-
import { isAbortError } from './errors.js';
|
|
17
|
+
import { isAbortError, formatExecutionErrorCard } from './errors.js';
|
|
18
18
|
import { COMMAND_DEFINITIONS, SUB_COMMANDS, findCommandDefinition, getCommandDetails } from './commandRegistry.js';
|
|
19
19
|
import { ModelManager } from './modelManager.js';
|
|
20
|
-
import { PACKAGE_VERSION } from './version.js';
|
|
21
|
-
import { getTheme, getThemeName } from './theme.js';
|
|
22
|
-
import { createTerminalImageGallery } from './terminalImage.js';
|
|
20
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
21
|
+
import { getTheme, getThemeName } from './theme.js';
|
|
22
|
+
import { createTerminalImageGallery, getWelcomeGalleryLines } from './terminalImage.js';
|
|
23
23
|
import {
|
|
24
24
|
classifyToolResult,
|
|
25
25
|
formatDetailDuration,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
printWelcomeHero, getWelcomeHeroLines, randomFanName, setupOutputInterception,
|
|
40
40
|
rawWrite, INPUT_PLACEHOLDER, UI_ICONS,
|
|
41
41
|
spinnerFrames, thinkingFrames, createProgressRunner,
|
|
42
|
-
enterAlternateScreen, exitAlternateScreen, getTuiViewportLayout, getNewOutputScrollOffset, VirtualScreenBuffer,
|
|
42
|
+
enterAlternateScreen, exitAlternateScreen, getTuiViewportLayout, getNewOutputScrollOffset, VirtualScreenBuffer,
|
|
43
43
|
} from './ui.js';
|
|
44
44
|
|
|
45
45
|
let activeAbortController = null;
|
|
@@ -318,7 +318,7 @@ function printQueryDetails(reference = '') {
|
|
|
318
318
|
});
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
function copyQueryDetails(reference = '') {
|
|
321
|
+
function copyQueryDetails(reference = '', session = null) {
|
|
322
322
|
const request = parseDetailsRequest(reference);
|
|
323
323
|
if (request.error) {
|
|
324
324
|
printSolidPanel({
|
|
@@ -326,11 +326,13 @@ function copyQueryDetails(reference = '') {
|
|
|
326
326
|
lines: [
|
|
327
327
|
`${C.amber}${request.error}${C.reset}`,
|
|
328
328
|
'',
|
|
329
|
+
`${C.white}/copy session [markdown|json|text]${C.reset} ${C.dim}— copy full session transcript${C.reset}`,
|
|
329
330
|
`${C.white}/copy [Qn] [answer]${C.reset} ${C.dim}— copy model answer (default)${C.reset}`,
|
|
330
331
|
`${C.white}/copy [Qn] thinking${C.reset} ${C.dim}— copy thinking trace${C.reset}`,
|
|
331
332
|
`${C.white}/copy [Qn] tools${C.reset} ${C.dim}— copy tools JSON${C.reset}`,
|
|
332
333
|
`${C.white}/copy [Qn] tool <n>${C.reset} ${C.dim}— copy tool <n> raw result${C.reset}`,
|
|
333
334
|
`${C.white}/copy [Qn] all${C.reset} ${C.dim}— copy full question & answer markdown${C.reset}`,
|
|
335
|
+
`${C.white}/copy [Qn] json${C.reset} ${C.dim}— copy question diagnostics JSON${C.reset}`,
|
|
334
336
|
],
|
|
335
337
|
});
|
|
336
338
|
return false;
|
|
@@ -340,13 +342,13 @@ function copyQueryDetails(reference = '') {
|
|
|
340
342
|
? lastQueryDetails
|
|
341
343
|
: queryDetails.get(request.queryNumber);
|
|
342
344
|
|
|
343
|
-
if (!details) {
|
|
345
|
+
if (!details && !request.isSession && !session) {
|
|
344
346
|
const target = request.queryNumber == null ? 'the latest query' : `Q${request.queryNumber}`;
|
|
345
347
|
console.log(`\n ${C.amber}[NOT FOUND] No diagnostics recorded for ${target}.${C.reset}\n`);
|
|
346
348
|
return false;
|
|
347
349
|
}
|
|
348
350
|
|
|
349
|
-
const copyable = getCopyableContent(details, request);
|
|
351
|
+
const copyable = getCopyableContent(details, request, session);
|
|
350
352
|
if (!copyable || !copyable.text) {
|
|
351
353
|
console.log(`\n ${C.amber}[EMPTY] Nothing to copy for ${request.view || 'answer'}.${C.reset}\n`);
|
|
352
354
|
return false;
|
|
@@ -461,44 +463,112 @@ export function printHelp(specificCmd = null) {
|
|
|
461
463
|
|
|
462
464
|
const ads = [
|
|
463
465
|
{
|
|
464
|
-
title: 'DashSecurity',
|
|
466
|
+
title: 'DashSecurity',
|
|
467
|
+
subtitle: 'Cybersecurity Knowledge Cloud',
|
|
468
|
+
tagline: 'Enterprise Cyber Threat Intelligence & AI-Powered DevSecOps',
|
|
469
|
+
badge: 'Security Platform',
|
|
465
470
|
url: 'https://dashsecurity.kpltgroup.com/',
|
|
466
|
-
|
|
471
|
+
summary: 'Next-generation application security & vulnerability intelligence platform designed for developers, security engineers, and red teamers.',
|
|
472
|
+
features: [
|
|
473
|
+
'AI-Powered SAST/DAST Static & Dynamic Security Code Analyzer',
|
|
474
|
+
'Automated OWASP Top 10 & API Security Vulnerability Detection',
|
|
475
|
+
'Interactive Certification Prep Labs (OSCP, CEH, CompTIA Security+)',
|
|
476
|
+
'Real-time Threat Intelligence Feeds & CVE Remediation Advisory',
|
|
477
|
+
],
|
|
478
|
+
highlights: [
|
|
479
|
+
'AI-Powered SAST/DAST Static & Dynamic Security Code Analyzer',
|
|
480
|
+
'Automated OWASP Top 10 & API Security Vulnerability Detection',
|
|
481
|
+
'Interactive Certification Prep Labs (OSCP, CEH, CompTIA Security+)',
|
|
482
|
+
'Real-time Threat Intelligence Feeds & CVE Remediation Advisory',
|
|
483
|
+
],
|
|
484
|
+
cta: 'Explore security tools & interactive labs',
|
|
467
485
|
},
|
|
468
486
|
{
|
|
469
|
-
title: 'Ju Jingyi
|
|
487
|
+
title: 'Ju Jingyi • 鞠婧祎 Official Portal',
|
|
488
|
+
subtitle: 'Official Portfolio & SSOT Knowledge Base',
|
|
489
|
+
tagline: 'Definitive Multimedia Knowledge Base & Autonomous Agent Ecosystem',
|
|
490
|
+
badge: 'Official Fan Portal',
|
|
470
491
|
url: 'https://jujingyi-kikuuu.vercel.app/',
|
|
471
|
-
|
|
492
|
+
summary: 'The single source of truth for Ju Jingyi — featuring exhaustive career timelines, 45+ discography releases, fashion milestones, and full REST API integration.',
|
|
493
|
+
features: [
|
|
494
|
+
'Complete Filmography: In Blossom, Beauty of Resilience, 2026 upcoming dramas',
|
|
495
|
+
'Full Discography: 45+ Singles, Studio Albums, OSTs & live stage tracks',
|
|
496
|
+
'Fashion & Awards: Versace Global Ambassador milestones & career honors',
|
|
497
|
+
'Developer API: Production REST API v1, MCP Server & OpenAPI 3.1 Specification',
|
|
498
|
+
],
|
|
499
|
+
highlights: [
|
|
500
|
+
'Complete Filmography: In Blossom, Beauty of Resilience, 2026 upcoming dramas',
|
|
501
|
+
'Full Discography: 45+ Singles, Studio Albums, OSTs & live stage tracks',
|
|
502
|
+
'Fashion & Awards: Versace Global Ambassador milestones & career honors',
|
|
503
|
+
'Developer API: Production REST API v1, MCP Server & OpenAPI 3.1 Specification',
|
|
504
|
+
],
|
|
505
|
+
cta: 'Visit the portal & test the REST API',
|
|
472
506
|
},
|
|
473
507
|
{
|
|
474
|
-
title: 'ThemeHackers
|
|
508
|
+
title: 'ThemeHackers Research Group',
|
|
509
|
+
subtitle: 'Security Research & Tooling',
|
|
510
|
+
tagline: 'Elite Offensive Cyber Research & Open-Source Security Ecosystem',
|
|
511
|
+
badge: 'Security Research',
|
|
475
512
|
url: 'https://github.com/Themehackers/',
|
|
476
|
-
|
|
513
|
+
summary: 'Independent cybersecurity research collective specializing in red teaming, vulnerability disclosure, penetration testing frameworks, and defensive automation.',
|
|
514
|
+
features: [
|
|
515
|
+
'Red Team Operations & Advanced Penetration Testing Toolkits',
|
|
516
|
+
'Zero-Day Vulnerability Research & Coordinated Security Disclosures',
|
|
517
|
+
'Open-Source Security Automation Scripts & Reverse Engineering Tools',
|
|
518
|
+
'10+ Industry Professional Security Certifications & Technical Writeups',
|
|
519
|
+
],
|
|
520
|
+
highlights: [
|
|
521
|
+
'Red Team Operations & Advanced Penetration Testing Toolkits',
|
|
522
|
+
'Zero-Day Vulnerability Research & Coordinated Security Disclosures',
|
|
523
|
+
'Open-Source Security Automation Scripts & Reverse Engineering Tools',
|
|
524
|
+
'10+ Industry Professional Security Certifications & Technical Writeups',
|
|
525
|
+
],
|
|
526
|
+
cta: 'Explore open-source security repos on GitHub',
|
|
477
527
|
},
|
|
478
528
|
];
|
|
479
529
|
|
|
480
530
|
let adIndex = 0;
|
|
481
531
|
function printAd() {
|
|
482
|
-
const
|
|
532
|
+
const currentIdx = adIndex % ads.length;
|
|
533
|
+
const ad = ads[currentIdx];
|
|
483
534
|
adIndex++;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
535
|
+
|
|
536
|
+
const lines = [
|
|
537
|
+
`${C.bold}${C.white}${ad.tagline || ad.subtitle}${C.reset}`,
|
|
538
|
+
'',
|
|
539
|
+
`${C.dim}${ad.summary}${C.reset}`,
|
|
540
|
+
'',
|
|
541
|
+
`${C.gold}${C.bold}Core Highlights & Features:${C.reset}`,
|
|
542
|
+
...(ad.highlights || ad.features).map(f => ` ${C.emerald}◆${C.reset} ${C.white}${f}${C.reset}`),
|
|
543
|
+
'',
|
|
544
|
+
`${C.slate}${ad.cta || 'Visit'}:${C.reset} ${C.cyberCyan}${C.underline}${ad.url}${C.reset}`,
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
printSolidPanel({
|
|
548
|
+
title: `${UI_ICONS.sponsor} SPONSOR SPOTLIGHT [${currentIdx + 1}/${ads.length}] • ${ad.title} (${ad.badge || 'Featured'})`,
|
|
549
|
+
lines,
|
|
550
|
+
headerBg: C.bgPanelHeader,
|
|
551
|
+
bg: C.bgPanel,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function getInitialViewportLines() {
|
|
556
|
+
const heroLines = getWelcomeHeroLines();
|
|
557
|
+
const galleryLines = getWelcomeGalleryLines(2);
|
|
558
|
+
return galleryLines.length > 0 ? [...heroLines, ...galleryLines] : heroLines;
|
|
490
559
|
}
|
|
491
560
|
|
|
492
561
|
class InteractiveTuiPrompt {
|
|
493
562
|
constructor(options = {}) {
|
|
494
|
-
this.defaultPromptText = options.prompt || 'Kiku ❯ ';
|
|
495
|
-
this.promptText = this.defaultPromptText;
|
|
496
|
-
this.inputPlaceholder = INPUT_PLACEHOLDER;
|
|
497
|
-
this.recordInputHistory = true;
|
|
498
|
-
this.cancelValue = null;
|
|
563
|
+
this.defaultPromptText = options.prompt || 'Kiku ❯ ';
|
|
564
|
+
this.promptText = this.defaultPromptText;
|
|
565
|
+
this.inputPlaceholder = INPUT_PLACEHOLDER;
|
|
566
|
+
this.recordInputHistory = true;
|
|
567
|
+
this.cancelValue = null;
|
|
499
568
|
this.agent = options.agent || null;
|
|
500
569
|
this.modelName = options.modelName || 'gemma-4-e4b';
|
|
501
570
|
this.provider = options.provider || 'lmstudio';
|
|
571
|
+
this.isConnected = options.isConnected ?? true;
|
|
502
572
|
this.history = [];
|
|
503
573
|
this.historyIndex = -1;
|
|
504
574
|
this.line = '';
|
|
@@ -518,7 +588,7 @@ class InteractiveTuiPrompt {
|
|
|
518
588
|
this.agentStateTime = 0;
|
|
519
589
|
this._lastCtrlCTime = 0;
|
|
520
590
|
|
|
521
|
-
this.viewportLines =
|
|
591
|
+
this.viewportLines = getInitialViewportLines();
|
|
522
592
|
this.scrollOffset = 0;
|
|
523
593
|
this.screenBuffer = new VirtualScreenBuffer(
|
|
524
594
|
Math.max(20, process.stdout.columns || 80),
|
|
@@ -526,28 +596,28 @@ class InteractiveTuiPrompt {
|
|
|
526
596
|
);
|
|
527
597
|
}
|
|
528
598
|
|
|
529
|
-
addViewportLines(lines) {
|
|
530
|
-
if (lines === null || lines === undefined) return;
|
|
531
|
-
if (!Array.isArray(lines)) lines = [lines];
|
|
532
|
-
const wasLive = this.scrollOffset === 0;
|
|
533
|
-
let addedLineCount = 0;
|
|
534
|
-
for (const l of lines) {
|
|
535
|
-
if (typeof l === 'string') {
|
|
536
|
-
const sublines = l.split('\n');
|
|
537
|
-
for (const sl of sublines) {
|
|
538
|
-
this.viewportLines.push(sl);
|
|
539
|
-
addedLineCount += 1;
|
|
540
|
-
}
|
|
541
|
-
} else if (l != null) {
|
|
542
|
-
this.viewportLines.push(String(l));
|
|
543
|
-
addedLineCount += 1;
|
|
544
|
-
}
|
|
599
|
+
addViewportLines(lines) {
|
|
600
|
+
if (lines === null || lines === undefined) return;
|
|
601
|
+
if (!Array.isArray(lines)) lines = [lines];
|
|
602
|
+
const wasLive = this.scrollOffset === 0;
|
|
603
|
+
let addedLineCount = 0;
|
|
604
|
+
for (const l of lines) {
|
|
605
|
+
if (typeof l === 'string') {
|
|
606
|
+
const sublines = l.split('\n');
|
|
607
|
+
for (const sl of sublines) {
|
|
608
|
+
this.viewportLines.push(sl);
|
|
609
|
+
addedLineCount += 1;
|
|
610
|
+
}
|
|
611
|
+
} else if (l != null) {
|
|
612
|
+
this.viewportLines.push(String(l));
|
|
613
|
+
addedLineCount += 1;
|
|
614
|
+
}
|
|
545
615
|
}
|
|
546
616
|
if (this.viewportLines.length > 5000) {
|
|
547
617
|
const removedCount = this.viewportLines.length - 5000;
|
|
548
618
|
this.viewportLines.splice(0, removedCount);
|
|
549
619
|
}
|
|
550
|
-
this.scrollOffset = getNewOutputScrollOffset(addedLineCount, process.stdout.rows || 24, wasLive ? 0 : this.scrollOffset);
|
|
620
|
+
this.scrollOffset = getNewOutputScrollOffset(addedLineCount, process.stdout.rows || 24, wasLive ? 0 : this.scrollOffset);
|
|
551
621
|
this.render();
|
|
552
622
|
}
|
|
553
623
|
|
|
@@ -561,7 +631,7 @@ class InteractiveTuiPrompt {
|
|
|
561
631
|
}
|
|
562
632
|
|
|
563
633
|
clearViewport() {
|
|
564
|
-
this.viewportLines =
|
|
634
|
+
this.viewportLines = getInitialViewportLines();
|
|
565
635
|
this.scrollOffset = 0;
|
|
566
636
|
this.render();
|
|
567
637
|
}
|
|
@@ -584,15 +654,15 @@ class InteractiveTuiPrompt {
|
|
|
584
654
|
try { if (input.isTTY) input.setRawMode(false); } catch { }
|
|
585
655
|
}
|
|
586
656
|
|
|
587
|
-
async readLine(options = {}) {
|
|
588
|
-
return new Promise((resolve, reject) => {
|
|
589
|
-
this._cleanupInput();
|
|
590
|
-
const inputOptions = typeof options === 'string' ? { prompt: options } : options;
|
|
591
|
-
this.promptText = inputOptions.prompt || this.defaultPromptText;
|
|
592
|
-
this.inputPlaceholder = inputOptions.placeholder || INPUT_PLACEHOLDER;
|
|
593
|
-
this.recordInputHistory = inputOptions.history !== false;
|
|
594
|
-
this.cancelValue = inputOptions.cancelValue ?? null;
|
|
595
|
-
this.line = '';
|
|
657
|
+
async readLine(options = {}) {
|
|
658
|
+
return new Promise((resolve, reject) => {
|
|
659
|
+
this._cleanupInput();
|
|
660
|
+
const inputOptions = typeof options === 'string' ? { prompt: options } : options;
|
|
661
|
+
this.promptText = inputOptions.prompt || this.defaultPromptText;
|
|
662
|
+
this.inputPlaceholder = inputOptions.placeholder || INPUT_PLACEHOLDER;
|
|
663
|
+
this.recordInputHistory = inputOptions.history !== false;
|
|
664
|
+
this.cancelValue = inputOptions.cancelValue ?? null;
|
|
665
|
+
this.line = '';
|
|
596
666
|
this.cursor = 0;
|
|
597
667
|
this.historyIndex = -1;
|
|
598
668
|
this.popoverIndex = 0;
|
|
@@ -626,7 +696,7 @@ class InteractiveTuiPrompt {
|
|
|
626
696
|
this.cursor = 0;
|
|
627
697
|
this.popoverActive = false;
|
|
628
698
|
this.popoverItems = [];
|
|
629
|
-
if (this.recordInputHistory && result && result.trim()) this.history.push(result);
|
|
699
|
+
if (this.recordInputHistory && result && result.trim()) this.history.push(result);
|
|
630
700
|
const resolve = this.activeResolve;
|
|
631
701
|
this.activeResolve = null;
|
|
632
702
|
this.activeReject = null;
|
|
@@ -670,7 +740,11 @@ class InteractiveTuiPrompt {
|
|
|
670
740
|
this.popoverActive = false;
|
|
671
741
|
this.popoverItems = [];
|
|
672
742
|
this.popoverIndex = 0;
|
|
673
|
-
this.
|
|
743
|
+
if (!this.isConnected && this.line.length > 0) {
|
|
744
|
+
this.ghostText = ' (LM Studio offline - type /model reconnect)';
|
|
745
|
+
} else {
|
|
746
|
+
this.ghostText = '';
|
|
747
|
+
}
|
|
674
748
|
}
|
|
675
749
|
}
|
|
676
750
|
|
|
@@ -691,7 +765,9 @@ class InteractiveTuiPrompt {
|
|
|
691
765
|
|
|
692
766
|
|
|
693
767
|
const shortModel = (this.modelName || 'gemma-4-e4b').replace(/^.*\//, '');
|
|
694
|
-
const modelLabel =
|
|
768
|
+
const modelLabel = this.isConnected
|
|
769
|
+
? `${C.gold}✦ ${shortModel}${C.reset}`
|
|
770
|
+
: `${C.roseGlow}✦ OFFLINE${C.reset}`;
|
|
695
771
|
const mcpCount = this.agent?.mcpManager?.servers?.size || 0;
|
|
696
772
|
const mcpLabel = mcpCount > 0 ? `${C.emerald}● ${mcpCount} MCP${C.reset}` : `${C.slate}○ 0 MCP${C.reset}`;
|
|
697
773
|
|
|
@@ -732,7 +808,9 @@ class InteractiveTuiPrompt {
|
|
|
732
808
|
const pad = Math.max(1, safeCols - getStringDisplayWidth(statusLeft) - getStringDisplayWidth(statusRight));
|
|
733
809
|
statusStyled = `${statusLeft}${' '.repeat(pad)}${statusRight}`;
|
|
734
810
|
} else {
|
|
735
|
-
|
|
811
|
+
const hkRight = `${C.slate}Esc${C.reset}${C.dim} Cancel${C.reset} ${C.slate}F3${C.reset}${C.dim} Think${C.reset} ${C.slate}F5${C.reset}${C.dim} Copy${C.reset} `;
|
|
812
|
+
const pad = Math.max(1, safeCols - getStringDisplayWidth(statusLeft) - getStringDisplayWidth(hkRight));
|
|
813
|
+
statusStyled = `${statusLeft}${' '.repeat(pad)}${hkRight}`;
|
|
736
814
|
}
|
|
737
815
|
} else if (currentOffset > 0) {
|
|
738
816
|
const posInfo = startIndex > 0 ? ` ${C.slate}• ↑ ${startIndex} above${C.reset}` : '';
|
|
@@ -742,8 +820,11 @@ class InteractiveTuiPrompt {
|
|
|
742
820
|
statusStyled = `${statusLeft}${' '.repeat(pad)}${statusRight}`;
|
|
743
821
|
} else if (musicStatus) {
|
|
744
822
|
statusStyled = ` ${C.cyberCyan}🎵 ${musicStatus}${C.reset}`;
|
|
823
|
+
} else if (!this.isConnected) {
|
|
824
|
+
statusStyled = ` ${C.roseGlow}○ OFFLINE${C.reset} ${C.slate}• Start LM Studio & type ${C.cyberCyan}/model reconnect${C.reset}`;
|
|
745
825
|
} else {
|
|
746
|
-
|
|
826
|
+
const hkHints = `${C.slate}F1${C.reset}${C.dim} Help${C.reset} ${C.slate}F3${C.reset}${C.dim} Think${C.reset} ${C.slate}F4${C.reset}${C.dim} Tools${C.reset} ${C.slate}F5${C.reset}${C.dim} Copy${C.reset} ${C.slate}F7${C.reset}${C.dim} Theme${C.reset}`;
|
|
827
|
+
statusStyled = ` ${C.emerald}● READY${C.reset} ${C.slate}│${C.reset} ${hkHints}`;
|
|
747
828
|
}
|
|
748
829
|
|
|
749
830
|
this.screenBuffer.setRow(statusBarRow, statusStyled, C.bgPanelHeader);
|
|
@@ -795,7 +876,10 @@ class InteractiveTuiPrompt {
|
|
|
795
876
|
trailingStyled = `${C.dim}${C.slate}${ghostDisplay}${C.reset}`;
|
|
796
877
|
}
|
|
797
878
|
} else if (!this.line) {
|
|
798
|
-
const
|
|
879
|
+
const defaultPlaceholder = this.isConnected
|
|
880
|
+
? this.inputPlaceholder
|
|
881
|
+
: '[AI Offline] Start LM Studio & type /model reconnect (or /help)';
|
|
882
|
+
const placeholder = sliceLineForDisplay(defaultPlaceholder, 0, maxLineWidth).displayLine;
|
|
799
883
|
trailingStyled = `${C.dim}${C.slate}${placeholder}${C.reset}`;
|
|
800
884
|
}
|
|
801
885
|
|
|
@@ -812,12 +896,12 @@ class InteractiveTuiPrompt {
|
|
|
812
896
|
handleKeypress(str, key) {
|
|
813
897
|
if (!key && !str) return;
|
|
814
898
|
|
|
815
|
-
if ((key?.ctrl && key?.name === 'c') || str === '\x03') {
|
|
816
|
-
if (this.cancelValue !== null) {
|
|
817
|
-
this.finish(this.cancelValue);
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
if (isProcessing) {
|
|
899
|
+
if ((key?.ctrl && key?.name === 'c') || str === '\x03') {
|
|
900
|
+
if (this.cancelValue !== null) {
|
|
901
|
+
this.finish(this.cancelValue);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (isProcessing) {
|
|
821
905
|
if (this.onCancel) this.onCancel();
|
|
822
906
|
else if (this.onSigInt) this.onSigInt();
|
|
823
907
|
this.addViewportLines(` ${C.amber}[✓ Cancelled by user]${C.reset}`);
|
|
@@ -842,12 +926,12 @@ class InteractiveTuiPrompt {
|
|
|
842
926
|
return;
|
|
843
927
|
}
|
|
844
928
|
|
|
845
|
-
if (key?.name === 'escape' || str === '\x1b') {
|
|
846
|
-
if (this.cancelValue !== null) {
|
|
847
|
-
this.finish(this.cancelValue);
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
if (this.popoverActive) {
|
|
929
|
+
if (key?.name === 'escape' || str === '\x1b') {
|
|
930
|
+
if (this.cancelValue !== null) {
|
|
931
|
+
this.finish(this.cancelValue);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (this.popoverActive) {
|
|
851
935
|
this.popoverActive = false;
|
|
852
936
|
this.popoverItems = [];
|
|
853
937
|
this.ghostText = '';
|
|
@@ -869,38 +953,24 @@ class InteractiveTuiPrompt {
|
|
|
869
953
|
return;
|
|
870
954
|
}
|
|
871
955
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
) {
|
|
884
|
-
if (str.includes('[<64;') || str.includes('[64;') || str.includes('<64;') || (key?.name === 'up' && key?.shift)) {
|
|
885
|
-
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
886
|
-
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
887
|
-
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + 3);
|
|
956
|
+
|
|
957
|
+
if (str && (str.includes('[<64;') || str.includes('[64;') || str.includes('<64;') || (key?.name === 'up' && key?.shift))) {
|
|
958
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
959
|
+
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
960
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + 3);
|
|
961
|
+
this.render();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (str && (str.includes('[<65;') || str.includes('[65;') || str.includes('<65;') || (key?.name === 'down' && key?.shift))) {
|
|
965
|
+
if (this.scrollOffset > 0) {
|
|
966
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 3);
|
|
888
967
|
this.render();
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
if (str.includes('[<65;') || str.includes('[65;') || str.includes('<65;') || (key?.name === 'down' && key?.shift)) {
|
|
892
|
-
if (this.scrollOffset > 0) {
|
|
893
|
-
this.scrollOffset = Math.max(0, this.scrollOffset - 3);
|
|
894
|
-
this.render();
|
|
895
|
-
}
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
if (str !== '\r' && str !== '\n' && str !== '\t' && str !== '\x08' && str !== '\x7f') {
|
|
899
|
-
return;
|
|
900
968
|
}
|
|
969
|
+
return;
|
|
901
970
|
}
|
|
902
971
|
|
|
903
|
-
|
|
972
|
+
|
|
973
|
+
if (key?.name === 'pageup' || str === '\x1b[5~') {
|
|
904
974
|
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
905
975
|
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
906
976
|
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + Math.max(1, viewportHeight - 2));
|
|
@@ -908,22 +978,48 @@ class InteractiveTuiPrompt {
|
|
|
908
978
|
return;
|
|
909
979
|
}
|
|
910
980
|
|
|
911
|
-
if (key?.name === 'pagedown') {
|
|
981
|
+
if (key?.name === 'pagedown' || str === '\x1b[6~') {
|
|
912
982
|
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
913
983
|
this.scrollOffset = Math.max(0, this.scrollOffset - Math.max(1, viewportHeight - 2));
|
|
914
984
|
this.render();
|
|
915
985
|
return;
|
|
916
986
|
}
|
|
917
987
|
|
|
918
|
-
if (key?.ctrl && key?.name === 'l') {
|
|
919
|
-
this.render();
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
988
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
989
|
+
const isF1 = key?.name === 'f1' || str === '\x1bOP' || str === '\x1b[[A' || str === '\x1b[11~';
|
|
990
|
+
const isF2 = key?.name === 'f2' || str === '\x1bOQ' || str === '\x1b[[B' || str === '\x1b[12~';
|
|
991
|
+
const isF3 = key?.name === 'f3' || str === '\x1bOR' || str === '\x1b[[C' || str === '\x1b[13~';
|
|
992
|
+
const isF4 = key?.name === 'f4' || str === '\x1bOS' || str === '\x1b[[D' || str === '\x1b[14~';
|
|
993
|
+
const isF5 = key?.name === 'f5' || str === '\x1b[15~' || str === '\x1b[[E';
|
|
994
|
+
const isF6 = key?.name === 'f6' || str === '\x1b[17~';
|
|
995
|
+
const isF7 = key?.name === 'f7' || str === '\x1b[18~';
|
|
996
|
+
const isF8 = key?.name === 'f8' || str === '\x1b[19~';
|
|
997
|
+
const isF9 = key?.name === 'f9' || str === '\x1b[20~';
|
|
998
|
+
const isF10 = key?.name === 'f10' || str === '\x1b[21~';
|
|
999
|
+
|
|
1000
|
+
if (isF1) { this.finish('/help'); return; }
|
|
1001
|
+
if (isF2) { this.finish('/details'); return; }
|
|
1002
|
+
if (isF3) { this.finish('/details thinking'); return; }
|
|
1003
|
+
if (isF4) { this.finish('/details tools'); return; }
|
|
1004
|
+
if (isF5) { this.finish('/copy'); return; }
|
|
1005
|
+
if (isF6) { this.finish('/gallery'); return; }
|
|
1006
|
+
if (isF7) { this.finish('/theme next'); return; }
|
|
1007
|
+
if (isF8) { this.finish('/stats'); return; }
|
|
1008
|
+
if (isF9) { this.finish('/provider'); return; }
|
|
1009
|
+
if (isF10) { this.finish('/mcp list'); return; }
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
const isCtrlL = (key?.ctrl && key?.name === 'l') || str === '\x0c';
|
|
1013
|
+
const isCtrlT = (key?.ctrl && key?.name === 't') || str === '\x14';
|
|
1014
|
+
const isCtrlR = (key?.ctrl && key?.name === 'r') || str === '\x12';
|
|
1015
|
+
const isCtrlS = (key?.ctrl && key?.name === 's') || str === '\x13';
|
|
1016
|
+
const isCtrlG = (key?.ctrl && key?.name === 'g') || str === '\x07';
|
|
1017
|
+
|
|
1018
|
+
if (isCtrlL) { this.render(); return; }
|
|
1019
|
+
if (isCtrlT) { this.finish('/thinking toggle'); return; }
|
|
1020
|
+
if (isCtrlR) { this.finish('/retry'); return; }
|
|
1021
|
+
if (isCtrlS) { this.finish('/save'); return; }
|
|
1022
|
+
if (isCtrlG) { this.finish('/gallery'); return; }
|
|
927
1023
|
|
|
928
1024
|
if (key?.name === 'tab' || str === '\t') {
|
|
929
1025
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
@@ -966,14 +1062,15 @@ class InteractiveTuiPrompt {
|
|
|
966
1062
|
return;
|
|
967
1063
|
}
|
|
968
1064
|
|
|
969
|
-
|
|
1065
|
+
const isUp = key?.name === 'up' || (key?.ctrl && key?.name === 'p') || str === '\x10';
|
|
1066
|
+
if (isUp) {
|
|
970
1067
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
971
1068
|
this.popoverIndex = (this.popoverIndex - 1 + this.popoverItems.length) % this.popoverItems.length;
|
|
972
1069
|
this.updateState();
|
|
973
1070
|
this.render();
|
|
974
1071
|
return;
|
|
975
1072
|
}
|
|
976
|
-
if (key
|
|
1073
|
+
if (key?.ctrl && this.history.length > 0) {
|
|
977
1074
|
if (this.historyIndex === -1) this.historyIndex = this.history.length - 1;
|
|
978
1075
|
else if (this.historyIndex > 0) this.historyIndex--;
|
|
979
1076
|
this.line = this.history[this.historyIndex] || '';
|
|
@@ -989,7 +1086,8 @@ class InteractiveTuiPrompt {
|
|
|
989
1086
|
return;
|
|
990
1087
|
}
|
|
991
1088
|
|
|
992
|
-
|
|
1089
|
+
const isDown = key?.name === 'down' || (key?.ctrl && key?.name === 'n') || str === '\x0e';
|
|
1090
|
+
if (isDown) {
|
|
993
1091
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
994
1092
|
this.popoverIndex = (this.popoverIndex + 1) % this.popoverItems.length;
|
|
995
1093
|
this.updateState();
|
|
@@ -1016,7 +1114,8 @@ class InteractiveTuiPrompt {
|
|
|
1016
1114
|
return;
|
|
1017
1115
|
}
|
|
1018
1116
|
|
|
1019
|
-
|
|
1117
|
+
const isHome = key?.name === 'home' || (key?.ctrl && key?.name === 'a') || str === '\x01' || str === '\x1b[H' || str === '\x1b[1~';
|
|
1118
|
+
if (isHome) {
|
|
1020
1119
|
if (this.line === '') {
|
|
1021
1120
|
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
1022
1121
|
this.scrollOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
@@ -1028,7 +1127,8 @@ class InteractiveTuiPrompt {
|
|
|
1028
1127
|
return;
|
|
1029
1128
|
}
|
|
1030
1129
|
|
|
1031
|
-
|
|
1130
|
+
const isEnd = key?.name === 'end' || (key?.ctrl && key?.name === 'e') || str === '\x05' || str === '\x1b[F' || str === '\x1b[4~';
|
|
1131
|
+
if (isEnd) {
|
|
1032
1132
|
if (this.line === '') {
|
|
1033
1133
|
this.scrollOffset = 0;
|
|
1034
1134
|
this.render();
|
|
@@ -1043,7 +1143,9 @@ class InteractiveTuiPrompt {
|
|
|
1043
1143
|
if (key?.name === 'left') { if (this.cursor > 0) { this.cursor = previousGraphemeBoundary(this.line, this.cursor); this.render(); } return; }
|
|
1044
1144
|
|
|
1045
1145
|
if (key?.name === 'backspace' || str === '\x08' || str === '\x7f') { if (this.cursor > 0) { const previous = previousGraphemeBoundary(this.line, this.cursor); this.line = this.line.slice(0, previous) + this.line.slice(this.cursor); this.cursor = previous; this.updateState(); this.render(); } return; }
|
|
1046
|
-
|
|
1146
|
+
|
|
1147
|
+
const isDelete = key?.name === 'delete' || str === '\x1b[3~';
|
|
1148
|
+
if (isDelete) { if (this.cursor < this.line.length) { const next = nextGraphemeBoundary(this.line, this.cursor); this.line = this.line.slice(0, this.cursor) + this.line.slice(next); this.updateState(); this.render(); } return; }
|
|
1047
1149
|
|
|
1048
1150
|
if (str && !key?.ctrl && !key?.meta) {
|
|
1049
1151
|
if (
|
|
@@ -1113,16 +1215,21 @@ function getSuggestionsForLine(line, cursor = (line || '').length, agent = null)
|
|
|
1113
1215
|
if (parts.length > 1 || (parts.length === 1 && line.includes(' '))) {
|
|
1114
1216
|
const subs = SUB_COMMANDS[baseCmd] || [];
|
|
1115
1217
|
const details = getCommandDetails(baseCmd);
|
|
1218
|
+
const def = findCommandDefinition(baseCmd);
|
|
1116
1219
|
const query = parts.slice(1).join(' ').toLowerCase();
|
|
1117
1220
|
return subs
|
|
1118
1221
|
.filter(s => s.toLowerCase().includes(query))
|
|
1119
1222
|
.map(s => {
|
|
1120
|
-
const
|
|
1223
|
+
const sNorm = s.trim().toLowerCase();
|
|
1224
|
+
const subWords = sNorm.split(/\s+/);
|
|
1121
1225
|
const matchedDetail = details?.subcommands?.find(sc => {
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1226
|
+
const scNorm = (sc.cmd || '').trim().toLowerCase();
|
|
1227
|
+
const scWords = scNorm.split(/\s+/);
|
|
1228
|
+
if (scNorm === sNorm) return true;
|
|
1229
|
+
if (scWords.length >= 2 && subWords.length >= 2 && scWords[0] === subWords[0] && scWords[1] === subWords[1]) return true;
|
|
1230
|
+
return scWords[0] === subWords[0];
|
|
1124
1231
|
});
|
|
1125
|
-
const desc = matchedDetail?.desc || '';
|
|
1232
|
+
const desc = matchedDetail?.desc || details?.summary || def?.desc || '';
|
|
1126
1233
|
return { name: `${baseCmd} ${s}`, desc, insert: `${baseCmd} ${s} ` };
|
|
1127
1234
|
});
|
|
1128
1235
|
}
|
|
@@ -1509,13 +1616,8 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1509
1616
|
}
|
|
1510
1617
|
},
|
|
1511
1618
|
|
|
1512
|
-
onError: (
|
|
1619
|
+
onError: (_err) => {
|
|
1513
1620
|
stopSpinner();
|
|
1514
|
-
printRichCard({
|
|
1515
|
-
title: 'Execution Error',
|
|
1516
|
-
status: 'error',
|
|
1517
|
-
lines: [err.message],
|
|
1518
|
-
});
|
|
1519
1621
|
},
|
|
1520
1622
|
});
|
|
1521
1623
|
|
|
@@ -1526,11 +1628,7 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1526
1628
|
stopSpinner();
|
|
1527
1629
|
persistQueryDetails(isAbortError(err) ? 'aborted' : 'error', err);
|
|
1528
1630
|
if (!isAbortError(err)) {
|
|
1529
|
-
printRichCard({
|
|
1530
|
-
title: 'Query Failed',
|
|
1531
|
-
status: 'error',
|
|
1532
|
-
lines: [err.message],
|
|
1533
|
-
});
|
|
1631
|
+
printRichCard(formatExecutionErrorCard(err, { client: agent.client, provider: agent.client?.provider, model: agent.client?.model }));
|
|
1534
1632
|
} else {
|
|
1535
1633
|
printRichCard({
|
|
1536
1634
|
title: 'Query Cancelled',
|
|
@@ -1560,23 +1658,23 @@ async function main() {
|
|
|
1560
1658
|
|
|
1561
1659
|
if (opts.showStats) { printWelcomeHero(); const r = await executeTool('get_system_stats', {}); console.log(JSON.stringify(r, null, 2)); process.exit(0); }
|
|
1562
1660
|
|
|
1563
|
-
if (opts.query) {
|
|
1564
|
-
printWelcomeHero();
|
|
1565
|
-
const client = new LLMClient({
|
|
1661
|
+
if (opts.query) {
|
|
1662
|
+
printWelcomeHero();
|
|
1663
|
+
const client = new LLMClient({
|
|
1566
1664
|
provider: opts.provider || config.activeProvider,
|
|
1567
1665
|
baseUrl: opts.url,
|
|
1568
1666
|
apiKey: opts.apiKey,
|
|
1569
|
-
model: opts.model
|
|
1570
|
-
});
|
|
1571
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1572
|
-
const modelResolution = await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1573
|
-
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
1574
|
-
throw new Error(modelResolution.error || `No available chat model found for ${client.providerName}`);
|
|
1575
|
-
}
|
|
1576
|
-
if (modelResolution.usedFallback) {
|
|
1577
|
-
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
1578
|
-
}
|
|
1579
|
-
console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
|
|
1667
|
+
model: opts.model
|
|
1668
|
+
});
|
|
1669
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1670
|
+
const modelResolution = await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1671
|
+
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
1672
|
+
throw new Error(modelResolution.error || `No available chat model found for ${client.providerName}`);
|
|
1673
|
+
}
|
|
1674
|
+
if (modelResolution.usedFallback) {
|
|
1675
|
+
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
1676
|
+
}
|
|
1677
|
+
console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
|
|
1580
1678
|
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1581
1679
|
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
1582
1680
|
process.exit(0);
|
|
@@ -1592,7 +1690,7 @@ async function main() {
|
|
|
1592
1690
|
apiKey: opts.apiKey,
|
|
1593
1691
|
model: opts.model
|
|
1594
1692
|
});
|
|
1595
|
-
|
|
1693
|
+
let baseUrl = client.baseUrl;
|
|
1596
1694
|
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1597
1695
|
|
|
1598
1696
|
function cancelActiveQuery() {
|
|
@@ -1620,11 +1718,12 @@ async function main() {
|
|
|
1620
1718
|
return false;
|
|
1621
1719
|
}
|
|
1622
1720
|
|
|
1623
|
-
const tuiPrompt = new InteractiveTuiPrompt({
|
|
1721
|
+
const tuiPrompt = new InteractiveTuiPrompt({
|
|
1624
1722
|
prompt: 'Kiku ❯ ',
|
|
1625
1723
|
agent,
|
|
1626
1724
|
modelName: opts.model || client.model || 'gemma-4-e4b',
|
|
1627
1725
|
provider: client.providerName,
|
|
1726
|
+
isConnected: false,
|
|
1628
1727
|
onCancel: cancelActiveQuery,
|
|
1629
1728
|
onSigInt: async () => {
|
|
1630
1729
|
if (activeAbortController || isProcessing) {
|
|
@@ -1636,12 +1735,13 @@ async function main() {
|
|
|
1636
1735
|
globalTuiPrompt = null;
|
|
1637
1736
|
}
|
|
1638
1737
|
exitAlternateScreen();
|
|
1639
|
-
|
|
1640
|
-
|
|
1738
|
+
process.stdout.write(`\n ${C.sakura}Goodbye • 再见 (鞠躬 ~ 谢谢!)${C.reset}\n\n`, () => {
|
|
1739
|
+
process.exit(0);
|
|
1740
|
+
});
|
|
1641
1741
|
}
|
|
1642
1742
|
}
|
|
1643
|
-
});
|
|
1644
|
-
const terminalGallery = createTerminalImageGallery();
|
|
1743
|
+
});
|
|
1744
|
+
const terminalGallery = createTerminalImageGallery();
|
|
1645
1745
|
|
|
1646
1746
|
globalTuiPrompt = tuiPrompt;
|
|
1647
1747
|
setupOutputInterception(tuiPrompt, () => isProcessing);
|
|
@@ -1650,57 +1750,84 @@ async function main() {
|
|
|
1650
1750
|
let catalogOnlyMode = false;
|
|
1651
1751
|
|
|
1652
1752
|
if (!health.connected) {
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1753
|
+
if (client.provider !== 'lmstudio') {
|
|
1754
|
+
printRichCard({
|
|
1755
|
+
title: `${client.providerName} Unavailable • Auto-Fallback to LM Studio`,
|
|
1756
|
+
status: 'warning',
|
|
1757
|
+
lines: [
|
|
1758
|
+
`• ${client.providerName} is not ready (${health.error || 'unreachable'}).`,
|
|
1759
|
+
'• Auto-switching to local LM Studio (google/gemma-4-e4b)...',
|
|
1760
|
+
'Tip: To configure keys, use /provider setkey <key> or /provider use <name>',
|
|
1761
|
+
],
|
|
1762
|
+
});
|
|
1661
1763
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1764
|
+
client.switchProvider('lmstudio');
|
|
1765
|
+
if (agent?.client) agent.client.switchProvider('lmstudio');
|
|
1766
|
+
baseUrl = client.baseUrl;
|
|
1767
|
+
tuiPrompt.provider = client.providerName;
|
|
1768
|
+
tuiPrompt.modelName = client.model;
|
|
1769
|
+
tuiPrompt.render();
|
|
1770
|
+
|
|
1771
|
+
health = await client.checkConnection();
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (!health.connected) {
|
|
1775
|
+
printRichCard({
|
|
1776
|
+
title: `${client.providerName} Offline`,
|
|
1777
|
+
status: 'offline',
|
|
1778
|
+
lines: [
|
|
1779
|
+
`• Endpoint: ${baseUrl || 'local'} (${health.error || 'unreachable'})`,
|
|
1780
|
+
...(client.provider === 'lmstudio' ? ['• Action: Attempting auto-start of local LM Studio...'] : []),
|
|
1781
|
+
],
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
if (client.provider === 'lmstudio') {
|
|
1785
|
+
const autoHealth = await attemptAutoStartLMStudio(baseUrl, opts.model || client.model);
|
|
1786
|
+
if (autoHealth) {
|
|
1787
|
+
health = autoHealth;
|
|
1788
|
+
printRichCard({
|
|
1789
|
+
title: 'LM Studio Started',
|
|
1790
|
+
status: 'ok',
|
|
1791
|
+
lines: [
|
|
1792
|
+
'Local LM Studio process started and responding.',
|
|
1793
|
+
],
|
|
1794
|
+
});
|
|
1795
|
+
} else {
|
|
1796
|
+
catalogOnlyMode = true;
|
|
1797
|
+
tuiPrompt.isConnected = false;
|
|
1798
|
+
printRichCard({
|
|
1799
|
+
title: 'LM Studio Offline • AI Chat Locked',
|
|
1800
|
+
status: 'offline',
|
|
1801
|
+
lines: [
|
|
1802
|
+
'LM Studio is not running. Free-form chat typing is locked to prevent errors.',
|
|
1803
|
+
'Open LM Studio, load a model (e.g. google/gemma-4-e4b), then type /model reconnect.',
|
|
1804
|
+
'Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
1805
|
+
],
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1673
1808
|
} else {
|
|
1674
1809
|
catalogOnlyMode = true;
|
|
1810
|
+
tuiPrompt.isConnected = false;
|
|
1675
1811
|
printRichCard({
|
|
1676
|
-
title:
|
|
1677
|
-
status: '
|
|
1812
|
+
title: `${client.providerName} Offline • AI Chat Locked`,
|
|
1813
|
+
status: 'offline',
|
|
1678
1814
|
lines: [
|
|
1679
|
-
|
|
1680
|
-
'
|
|
1815
|
+
`${client.providerName} is unreachable. Chat typing is locked to prevent errors.`,
|
|
1816
|
+
'Verify your provider endpoint/key and use /model reconnect or /provider.',
|
|
1817
|
+
'Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
1681
1818
|
],
|
|
1682
1819
|
});
|
|
1683
1820
|
}
|
|
1684
|
-
} else {
|
|
1685
|
-
catalogOnlyMode = true;
|
|
1686
|
-
printRichCard({
|
|
1687
|
-
title: 'Catalog-Only Mode',
|
|
1688
|
-
status: 'catalog',
|
|
1689
|
-
lines: [
|
|
1690
|
-
'Direct database & MCP tools remain available without AI synthesis.',
|
|
1691
|
-
'Verify your provider endpoint/key and use /model reconnect.',
|
|
1692
|
-
],
|
|
1693
|
-
});
|
|
1694
1821
|
}
|
|
1695
1822
|
}
|
|
1696
1823
|
|
|
1697
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1824
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1698
1825
|
const modelResolution = catalogOnlyMode
|
|
1699
1826
|
? { connected: false, state: 'catalog_only' }
|
|
1700
1827
|
: await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1701
1828
|
if (!catalogOnlyMode && (!modelResolution.connected || !modelResolution.selectedModel)) {
|
|
1702
1829
|
printRichCard({
|
|
1703
|
-
title: 'Model Not Loaded',
|
|
1830
|
+
title: 'Model Not Loaded • AI Chat Locked',
|
|
1704
1831
|
status: 'warning',
|
|
1705
1832
|
lines: [
|
|
1706
1833
|
modelResolution.error || 'No chat-capable model loaded',
|
|
@@ -1708,13 +1835,15 @@ async function main() {
|
|
|
1708
1835
|
],
|
|
1709
1836
|
});
|
|
1710
1837
|
catalogOnlyMode = true;
|
|
1838
|
+
tuiPrompt.isConnected = false;
|
|
1711
1839
|
} else if (!catalogOnlyMode && modelResolution.selectedModel) {
|
|
1840
|
+
tuiPrompt.isConnected = true;
|
|
1712
1841
|
tuiPrompt.modelName = modelResolution.selectedModel;
|
|
1713
1842
|
printRichCard({
|
|
1714
1843
|
title: 'Connected & Ready',
|
|
1715
1844
|
status: 'connected',
|
|
1716
1845
|
lines: [
|
|
1717
|
-
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${modelResolution.selectedModel}`,
|
|
1846
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${modelResolution.selectedModel}`,
|
|
1718
1847
|
`• Status: Chat & Tool DAG Active • Tools: ${toolsSchema.length} Loaded`,
|
|
1719
1848
|
],
|
|
1720
1849
|
});
|
|
@@ -1763,17 +1892,17 @@ async function main() {
|
|
|
1763
1892
|
|
|
1764
1893
|
const mcpStatus = agent.mcpManager ? agent.mcpManager.getStatus() : null;
|
|
1765
1894
|
if (mcpStatus && mcpStatus.activeServersCount > 0 && !opts.mcp) {
|
|
1766
|
-
const serverSummaries = mcpStatus.servers
|
|
1767
|
-
.filter(s => s.status === 'connected')
|
|
1768
|
-
.map(s => {
|
|
1769
|
-
const sources = (s.sources || [])
|
|
1770
|
-
.map(source => String(source).split(/[\\/]/).pop())
|
|
1771
|
-
.filter(Boolean);
|
|
1772
|
-
const sourceLabel = sources.length > 0
|
|
1773
|
-
? ` • source: ${[...new Set(sources)].join(', ')}`
|
|
1774
|
-
: '';
|
|
1775
|
-
return `• ${s.name} (${s.type}) — ${s.toolsCount} tools${sourceLabel}`;
|
|
1776
|
-
});
|
|
1895
|
+
const serverSummaries = mcpStatus.servers
|
|
1896
|
+
.filter(s => s.status === 'connected')
|
|
1897
|
+
.map(s => {
|
|
1898
|
+
const sources = (s.sources || [])
|
|
1899
|
+
.map(source => String(source).split(/[\\/]/).pop())
|
|
1900
|
+
.filter(Boolean);
|
|
1901
|
+
const sourceLabel = sources.length > 0
|
|
1902
|
+
? ` • source: ${[...new Set(sources)].join(', ')}`
|
|
1903
|
+
: '';
|
|
1904
|
+
return `• ${s.name} (${s.type}) — ${s.toolsCount} tools${sourceLabel}`;
|
|
1905
|
+
});
|
|
1777
1906
|
printRichCard({
|
|
1778
1907
|
title: 'Persisted MCP Connected',
|
|
1779
1908
|
status: 'mcp',
|
|
@@ -1826,22 +1955,29 @@ async function main() {
|
|
|
1826
1955
|
baseUrl,
|
|
1827
1956
|
modelManager,
|
|
1828
1957
|
catalogOnlyMode,
|
|
1829
|
-
setCatalogOnlyMode: value => {
|
|
1958
|
+
setCatalogOnlyMode: value => {
|
|
1959
|
+
catalogOnlyMode = Boolean(value);
|
|
1960
|
+
if (tuiPrompt) {
|
|
1961
|
+
tuiPrompt.isConnected = !catalogOnlyMode;
|
|
1962
|
+
tuiPrompt.render();
|
|
1963
|
+
}
|
|
1964
|
+
},
|
|
1830
1965
|
get lastQuery() { return lastQuery; },
|
|
1831
1966
|
set lastQuery(value) { lastQuery = value; },
|
|
1832
1967
|
printHelp,
|
|
1833
1968
|
printQueryDetails,
|
|
1834
|
-
copyQueryDetails,
|
|
1835
|
-
printWelcome: printWelcomeHero,
|
|
1836
|
-
printAd,
|
|
1837
|
-
showTerminalImage: () => {
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1969
|
+
copyQueryDetails: (ref) => copyQueryDetails(ref, agent?.session),
|
|
1970
|
+
printWelcome: printWelcomeHero,
|
|
1971
|
+
printAd,
|
|
1972
|
+
showTerminalImage: (count = 2) => {
|
|
1973
|
+
const num = Number(count) || 2;
|
|
1974
|
+
const imageLines = terminalGallery.maybeRender(queryCount, { force: true, count: num });
|
|
1975
|
+
if (imageLines.length > 0) tuiPrompt.addViewportLines(imageLines);
|
|
1976
|
+
return imageLines.length > 0;
|
|
1977
|
+
},
|
|
1978
|
+
cancelActiveQuery,
|
|
1979
|
+
readInput: options => tuiPrompt.readLine(options),
|
|
1980
|
+
enqueueQuery: async (prompt, isRetry) => {
|
|
1845
1981
|
queryQueue.push({ id: 'q_' + Date.now(), number: nextQueryNumber++, prompt, addedAt: Date.now() });
|
|
1846
1982
|
triggerQueueProcessing();
|
|
1847
1983
|
},
|
|
@@ -1879,46 +2015,49 @@ async function main() {
|
|
|
1879
2015
|
|
|
1880
2016
|
try {
|
|
1881
2017
|
if (catalogOnlyMode) {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2018
|
+
if (nextItem.prompt.startsWith('@')) {
|
|
2019
|
+
const targetTool = nextItem.prompt.slice(1).split(/\s+/)[0];
|
|
2020
|
+
const rawArg = nextItem.prompt.slice(targetTool.length + 1).trim();
|
|
2021
|
+
|
|
2022
|
+
let queryArgs = {};
|
|
2023
|
+
try { queryArgs = JSON.parse(rawArg); } catch { queryArgs = { query: rawArg }; }
|
|
2024
|
+
|
|
2025
|
+
let result;
|
|
2026
|
+
if (agent.mcpManager?.isMcpTool(targetTool)) {
|
|
2027
|
+
result = await agent.mcpManager.callTool(targetTool, queryArgs);
|
|
2028
|
+
} else {
|
|
2029
|
+
result = await executeTool(targetTool, queryArgs);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
const serialized = JSON.stringify(result, null, 2);
|
|
2033
|
+
printSolidPanel({
|
|
2034
|
+
title: `Direct Tool Result • ${targetTool} • Q${nextItem.number}`,
|
|
2035
|
+
lines: serialized.split('\n'),
|
|
2036
|
+
});
|
|
1895
2037
|
} else {
|
|
1896
|
-
|
|
2038
|
+
printRichCard({
|
|
2039
|
+
title: 'Query Blocked • LM Studio Offline',
|
|
2040
|
+
status: 'warning',
|
|
2041
|
+
lines: [
|
|
2042
|
+
'Cannot execute chat query because LM Studio is offline.',
|
|
2043
|
+
'Please start LM Studio and run /model reconnect to enable conversational chat.',
|
|
2044
|
+
],
|
|
2045
|
+
});
|
|
1897
2046
|
}
|
|
1898
|
-
|
|
1899
|
-
const serialized = JSON.stringify(result, null, 2);
|
|
1900
|
-
printSolidPanel({
|
|
1901
|
-
title: `Direct Tool Result • ${targetTool} • Q${nextItem.number}`,
|
|
1902
|
-
lines: serialized.split('\n'),
|
|
1903
|
-
});
|
|
1904
2047
|
} else {
|
|
1905
2048
|
await executeQueryWithUI(agent, nextItem.prompt, agent.session.thinkingMode === 'show', activeAbortController.signal, nextItem.number);
|
|
1906
2049
|
}
|
|
1907
2050
|
} catch (err) {
|
|
1908
2051
|
if (!isAbortError(err)) {
|
|
1909
|
-
printRichCard({
|
|
1910
|
-
title: 'Execution Error',
|
|
1911
|
-
status: 'error',
|
|
1912
|
-
lines: [err.message],
|
|
1913
|
-
});
|
|
2052
|
+
printRichCard(formatExecutionErrorCard(err, { client: agent.client, provider: agent.client?.provider, model: agent.client?.model }));
|
|
1914
2053
|
}
|
|
1915
2054
|
} finally {
|
|
1916
|
-
activeAbortController = null;
|
|
1917
|
-
queryCount++;
|
|
1918
|
-
printAd();
|
|
1919
|
-
const imageLines = terminalGallery.maybeRender(queryCount);
|
|
1920
|
-
if (imageLines.length > 0) tuiPrompt.addViewportLines(imageLines);
|
|
1921
|
-
activeQueryNumber = null;
|
|
2055
|
+
activeAbortController = null;
|
|
2056
|
+
queryCount++;
|
|
2057
|
+
printAd();
|
|
2058
|
+
const imageLines = terminalGallery.maybeRender(queryCount);
|
|
2059
|
+
if (imageLines.length > 0) tuiPrompt.addViewportLines(imageLines);
|
|
2060
|
+
activeQueryNumber = null;
|
|
1922
2061
|
if (globalTuiPrompt) {
|
|
1923
2062
|
globalTuiPrompt.agentState = '';
|
|
1924
2063
|
globalTuiPrompt.render();
|
|
@@ -2009,6 +2148,75 @@ async function main() {
|
|
|
2009
2148
|
continue;
|
|
2010
2149
|
}
|
|
2011
2150
|
|
|
2151
|
+
|
|
2152
|
+
if (catalogOnlyMode || !tuiPrompt.isConnected) {
|
|
2153
|
+
if (trimmed.startsWith('@')) {
|
|
2154
|
+
const targetTool = trimmed.slice(1).split(/\s+/)[0];
|
|
2155
|
+
const rawArg = trimmed.slice(targetTool.length + 1).trim();
|
|
2156
|
+
|
|
2157
|
+
let queryArgs = {};
|
|
2158
|
+
try { queryArgs = JSON.parse(rawArg); } catch { queryArgs = { query: rawArg }; }
|
|
2159
|
+
|
|
2160
|
+
try {
|
|
2161
|
+
let result;
|
|
2162
|
+
if (agent.mcpManager?.isMcpTool(targetTool)) {
|
|
2163
|
+
result = await agent.mcpManager.callTool(targetTool, queryArgs);
|
|
2164
|
+
} else {
|
|
2165
|
+
result = await executeTool(targetTool, queryArgs);
|
|
2166
|
+
}
|
|
2167
|
+
const serialized = JSON.stringify(result, null, 2);
|
|
2168
|
+
printSolidPanel({
|
|
2169
|
+
title: `Direct Tool Result • ${targetTool}`,
|
|
2170
|
+
lines: serialized.split('\n'),
|
|
2171
|
+
});
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
printRichCard({
|
|
2174
|
+
title: 'Tool Error',
|
|
2175
|
+
status: 'error',
|
|
2176
|
+
lines: [err.message],
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
|
|
2183
|
+
const lower = trimmed.toLowerCase();
|
|
2184
|
+
if (lower === 'exit' || lower === 'quit' || lower === 'q') {
|
|
2185
|
+
cancelActiveQuery();
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
if (lower === 'help' || lower === 'h' || lower === '?') {
|
|
2189
|
+
await routeCommand('/help', { ...cmdCtx, sub: '', extra: '', parts: ['/help'] });
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
if (lower === 'reconnect' || lower === 'connect') {
|
|
2193
|
+
await routeCommand('/model', { ...cmdCtx, sub: 'reconnect', extra: '', parts: ['/model', 'reconnect'] });
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (lower === 'doctor') {
|
|
2197
|
+
await routeCommand('/doctor', { ...cmdCtx, sub: '', extra: '', parts: ['/doctor'] });
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
|
|
2202
|
+
printSolidPanel({
|
|
2203
|
+
title: 'AI Chat Locked • LM Studio Offline',
|
|
2204
|
+
lines: [
|
|
2205
|
+
`${C.bold}${C.amber}LM Studio is not running or connected.${C.reset}`,
|
|
2206
|
+
`${C.white}Chat query typing is disabled until the AI server is active.${C.reset}`,
|
|
2207
|
+
'',
|
|
2208
|
+
`${C.bold}${C.white}How to unlock & connect:${C.reset}`,
|
|
2209
|
+
` 1. Open ${C.cyberCyan}LM Studio${C.reset} on your computer`,
|
|
2210
|
+
` 2. Load model ${C.gold}${opts.model || client.model || 'google/gemma-4-e4b'}${C.reset} and ensure the local server is running (port 1234)`,
|
|
2211
|
+
` 3. Type ${C.cyberCyan}/model reconnect${C.reset} in this terminal (or type ${C.cyberCyan}reconnect${C.reset})`,
|
|
2212
|
+
'',
|
|
2213
|
+
`${C.dim}Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit${C.reset}`,
|
|
2214
|
+
],
|
|
2215
|
+
bg: C.bgPanel,
|
|
2216
|
+
});
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2012
2220
|
const { sanitized, warnings } = sanitizeQuery(trimmed);
|
|
2013
2221
|
if (warnings.length > 0) {
|
|
2014
2222
|
printRichCard({
|
|
@@ -2037,8 +2245,9 @@ async function main() {
|
|
|
2037
2245
|
globalTuiPrompt = null;
|
|
2038
2246
|
}
|
|
2039
2247
|
exitAlternateScreen();
|
|
2040
|
-
|
|
2041
|
-
|
|
2248
|
+
process.stdout.write(`\n ${C.sakura}Goodbye • 再见 (鞠躬 ~ 谢谢!)${C.reset}\n\n`, () => {
|
|
2249
|
+
process.exit(0);
|
|
2250
|
+
});
|
|
2042
2251
|
}
|
|
2043
2252
|
|
|
2044
2253
|
process.on('exit', () => {
|