@codemieai/code 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codemie/guides/git-workflow.md +493 -0
- package/CLAUDE.md +130 -761
- package/README.md +283 -695
- package/bin/codemie-claude.js +122 -0
- package/bin/codemie-code.js +90 -15
- package/bin/codemie-codex.js +138 -0
- package/bin/codemie.js +1 -1
- package/config.example.json +10 -0
- package/dist/agents/adapters/claude-code.d.ts +2 -2
- package/dist/agents/adapters/claude-code.d.ts.map +1 -1
- package/dist/agents/adapters/claude-code.js +20 -42
- package/dist/agents/adapters/claude-code.js.map +1 -1
- package/dist/agents/adapters/codemie-code.d.ts +11 -2
- package/dist/agents/adapters/codemie-code.d.ts.map +1 -1
- package/dist/agents/adapters/codemie-code.js +93 -25
- package/dist/agents/adapters/codemie-code.js.map +1 -1
- package/dist/agents/adapters/codex.d.ts +2 -2
- package/dist/agents/adapters/codex.d.ts.map +1 -1
- package/dist/agents/adapters/codex.js +31 -24
- package/dist/agents/adapters/codex.js.map +1 -1
- package/dist/agents/codemie-code/agent.d.ts +89 -0
- package/dist/agents/codemie-code/agent.d.ts.map +1 -0
- package/dist/agents/codemie-code/agent.js +523 -0
- package/dist/agents/codemie-code/agent.js.map +1 -0
- package/dist/agents/codemie-code/config.d.ts +40 -0
- package/dist/agents/codemie-code/config.d.ts.map +1 -0
- package/dist/agents/codemie-code/config.js +276 -0
- package/dist/agents/codemie-code/config.js.map +1 -0
- package/dist/agents/codemie-code/filters.d.ts +91 -0
- package/dist/agents/codemie-code/filters.d.ts.map +1 -0
- package/dist/agents/codemie-code/filters.js +328 -0
- package/dist/agents/codemie-code/filters.js.map +1 -0
- package/dist/agents/codemie-code/index.d.ts +78 -0
- package/dist/agents/codemie-code/index.d.ts.map +1 -0
- package/dist/agents/codemie-code/index.js +259 -0
- package/dist/agents/codemie-code/index.js.map +1 -0
- package/dist/agents/codemie-code/prompts.d.ts +11 -0
- package/dist/agents/codemie-code/prompts.d.ts.map +1 -0
- package/dist/agents/codemie-code/prompts.js +31 -0
- package/dist/agents/codemie-code/prompts.js.map +1 -0
- package/dist/agents/codemie-code/streaming/events.d.ts +7 -0
- package/dist/agents/codemie-code/streaming/events.d.ts.map +1 -0
- package/dist/agents/codemie-code/streaming/events.js +7 -0
- package/dist/agents/codemie-code/streaming/events.js.map +1 -0
- package/dist/agents/codemie-code/streaming/formatter.d.ts +2 -0
- package/dist/agents/codemie-code/streaming/formatter.d.ts.map +1 -0
- package/dist/agents/codemie-code/streaming/formatter.js +2 -0
- package/dist/agents/codemie-code/streaming/formatter.js.map +1 -0
- package/dist/agents/codemie-code/streaming/ui.d.ts +2 -0
- package/dist/agents/codemie-code/streaming/ui.d.ts.map +1 -0
- package/dist/agents/codemie-code/streaming/ui.js +2 -0
- package/dist/agents/codemie-code/streaming/ui.js.map +1 -0
- package/dist/agents/codemie-code/tokenUtils.d.ts +108 -0
- package/dist/agents/codemie-code/tokenUtils.d.ts.map +1 -0
- package/dist/agents/codemie-code/tokenUtils.js +220 -0
- package/dist/agents/codemie-code/tokenUtils.js.map +1 -0
- package/dist/agents/codemie-code/toolMetadata.d.ts +15 -0
- package/dist/agents/codemie-code/toolMetadata.d.ts.map +1 -0
- package/dist/agents/codemie-code/toolMetadata.js +315 -0
- package/dist/agents/codemie-code/toolMetadata.js.map +1 -0
- package/dist/agents/codemie-code/tools/command.d.ts +2 -0
- package/dist/agents/codemie-code/tools/command.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/command.js +2 -0
- package/dist/agents/codemie-code/tools/command.js.map +1 -0
- package/dist/agents/codemie-code/tools/filesystem.d.ts +2 -0
- package/dist/agents/codemie-code/tools/filesystem.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/filesystem.js +2 -0
- package/dist/agents/codemie-code/tools/filesystem.js.map +1 -0
- package/dist/agents/codemie-code/tools/git.d.ts +2 -0
- package/dist/agents/codemie-code/tools/git.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/git.js +2 -0
- package/dist/agents/codemie-code/tools/git.js.map +1 -0
- package/dist/agents/codemie-code/tools/index.d.ts +19 -0
- package/dist/agents/codemie-code/tools/index.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/index.js +239 -0
- package/dist/agents/codemie-code/tools/index.js.map +1 -0
- package/dist/agents/codemie-code/tools/security.d.ts +2 -0
- package/dist/agents/codemie-code/tools/security.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/security.js +2 -0
- package/dist/agents/codemie-code/tools/security.js.map +1 -0
- package/dist/agents/codemie-code/types.d.ts +254 -0
- package/dist/agents/codemie-code/types.d.ts.map +1 -0
- package/dist/agents/codemie-code/types.js +35 -0
- package/dist/agents/codemie-code/types.js.map +1 -0
- package/dist/agents/codemie-code/ui.d.ts +83 -0
- package/dist/agents/codemie-code/ui.d.ts.map +1 -0
- package/dist/agents/codemie-code/ui.js +624 -0
- package/dist/agents/codemie-code/ui.js.map +1 -0
- package/dist/agents/registry.d.ts +1 -1
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +7 -13
- package/dist/agents/registry.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +323 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +113 -69
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/env.d.ts +3 -0
- package/dist/cli/commands/env.d.ts.map +1 -0
- package/dist/cli/commands/env.js +19 -0
- package/dist/cli/commands/env.js.map +1 -0
- package/dist/cli/commands/install.js +27 -33
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/list.js +18 -24
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +307 -21
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +3 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +357 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/uninstall.js +24 -30
- package/dist/cli/commands/uninstall.js.map +1 -1
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/commands/version.js +11 -16
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/index.js +47 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/env/manager.js +9 -46
- package/dist/env/manager.js.map +1 -1
- package/dist/index.d.ts +6 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -43
- package/dist/index.js.map +1 -1
- package/dist/utils/async-tips.d.ts.map +1 -1
- package/dist/utils/async-tips.js +16 -55
- package/dist/utils/async-tips.js.map +1 -1
- package/dist/utils/clipboard.d.ts +16 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js +179 -0
- package/dist/utils/clipboard.js.map +1 -0
- package/dist/utils/config-loader.d.ts +96 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +351 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/dirname.d.ts +7 -0
- package/dist/utils/dirname.d.ts.map +1 -0
- package/dist/utils/dirname.js +11 -0
- package/dist/utils/dirname.js.map +1 -0
- package/dist/utils/errors.js +7 -17
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/exec.js +3 -6
- package/dist/utils/exec.js.map +1 -1
- package/dist/utils/first-time.d.ts +34 -0
- package/dist/utils/first-time.d.ts.map +1 -0
- package/dist/utils/first-time.js +245 -0
- package/dist/utils/first-time.js.map +1 -0
- package/dist/utils/health-checker.d.ts +20 -0
- package/dist/utils/health-checker.d.ts.map +1 -0
- package/dist/utils/health-checker.js +168 -0
- package/dist/utils/health-checker.js.map +1 -0
- package/dist/utils/logger.js +12 -18
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/model-fetcher.d.ts +21 -0
- package/dist/utils/model-fetcher.d.ts.map +1 -0
- package/dist/utils/model-fetcher.js +137 -0
- package/dist/utils/model-fetcher.js.map +1 -0
- package/dist/utils/tips.d.ts.map +1 -1
- package/dist/utils/tips.js +13 -52
- package/dist/utils/tips.js.map +1 -1
- package/package.json +17 -23
- package/scripts/README.md +80 -0
- package/scripts/release.sh +156 -0
- package/dist/agents/adapters/aider.d.ts +0 -12
- package/dist/agents/adapters/aider.d.ts.map +0 -1
- package/dist/agents/adapters/aider.js +0 -80
- package/dist/agents/adapters/aider.js.map +0 -1
- package/dist/cli/cli.d.ts +0 -4
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js +0 -107
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/commands/mcp.d.ts +0 -3
- package/dist/cli/commands/mcp.d.ts.map +0 -1
- package/dist/cli/commands/mcp.js +0 -459
- package/dist/cli/commands/mcp.js.map +0 -1
- package/dist/code/agent-events.d.ts +0 -39
- package/dist/code/agent-events.d.ts.map +0 -1
- package/dist/code/agent-events.js +0 -4
- package/dist/code/agent-events.js.map +0 -1
- package/dist/code/agent.d.ts +0 -19
- package/dist/code/agent.d.ts.map +0 -1
- package/dist/code/agent.js +0 -144
- package/dist/code/agent.js.map +0 -1
- package/dist/code/config.d.ts +0 -13
- package/dist/code/config.d.ts.map +0 -1
- package/dist/code/config.js +0 -41
- package/dist/code/config.js.map +0 -1
- package/dist/code/index.d.ts +0 -19
- package/dist/code/index.d.ts.map +0 -1
- package/dist/code/index.js +0 -400
- package/dist/code/index.js.map +0 -1
- package/dist/code/prompts.d.ts +0 -2
- package/dist/code/prompts.d.ts.map +0 -1
- package/dist/code/prompts.js +0 -45
- package/dist/code/prompts.js.map +0 -1
- package/dist/code/tools/command.d.ts +0 -8
- package/dist/code/tools/command.d.ts.map +0 -1
- package/dist/code/tools/command.js +0 -83
- package/dist/code/tools/command.js.map +0 -1
- package/dist/code/tools/diff-utils.d.ts +0 -2
- package/dist/code/tools/diff-utils.d.ts.map +0 -1
- package/dist/code/tools/diff-utils.js +0 -45
- package/dist/code/tools/diff-utils.js.map +0 -1
- package/dist/code/tools/filesystem.d.ts +0 -11
- package/dist/code/tools/filesystem.d.ts.map +0 -1
- package/dist/code/tools/filesystem.js +0 -442
- package/dist/code/tools/filesystem.js.map +0 -1
- package/dist/code/tools/git.d.ts +0 -7
- package/dist/code/tools/git.d.ts.map +0 -1
- package/dist/code/tools/git.js +0 -111
- package/dist/code/tools/git.js.map +0 -1
- package/dist/code/tools/mcp.d.ts +0 -13
- package/dist/code/tools/mcp.d.ts.map +0 -1
- package/dist/code/tools/mcp.js +0 -230
- package/dist/code/tools/mcp.js.map +0 -1
- package/dist/data/tips.json +0 -118
- package/dist/ui/terminal-ui.d.ts +0 -73
- package/dist/ui/terminal-ui.d.ts.map +0 -1
- package/dist/ui/terminal-ui.js +0 -900
- package/dist/ui/terminal-ui.js.map +0 -1
- package/dist/utils/env-mapper.d.ts +0 -40
- package/dist/utils/env-mapper.d.ts.map +0 -1
- package/dist/utils/env-mapper.js +0 -122
- package/dist/utils/env-mapper.js.map +0 -1
- package/docs/USER_GUIDE.md +0 -573
- package/tests/agent-direct.test.mjs +0 -45
- package/tests/agent-output.test.mjs +0 -64
- package/tests/codemie-code.test.mjs +0 -42
- package/tests/context7-only.test.mjs +0 -42
- package/tests/conversation-flow.test.mjs +0 -63
- package/tests/interactive-simulation.test.mjs +0 -60
- package/tests/live-output.test.mjs +0 -53
- package/tests/mcp-context7.test.mjs +0 -105
- package/tests/mcp-e2e.test.mjs +0 -109
- package/tests/mcp-time-server.test.mjs +0 -58
- package/tests/streaming.test.mjs +0 -57
- package/tests/test-helpers.mjs +0 -94
- package/tests/text-wrapping.test.mjs +0 -33
- package/tests/tool-count.test.mjs +0 -81
- package/tests/ui-format.test.mjs +0 -39
- package/tests/ui-state.test.mjs +0 -72
package/dist/ui/terminal-ui.js
DELETED
|
@@ -1,900 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TerminalUI = void 0;
|
|
7
|
-
// Suppress ALL console.error during blessed import to avoid terminal capability parsing errors
|
|
8
|
-
const originalConsoleError = console.error;
|
|
9
|
-
let suppressErrors = true;
|
|
10
|
-
console.error = function (...args) {
|
|
11
|
-
if (suppressErrors) {
|
|
12
|
-
// Suppress all errors during initial setup
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
originalConsoleError.apply(console, args);
|
|
16
|
-
};
|
|
17
|
-
// Import blessed with error suppression
|
|
18
|
-
const blessed_1 = __importDefault(require("blessed"));
|
|
19
|
-
// Re-enable console.error after a short delay
|
|
20
|
-
setTimeout(() => {
|
|
21
|
-
suppressErrors = false;
|
|
22
|
-
}, 100);
|
|
23
|
-
class TerminalUI {
|
|
24
|
-
screen;
|
|
25
|
-
contentBox;
|
|
26
|
-
inputBox;
|
|
27
|
-
tipBox;
|
|
28
|
-
commandSuggestionBox;
|
|
29
|
-
config;
|
|
30
|
-
isProcessing = false;
|
|
31
|
-
currentAbortController = null;
|
|
32
|
-
tipRotationInterval = null;
|
|
33
|
-
currentTipIndex = 0;
|
|
34
|
-
tips = [];
|
|
35
|
-
shownTips = new Set();
|
|
36
|
-
availableCommands = [
|
|
37
|
-
{ command: '/doctor', description: 'Diagnose and verify your CodeMie installation and settings' },
|
|
38
|
-
{ command: '/list', description: 'List all available agents and their status' },
|
|
39
|
-
{ command: '/clear', description: 'Clear conversation history and free up context', aliases: ['reset', 'new'] },
|
|
40
|
-
{ command: '/exit', description: 'Exit the REPL', aliases: ['quit'] },
|
|
41
|
-
{ command: '/help', description: 'Show help information' },
|
|
42
|
-
{ command: '/install', description: 'Install an agent (e.g., /install aider)' },
|
|
43
|
-
{ command: '/uninstall', description: 'Uninstall an agent' },
|
|
44
|
-
{ command: '/run', description: 'Run a specific agent' },
|
|
45
|
-
];
|
|
46
|
-
filteredCommands = [];
|
|
47
|
-
selectedCommandIndex = 0;
|
|
48
|
-
isSuggestionsVisible = false;
|
|
49
|
-
currentStreamingContent = '';
|
|
50
|
-
currentToolCallContent = [];
|
|
51
|
-
currentStreamingLineCount = 0;
|
|
52
|
-
baseContentLines = [];
|
|
53
|
-
isCancelling = false;
|
|
54
|
-
currentContent = '';
|
|
55
|
-
constructor(config) {
|
|
56
|
-
this.config = config;
|
|
57
|
-
this.screen = this.createScreen();
|
|
58
|
-
this.contentBox = this.createContentBox();
|
|
59
|
-
this.inputBox = this.createInputBox();
|
|
60
|
-
this.tipBox = this.createTipBox();
|
|
61
|
-
this.commandSuggestionBox = this.createCommandSuggestionBox();
|
|
62
|
-
this.setupLayout();
|
|
63
|
-
this.setupKeyBindings();
|
|
64
|
-
this.setupInputHandlers();
|
|
65
|
-
this.screen.render();
|
|
66
|
-
}
|
|
67
|
-
createScreen() {
|
|
68
|
-
const screenOptions = {
|
|
69
|
-
smartCSR: true,
|
|
70
|
-
fullUnicode: true, // Enable full unicode for emoji support
|
|
71
|
-
dockBorders: true,
|
|
72
|
-
title: 'CodeMie Code Assistant',
|
|
73
|
-
warnings: false, // Suppress terminfo warnings
|
|
74
|
-
forceUnicode: true, // Force unicode support for better emoji rendering
|
|
75
|
-
autoPadding: true,
|
|
76
|
-
ignoreLocked: ['C-c'],
|
|
77
|
-
// Enable mouse support for better text selection
|
|
78
|
-
sendFocus: true,
|
|
79
|
-
useBCE: false, // Disable BCE to avoid terminal capability issues
|
|
80
|
-
// Disable cursor color and other advanced features that cause issues
|
|
81
|
-
cursor: {
|
|
82
|
-
artificial: false,
|
|
83
|
-
shape: 'line',
|
|
84
|
-
blink: true,
|
|
85
|
-
color: null
|
|
86
|
-
},
|
|
87
|
-
// Terminal detection options
|
|
88
|
-
terminal: 'xterm-256color',
|
|
89
|
-
dump: false,
|
|
90
|
-
debug: false
|
|
91
|
-
};
|
|
92
|
-
const screen = blessed_1.default.screen(screenOptions);
|
|
93
|
-
// Suppress error output for terminal capabilities
|
|
94
|
-
screen.on('warning', () => {
|
|
95
|
-
// Silently ignore warnings
|
|
96
|
-
});
|
|
97
|
-
// Disable problematic terminal capabilities after screen creation
|
|
98
|
-
if (screen.program && screen.program.setupColors) {
|
|
99
|
-
try {
|
|
100
|
-
// Override setupColors to prevent underline color issues
|
|
101
|
-
const originalSetupColors = screen.program.setupColors;
|
|
102
|
-
screen.program.setupColors = function () {
|
|
103
|
-
try {
|
|
104
|
-
originalSetupColors();
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Ignore color setup errors
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// Ignore if setupColors doesn't exist
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Suppress all console.error calls that might be terminal capability related
|
|
116
|
-
// This catches errors from blessed's terminfo parser (like Setulc)
|
|
117
|
-
if (screen.program) {
|
|
118
|
-
const originalError = console.error;
|
|
119
|
-
console.error = function (...args) {
|
|
120
|
-
// Check if it's a terminal capability error
|
|
121
|
-
const errorStr = args.join(' ');
|
|
122
|
-
if (errorStr.includes('Error on xterm') ||
|
|
123
|
-
errorStr.includes('Setulc') ||
|
|
124
|
-
errorStr.includes('stack.pop') ||
|
|
125
|
-
errorStr.includes('terminfo')) {
|
|
126
|
-
// Silently ignore terminal capability errors
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
// Pass through other errors
|
|
130
|
-
originalError.apply(console, args);
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return screen;
|
|
134
|
-
}
|
|
135
|
-
createContentBox() {
|
|
136
|
-
const boxOptions = {
|
|
137
|
-
top: 0,
|
|
138
|
-
left: 0,
|
|
139
|
-
width: '100%',
|
|
140
|
-
bottom: 7, // Reserve space for input (3 lines) + tips (4 lines with borders)
|
|
141
|
-
scrollable: true,
|
|
142
|
-
alwaysScroll: true,
|
|
143
|
-
scrollbar: {
|
|
144
|
-
ch: '|',
|
|
145
|
-
style: {
|
|
146
|
-
fg: 'cyan'
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
keys: true,
|
|
150
|
-
vi: true,
|
|
151
|
-
mouse: true,
|
|
152
|
-
tags: true,
|
|
153
|
-
border: 'line',
|
|
154
|
-
style: {
|
|
155
|
-
fg: 'white',
|
|
156
|
-
bg: 'black',
|
|
157
|
-
border: {
|
|
158
|
-
fg: 'cyan'
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
label: ' CodeMie Code ',
|
|
162
|
-
// Enable text selection by allowing mouse events to pass through
|
|
163
|
-
clickable: true,
|
|
164
|
-
input: false,
|
|
165
|
-
// Enable word wrapping to prevent text cutoff
|
|
166
|
-
wrap: true,
|
|
167
|
-
wordWrap: true
|
|
168
|
-
};
|
|
169
|
-
return blessed_1.default.box(boxOptions);
|
|
170
|
-
}
|
|
171
|
-
createInputBox() {
|
|
172
|
-
// Use a simple box instead of textarea - just display the prompt
|
|
173
|
-
const inputOptions = {
|
|
174
|
-
bottom: 4,
|
|
175
|
-
left: 0,
|
|
176
|
-
width: '100%',
|
|
177
|
-
height: 3,
|
|
178
|
-
border: 'line',
|
|
179
|
-
style: {
|
|
180
|
-
fg: 'white',
|
|
181
|
-
bg: 'black',
|
|
182
|
-
border: {
|
|
183
|
-
fg: 'green'
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
label: ' You ',
|
|
187
|
-
tags: true,
|
|
188
|
-
content: '{white-fg}> {/white-fg}',
|
|
189
|
-
scrollable: true,
|
|
190
|
-
alwaysScroll: true,
|
|
191
|
-
scrollbar: {
|
|
192
|
-
ch: '|',
|
|
193
|
-
style: {
|
|
194
|
-
fg: 'cyan'
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
const box = blessed_1.default.box(inputOptions);
|
|
199
|
-
// Track input text
|
|
200
|
-
let inputText = '';
|
|
201
|
-
// Update display with multiline support and dynamic height
|
|
202
|
-
const updateDisplay = () => {
|
|
203
|
-
// Split into lines and add "> " only to first line, " " to continuation lines
|
|
204
|
-
const lines = inputText.split('\n');
|
|
205
|
-
const formattedLines = lines.map((line, index) => {
|
|
206
|
-
const prefix = index === 0 ? '> ' : ' ';
|
|
207
|
-
return '{white-fg}' + prefix + this.escapeForBlessed(line) + '{/white-fg}';
|
|
208
|
-
});
|
|
209
|
-
box.setContent(formattedLines.join('\n'));
|
|
210
|
-
// Dynamically adjust height based on number of lines (min 3, max 10)
|
|
211
|
-
const lineCount = lines.length;
|
|
212
|
-
const newHeight = Math.min(Math.max(lineCount + 2, 3), 10 + 2); // +2 for borders
|
|
213
|
-
if (box.height !== newHeight) {
|
|
214
|
-
box.height = newHeight;
|
|
215
|
-
// Need to reposition dependent elements (tip box position stays same at bottom: 0)
|
|
216
|
-
this.screen.render();
|
|
217
|
-
}
|
|
218
|
-
// Auto-scroll to bottom if content exceeds box height
|
|
219
|
-
box.setScrollPerc(100);
|
|
220
|
-
this.screen.render();
|
|
221
|
-
};
|
|
222
|
-
// Store methods and data for getting/clearing input
|
|
223
|
-
box.getInputText = () => inputText;
|
|
224
|
-
box.clearInputText = () => {
|
|
225
|
-
inputText = '';
|
|
226
|
-
box.inputText = inputText;
|
|
227
|
-
// Reset height to minimum when cleared
|
|
228
|
-
box.height = 3;
|
|
229
|
-
updateDisplay();
|
|
230
|
-
};
|
|
231
|
-
box.addNewline = () => {
|
|
232
|
-
inputText += '\n';
|
|
233
|
-
box.inputText = inputText;
|
|
234
|
-
updateDisplay();
|
|
235
|
-
};
|
|
236
|
-
box.setInputText = (text) => {
|
|
237
|
-
inputText = text;
|
|
238
|
-
box.inputText = inputText;
|
|
239
|
-
// Reset height when setting new text (usually for autocomplete)
|
|
240
|
-
box.height = 3;
|
|
241
|
-
updateDisplay();
|
|
242
|
-
};
|
|
243
|
-
// Store inputText directly on box for external access
|
|
244
|
-
box.inputText = inputText;
|
|
245
|
-
// Listen to screen keypresses when box is focused
|
|
246
|
-
box.on('focus', () => {
|
|
247
|
-
box._inputFocused = true;
|
|
248
|
-
});
|
|
249
|
-
box.on('blur', () => {
|
|
250
|
-
box._inputFocused = false;
|
|
251
|
-
});
|
|
252
|
-
// Capture input at screen level
|
|
253
|
-
this.screen.on('keypress', (ch, key) => {
|
|
254
|
-
if (!box._inputFocused)
|
|
255
|
-
return;
|
|
256
|
-
if (!key)
|
|
257
|
-
return;
|
|
258
|
-
// Handle input
|
|
259
|
-
if (key.full === 'backspace') {
|
|
260
|
-
if (inputText.length > 0) {
|
|
261
|
-
inputText = inputText.slice(0, -1);
|
|
262
|
-
box.inputText = inputText; // Keep in sync
|
|
263
|
-
updateDisplay();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else if (key.full === 'enter' || key.full === 'return') {
|
|
267
|
-
// Let the key handler deal with this (shift+enter handled elsewhere)
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
else if (ch && typeof ch === 'string' && ch.length === 1 && !key.ctrl && !key.meta) {
|
|
271
|
-
inputText += ch;
|
|
272
|
-
box.inputText = inputText; // Keep in sync
|
|
273
|
-
updateDisplay();
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
// Make it focusable
|
|
277
|
-
box.clickable = true;
|
|
278
|
-
box.keyable = true;
|
|
279
|
-
box.keys = true;
|
|
280
|
-
box.input = true;
|
|
281
|
-
return box;
|
|
282
|
-
}
|
|
283
|
-
createTipBox() {
|
|
284
|
-
const tipOptions = {
|
|
285
|
-
bottom: 0,
|
|
286
|
-
left: 0,
|
|
287
|
-
width: '100%',
|
|
288
|
-
height: 4,
|
|
289
|
-
tags: true,
|
|
290
|
-
border: 'line',
|
|
291
|
-
style: {
|
|
292
|
-
fg: 'cyan',
|
|
293
|
-
border: {
|
|
294
|
-
fg: 'cyan'
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
label: ' Tip '
|
|
298
|
-
};
|
|
299
|
-
return blessed_1.default.box(tipOptions);
|
|
300
|
-
}
|
|
301
|
-
createCommandSuggestionBox() {
|
|
302
|
-
const suggestionOptions = {
|
|
303
|
-
bottom: 7,
|
|
304
|
-
left: 0,
|
|
305
|
-
width: '100%',
|
|
306
|
-
height: 'shrink',
|
|
307
|
-
tags: true,
|
|
308
|
-
border: 'line',
|
|
309
|
-
style: {
|
|
310
|
-
fg: 'white',
|
|
311
|
-
bg: 'black',
|
|
312
|
-
border: {
|
|
313
|
-
fg: 'yellow'
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
label: ' Available Commands ',
|
|
317
|
-
hidden: true,
|
|
318
|
-
scrollable: true,
|
|
319
|
-
alwaysScroll: true
|
|
320
|
-
};
|
|
321
|
-
return blessed_1.default.box(suggestionOptions);
|
|
322
|
-
}
|
|
323
|
-
setupLayout() {
|
|
324
|
-
this.screen.append(this.contentBox);
|
|
325
|
-
this.screen.append(this.inputBox);
|
|
326
|
-
this.screen.append(this.tipBox);
|
|
327
|
-
this.screen.append(this.commandSuggestionBox);
|
|
328
|
-
// Show welcome message
|
|
329
|
-
this.appendToContent('{cyan-fg}========================================{/cyan-fg}');
|
|
330
|
-
this.appendToContent('{cyan-fg} CodeMie Code Assistant {/cyan-fg}');
|
|
331
|
-
this.appendToContent('{cyan-fg}========================================{/cyan-fg}');
|
|
332
|
-
this.appendToContent('');
|
|
333
|
-
this.appendToContent(`{white-fg}Working directory: ${this.config.workingDirectory}{/white-fg}`);
|
|
334
|
-
this.appendToContent(`{white-fg}Model: ${this.config.model} (${this.config.provider}){/white-fg}`);
|
|
335
|
-
this.appendToContent('');
|
|
336
|
-
this.appendToContent('{gray-fg}Press Enter to send, Shift+Enter for new line{/gray-fg}');
|
|
337
|
-
this.appendToContent('{gray-fg}Use /command for CLI commands (e.g., /doctor, /list){/gray-fg}');
|
|
338
|
-
this.appendToContent('{gray-fg}Press Esc to cancel execution, Ctrl+C or "q" to quit{/gray-fg}');
|
|
339
|
-
this.appendToContent('');
|
|
340
|
-
// Focus input by default
|
|
341
|
-
this.inputBox.focus();
|
|
342
|
-
}
|
|
343
|
-
setupKeyBindings() {
|
|
344
|
-
// Ctrl+C to exit - use both screen and inputBox
|
|
345
|
-
const exitHandler = () => {
|
|
346
|
-
this.destroy();
|
|
347
|
-
this.config.onExit();
|
|
348
|
-
};
|
|
349
|
-
this.screen.key(['C-c', 'q'], exitHandler);
|
|
350
|
-
this.inputBox.key(['C-c'], exitHandler);
|
|
351
|
-
// Esc to cancel agent execution
|
|
352
|
-
this.screen.key(['escape'], () => {
|
|
353
|
-
if (this.isProcessing && this.currentAbortController && !this.isCancelling) {
|
|
354
|
-
this.isCancelling = true;
|
|
355
|
-
this.appendToContent('');
|
|
356
|
-
this.appendToContent('{yellow-fg}Cancelling execution...{/yellow-fg}');
|
|
357
|
-
this.currentAbortController.abort();
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
// Enter to submit
|
|
361
|
-
this.inputBox.key(['enter'], async () => {
|
|
362
|
-
await this.handleSubmit();
|
|
363
|
-
});
|
|
364
|
-
// Shift+Enter for newline (multiline support)
|
|
365
|
-
this.inputBox.key(['S-enter'], () => {
|
|
366
|
-
// Add newline using the built-in method
|
|
367
|
-
const box = this.inputBox;
|
|
368
|
-
box.addNewline();
|
|
369
|
-
});
|
|
370
|
-
// Arrow keys for command navigation when suggestions are visible
|
|
371
|
-
this.inputBox.key(['up'], () => {
|
|
372
|
-
if (this.isSuggestionsVisible && this.filteredCommands.length > 0) {
|
|
373
|
-
this.selectedCommandIndex = Math.max(0, this.selectedCommandIndex - 1);
|
|
374
|
-
this.updateCommandSuggestions();
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
this.inputBox.key(['down'], () => {
|
|
378
|
-
if (this.isSuggestionsVisible && this.filteredCommands.length > 0) {
|
|
379
|
-
this.selectedCommandIndex = Math.min(this.filteredCommands.length - 1, this.selectedCommandIndex + 1);
|
|
380
|
-
this.updateCommandSuggestions();
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
// Tab for autocomplete
|
|
384
|
-
this.inputBox.key(['tab'], () => {
|
|
385
|
-
if (this.isSuggestionsVisible && this.filteredCommands.length > 0) {
|
|
386
|
-
const selectedCommand = this.filteredCommands[this.selectedCommandIndex];
|
|
387
|
-
if (selectedCommand) {
|
|
388
|
-
// Set the input text using our custom method
|
|
389
|
-
const newText = selectedCommand.command + ' ';
|
|
390
|
-
const box = this.inputBox;
|
|
391
|
-
box.setInputText(newText);
|
|
392
|
-
this.hideCommandSuggestions();
|
|
393
|
-
}
|
|
394
|
-
return false; // Prevent default tab behavior
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
// Scroll content with arrow keys when not focused on input
|
|
398
|
-
this.screen.key(['up'], () => {
|
|
399
|
-
if (this.screen.focused !== this.inputBox) {
|
|
400
|
-
this.contentBox.scroll(-1);
|
|
401
|
-
this.screen.render();
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
this.screen.key(['down'], () => {
|
|
405
|
-
if (this.screen.focused !== this.inputBox) {
|
|
406
|
-
this.contentBox.scroll(1);
|
|
407
|
-
this.screen.render();
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
// Page up/down for faster scrolling
|
|
411
|
-
this.screen.key(['pageup'], () => {
|
|
412
|
-
this.contentBox.scroll(-10);
|
|
413
|
-
this.screen.render();
|
|
414
|
-
});
|
|
415
|
-
this.screen.key(['pagedown'], () => {
|
|
416
|
-
this.contentBox.scroll(10);
|
|
417
|
-
this.screen.render();
|
|
418
|
-
});
|
|
419
|
-
// Ctrl+Tab to switch focus between content and input (tab is used for autocomplete)
|
|
420
|
-
this.screen.key(['C-tab'], () => {
|
|
421
|
-
if (this.screen.focused === this.inputBox) {
|
|
422
|
-
this.contentBox.focus();
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
this.inputBox.focus();
|
|
426
|
-
}
|
|
427
|
-
this.screen.render();
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
setupInputHandlers() {
|
|
431
|
-
// Watch for input changes to show command suggestions
|
|
432
|
-
this.inputBox.on('keypress', () => {
|
|
433
|
-
// Use setImmediate to get the updated value after keypress
|
|
434
|
-
setImmediate(() => {
|
|
435
|
-
// Get the actual input text from our custom method
|
|
436
|
-
const box = this.inputBox;
|
|
437
|
-
const currentValue = box.getInputText() || '';
|
|
438
|
-
if (currentValue.startsWith('/') && !currentValue.includes('\n')) {
|
|
439
|
-
// Show command suggestions
|
|
440
|
-
this.showCommandSuggestions(currentValue);
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
// Hide suggestions
|
|
444
|
-
this.hideCommandSuggestions();
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
showCommandSuggestions(input) {
|
|
450
|
-
const searchTerm = input.slice(1).toLowerCase();
|
|
451
|
-
// Filter commands based on search term
|
|
452
|
-
this.filteredCommands = this.availableCommands.filter(cmd => {
|
|
453
|
-
const cmdName = cmd.command.slice(1);
|
|
454
|
-
return cmdName.startsWith(searchTerm) || searchTerm === '';
|
|
455
|
-
});
|
|
456
|
-
// Reset selection if out of bounds
|
|
457
|
-
if (this.selectedCommandIndex >= this.filteredCommands.length) {
|
|
458
|
-
this.selectedCommandIndex = 0;
|
|
459
|
-
}
|
|
460
|
-
if (this.filteredCommands.length > 0) {
|
|
461
|
-
this.isSuggestionsVisible = true;
|
|
462
|
-
this.updateCommandSuggestions();
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
this.hideCommandSuggestions();
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
updateCommandSuggestions() {
|
|
469
|
-
if (!this.isSuggestionsVisible || this.filteredCommands.length === 0)
|
|
470
|
-
return;
|
|
471
|
-
const separator = '{gray-fg}' + '─'.repeat(140) + '{/gray-fg}';
|
|
472
|
-
// Calculate column widths
|
|
473
|
-
const commandColWidth = 30;
|
|
474
|
-
let suggestions = '';
|
|
475
|
-
this.filteredCommands.forEach((cmd, index) => {
|
|
476
|
-
const isSelected = index === this.selectedCommandIndex;
|
|
477
|
-
const bgColor = isSelected ? '{black-bg}{white-fg}' : '';
|
|
478
|
-
const endColor = isSelected ? '{/white-fg}{/black-bg}' : '';
|
|
479
|
-
// Format command with aliases
|
|
480
|
-
const aliasText = cmd.aliases ? ` {gray-fg}(${cmd.aliases.join(', ')}){/gray-fg}` : '';
|
|
481
|
-
const commandText = `{yellow-fg}${cmd.command}{/yellow-fg}${aliasText}`;
|
|
482
|
-
// Calculate padding to align descriptions
|
|
483
|
-
// Note: We need to account for the actual visible length without tags
|
|
484
|
-
const visibleCmdLength = cmd.command.length + (cmd.aliases ? ` (${cmd.aliases.join(', ')})`.length : 0);
|
|
485
|
-
const padding = Math.max(0, commandColWidth - visibleCmdLength);
|
|
486
|
-
suggestions += `${bgColor} ${commandText}${' '.repeat(padding)}${cmd.description}${endColor}\n`;
|
|
487
|
-
});
|
|
488
|
-
this.commandSuggestionBox.setContent(separator + '\n' + suggestions + separator);
|
|
489
|
-
this.commandSuggestionBox.show();
|
|
490
|
-
// Adjust height based on number of matching commands
|
|
491
|
-
const lines = this.filteredCommands.length;
|
|
492
|
-
this.commandSuggestionBox.height = Math.min(lines + 3, 15);
|
|
493
|
-
this.screen.render();
|
|
494
|
-
}
|
|
495
|
-
hideCommandSuggestions() {
|
|
496
|
-
if (!this.commandSuggestionBox.hidden) {
|
|
497
|
-
this.isSuggestionsVisible = false;
|
|
498
|
-
this.selectedCommandIndex = 0;
|
|
499
|
-
this.filteredCommands = [];
|
|
500
|
-
this.commandSuggestionBox.hide();
|
|
501
|
-
this.screen.render();
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
async handleSubmit() {
|
|
505
|
-
if (this.isProcessing)
|
|
506
|
-
return;
|
|
507
|
-
// Get message from our custom input method
|
|
508
|
-
const box = this.inputBox;
|
|
509
|
-
const message = (box.getInputText() || '').trim();
|
|
510
|
-
if (!message)
|
|
511
|
-
return;
|
|
512
|
-
// Hide command suggestions
|
|
513
|
-
this.hideCommandSuggestions();
|
|
514
|
-
// Handle special commands
|
|
515
|
-
if (message.toLowerCase() === 'exit') {
|
|
516
|
-
this.destroy();
|
|
517
|
-
this.config.onExit();
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (message.toLowerCase() === 'clear' || message.toLowerCase() === '/clear') {
|
|
521
|
-
this.config.onClear();
|
|
522
|
-
this.clearContent();
|
|
523
|
-
const box1 = this.inputBox;
|
|
524
|
-
box1.clearInputText();
|
|
525
|
-
this.screen.render();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
// Clear input and reset prompt
|
|
529
|
-
const box2 = this.inputBox;
|
|
530
|
-
box2.clearInputText();
|
|
531
|
-
// Show user message
|
|
532
|
-
this.appendToContent('');
|
|
533
|
-
this.appendToContent(`{white-fg}> ${this.escapeForBlessed(message)}{/white-fg}`);
|
|
534
|
-
this.appendToContent('');
|
|
535
|
-
// Handle slash commands
|
|
536
|
-
if (message.startsWith('/')) {
|
|
537
|
-
const parts = message.slice(1).split(/\s+/);
|
|
538
|
-
const command = parts[0];
|
|
539
|
-
const args = parts.slice(1);
|
|
540
|
-
this.appendToContent(`{yellow-fg}Running command:{/yellow-fg} {white-fg}/${command} ${args.join(' ')}{/white-fg}`);
|
|
541
|
-
this.appendToContent('');
|
|
542
|
-
this.isProcessing = true;
|
|
543
|
-
this.screen.render();
|
|
544
|
-
try {
|
|
545
|
-
const result = await this.config.onSlashCommand(command, args);
|
|
546
|
-
this.appendToContent(`{cyan-fg}Output:{/cyan-fg}`);
|
|
547
|
-
// Split result into lines and append each with proper formatting
|
|
548
|
-
const lines = result.split('\n');
|
|
549
|
-
for (const line of lines) {
|
|
550
|
-
this.appendToContent(`{white-fg}${this.escapeForBlessed(line)}{/white-fg}`);
|
|
551
|
-
}
|
|
552
|
-
this.appendToContent('');
|
|
553
|
-
}
|
|
554
|
-
catch (error) {
|
|
555
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
556
|
-
this.appendToContent(`{red-fg}Error:{/red-fg} {white-fg}${this.escapeForBlessed(errorMessage)}{/white-fg}`);
|
|
557
|
-
this.appendToContent('');
|
|
558
|
-
this.appendToContent('{gray-fg}You can continue using the assistant or try the command again with correct arguments.{/gray-fg}');
|
|
559
|
-
this.appendToContent('');
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
// Ensure state is always restored, even if an error occurred
|
|
563
|
-
this.isProcessing = false;
|
|
564
|
-
// Use setImmediate to ensure focus happens after all other processing
|
|
565
|
-
setImmediate(() => {
|
|
566
|
-
this.inputBox.focus();
|
|
567
|
-
this.screen.render();
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
this.isProcessing = true;
|
|
573
|
-
// Create AbortController for cancellation
|
|
574
|
-
this.currentAbortController = new AbortController();
|
|
575
|
-
this.screen.render();
|
|
576
|
-
try {
|
|
577
|
-
// Use streaming if available
|
|
578
|
-
if (this.config.onSubmitStream) {
|
|
579
|
-
await this.config.onSubmitStream(message, (event) => {
|
|
580
|
-
try {
|
|
581
|
-
switch (event.type) {
|
|
582
|
-
case 'thinking_start':
|
|
583
|
-
this.showThinking();
|
|
584
|
-
break;
|
|
585
|
-
case 'thinking_end':
|
|
586
|
-
this.hideThinking();
|
|
587
|
-
break;
|
|
588
|
-
case 'content_chunk':
|
|
589
|
-
if (this.currentStreamingContent === '') {
|
|
590
|
-
// First chunk - hide thinking and start streaming response
|
|
591
|
-
this.hideThinking();
|
|
592
|
-
this.startStreamingResponse();
|
|
593
|
-
}
|
|
594
|
-
this.appendStreamChunk(event.content);
|
|
595
|
-
break;
|
|
596
|
-
case 'tool_call_start':
|
|
597
|
-
// Hide thinking indicator when tool execution starts
|
|
598
|
-
this.hideThinking();
|
|
599
|
-
// End any current streaming content before showing tool call
|
|
600
|
-
if (this.currentStreamingContent !== '') {
|
|
601
|
-
this.endStreamingResponse();
|
|
602
|
-
}
|
|
603
|
-
this.showToolCallStart(event.toolName, event.toolArgs);
|
|
604
|
-
break;
|
|
605
|
-
case 'tool_call_result':
|
|
606
|
-
this.showToolCallResult(event.toolName, event.result);
|
|
607
|
-
break;
|
|
608
|
-
case 'tool_call_error':
|
|
609
|
-
this.showToolCallError(event.toolName, event.error);
|
|
610
|
-
break;
|
|
611
|
-
case 'cancelled':
|
|
612
|
-
this.endStreamingResponse();
|
|
613
|
-
// Replace the "Cancelling execution..." line with "Execution cancelled."
|
|
614
|
-
this.removeLastLine(); // Remove empty line
|
|
615
|
-
this.removeLastLine(); // Remove "Cancelling execution..."
|
|
616
|
-
this.appendToContent('{yellow-fg}Execution cancelled.{/yellow-fg}');
|
|
617
|
-
this.appendToContent('');
|
|
618
|
-
break;
|
|
619
|
-
case 'complete':
|
|
620
|
-
this.endStreamingResponse();
|
|
621
|
-
break;
|
|
622
|
-
case 'error':
|
|
623
|
-
this.showError(event.error);
|
|
624
|
-
break;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
catch (err) {
|
|
628
|
-
// Catch any errors in event handling to prevent breaking the flow
|
|
629
|
-
console.error('Error handling event:', err);
|
|
630
|
-
}
|
|
631
|
-
}, this.currentAbortController.signal);
|
|
632
|
-
}
|
|
633
|
-
else {
|
|
634
|
-
// Fallback to non-streaming
|
|
635
|
-
this.appendToContent('{cyan-fg}Assistant:{/cyan-fg} {gray-fg}(thinking...){/gray-fg}');
|
|
636
|
-
await this.config.onSubmit(message);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
catch (error) {
|
|
640
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
641
|
-
// Silently ignore cancellation errors - already handled via 'cancelled' event
|
|
642
|
-
if (errorMessage === 'Execution cancelled by user') {
|
|
643
|
-
// Don't return early - fall through to finally block for proper cleanup
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
// Ensure error is displayed to user
|
|
647
|
-
this.showError(errorMessage || 'An error occurred');
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
finally {
|
|
651
|
-
// ALWAYS reset processing state, even if there was an error
|
|
652
|
-
this.isProcessing = false;
|
|
653
|
-
this.currentAbortController = null;
|
|
654
|
-
this.isCancelling = false;
|
|
655
|
-
// Use setImmediate to ensure focus happens after all rendering is complete
|
|
656
|
-
setImmediate(() => {
|
|
657
|
-
this.inputBox.focus();
|
|
658
|
-
this.screen.render();
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
appendToContent(text) {
|
|
663
|
-
this.contentBox.pushLine(text);
|
|
664
|
-
this.contentBox.setScrollPerc(100); // Auto-scroll to bottom
|
|
665
|
-
this.screen.render();
|
|
666
|
-
}
|
|
667
|
-
removeLastLine() {
|
|
668
|
-
const lines = this.contentBox.getLines();
|
|
669
|
-
if (lines.length > 0) {
|
|
670
|
-
this.contentBox.deleteBottom();
|
|
671
|
-
this.screen.render();
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
clearContent() {
|
|
675
|
-
this.contentBox.setContent('');
|
|
676
|
-
this.appendToContent('{yellow-fg}Conversation history cleared.{/yellow-fg}');
|
|
677
|
-
this.appendToContent('');
|
|
678
|
-
}
|
|
679
|
-
showAssistantResponse(response) {
|
|
680
|
-
// Remove the "thinking..." line
|
|
681
|
-
this.removeLastLine();
|
|
682
|
-
// Show assistant response
|
|
683
|
-
this.appendToContent(`{cyan-fg}Assistant:{/cyan-fg} {white-fg}${this.escapeForBlessed(response)}{/white-fg}`);
|
|
684
|
-
this.appendToContent('');
|
|
685
|
-
}
|
|
686
|
-
showError(error) {
|
|
687
|
-
// Remove the "thinking..." line
|
|
688
|
-
this.removeLastLine();
|
|
689
|
-
this.appendToContent(`{red-fg}Error:{/red-fg} {white-fg}${this.escapeForBlessed(error)}{/white-fg}`);
|
|
690
|
-
this.appendToContent('');
|
|
691
|
-
this.appendToContent('{yellow-fg}Note: You can continue the conversation or type "exit" to quit.{/yellow-fg}');
|
|
692
|
-
this.appendToContent('');
|
|
693
|
-
}
|
|
694
|
-
escapeForBlessed(text) {
|
|
695
|
-
// Escape blessed tags
|
|
696
|
-
return text
|
|
697
|
-
.replace(/\{/g, '\\{')
|
|
698
|
-
.replace(/\}/g, '\\}');
|
|
699
|
-
}
|
|
700
|
-
shortenFilePath(path) {
|
|
701
|
-
// Check if this looks like a file path (contains / or \)
|
|
702
|
-
if (!path.includes('/') && !path.includes('\\')) {
|
|
703
|
-
return path;
|
|
704
|
-
}
|
|
705
|
-
// Split by both forward and back slashes
|
|
706
|
-
const parts = path.split(/[/\\]/);
|
|
707
|
-
// If path has 2 or fewer parts, return as is
|
|
708
|
-
if (parts.length <= 2) {
|
|
709
|
-
return path;
|
|
710
|
-
}
|
|
711
|
-
// Return last 2 parts (parent dir + filename)
|
|
712
|
-
return parts.slice(-2).join('/');
|
|
713
|
-
}
|
|
714
|
-
shortenAllPathsInText(text) {
|
|
715
|
-
// Match absolute paths (starting with / or drive letter on Windows)
|
|
716
|
-
// Pattern: /path/to/file or C:\path\to\file
|
|
717
|
-
const pathPattern = /(?:^|[\s(,["'])([/\\][\w\-./\\]+[\w\-.]|[A-Z]:[/\\][\w\-./\\]+[\w\-.])/g;
|
|
718
|
-
return text.replace(pathPattern, (match, path) => {
|
|
719
|
-
const before = match[0] !== '/' && match[0] !== '\\' && !/[A-Z]:/.test(match.slice(0, 2)) ? match[0] : '';
|
|
720
|
-
const shortened = this.shortenFilePath(path.trim());
|
|
721
|
-
return before + shortened;
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
formatToolArg(key, value) {
|
|
725
|
-
let strValue;
|
|
726
|
-
if (typeof value === 'string') {
|
|
727
|
-
// Check if it's a file path and shorten it
|
|
728
|
-
strValue = this.shortenFilePath(value);
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
strValue = JSON.stringify(value);
|
|
732
|
-
}
|
|
733
|
-
return strValue;
|
|
734
|
-
}
|
|
735
|
-
setTips(tips) {
|
|
736
|
-
this.tips = tips;
|
|
737
|
-
if (tips.length > 0) {
|
|
738
|
-
this.showRandomTip();
|
|
739
|
-
this.startTipRotation();
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
getRandomTip() {
|
|
743
|
-
if (this.tips.length === 0)
|
|
744
|
-
return null;
|
|
745
|
-
const availableTips = this.tips
|
|
746
|
-
.map((tip, index) => ({ tip, index }))
|
|
747
|
-
.filter(({ index }) => !this.shownTips.has(index));
|
|
748
|
-
// Reset if all tips shown
|
|
749
|
-
if (availableTips.length === 0) {
|
|
750
|
-
this.shownTips.clear();
|
|
751
|
-
return this.getRandomTip();
|
|
752
|
-
}
|
|
753
|
-
const selected = availableTips[Math.floor(Math.random() * availableTips.length)];
|
|
754
|
-
this.shownTips.add(selected.index);
|
|
755
|
-
this.currentTipIndex = selected.index;
|
|
756
|
-
return selected.tip;
|
|
757
|
-
}
|
|
758
|
-
showRandomTip() {
|
|
759
|
-
const tip = this.getRandomTip();
|
|
760
|
-
if (tip) {
|
|
761
|
-
let tipText = `{cyan-fg}[Tip]{/cyan-fg} ${this.escapeForBlessed(tip.message)}`;
|
|
762
|
-
if (tip.command) {
|
|
763
|
-
tipText += `\n{gray-fg} =>{/gray-fg} {yellow-fg}${this.escapeForBlessed(tip.command)}{/yellow-fg}`;
|
|
764
|
-
}
|
|
765
|
-
this.tipBox.setContent(tipText);
|
|
766
|
-
this.screen.render();
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
startTipRotation(intervalMs = 15000) {
|
|
770
|
-
if (this.tipRotationInterval) {
|
|
771
|
-
clearInterval(this.tipRotationInterval);
|
|
772
|
-
}
|
|
773
|
-
this.tipRotationInterval = setInterval(() => {
|
|
774
|
-
if (!this.isProcessing) {
|
|
775
|
-
this.showRandomTip();
|
|
776
|
-
}
|
|
777
|
-
}, intervalMs);
|
|
778
|
-
}
|
|
779
|
-
render() {
|
|
780
|
-
this.screen.render();
|
|
781
|
-
}
|
|
782
|
-
// Streaming-specific methods
|
|
783
|
-
startStreamingResponse() {
|
|
784
|
-
this.currentStreamingContent = '';
|
|
785
|
-
this.currentToolCallContent = [];
|
|
786
|
-
this.currentStreamingLineCount = 0;
|
|
787
|
-
// Store current content state
|
|
788
|
-
this.baseContentLines = this.contentBox.getLines().slice();
|
|
789
|
-
}
|
|
790
|
-
appendStreamChunk(chunk) {
|
|
791
|
-
this.currentStreamingContent += chunk;
|
|
792
|
-
// Rebuild content from base + streaming
|
|
793
|
-
const allLines = [...this.baseContentLines];
|
|
794
|
-
// Split streaming content into logical lines
|
|
795
|
-
const streamingLines = this.currentStreamingContent.split('\n');
|
|
796
|
-
// Add first line with green dot
|
|
797
|
-
allLines.push(`{green-fg}⏺{/green-fg} {white-fg}${this.escapeForBlessed(streamingLines[0])}{/white-fg}`);
|
|
798
|
-
// Add continuation lines without the dot
|
|
799
|
-
for (let i = 1; i < streamingLines.length; i++) {
|
|
800
|
-
allLines.push(`{white-fg}${this.escapeForBlessed(streamingLines[i])}{/white-fg}`);
|
|
801
|
-
}
|
|
802
|
-
// Replace entire content
|
|
803
|
-
this.contentBox.setContent(allLines.join('\n'));
|
|
804
|
-
this.contentBox.setScrollPerc(100); // Auto-scroll to bottom
|
|
805
|
-
this.screen.render();
|
|
806
|
-
}
|
|
807
|
-
showThinking() {
|
|
808
|
-
this.appendToContent(`{gray-fg}(thinking...){/gray-fg}`);
|
|
809
|
-
}
|
|
810
|
-
hideThinking() {
|
|
811
|
-
this.removeLastLine();
|
|
812
|
-
}
|
|
813
|
-
showToolCallStart(toolName, toolArgs) {
|
|
814
|
-
// Format tool call as: ⏺ ToolName with only file path (hide large content args and null values)
|
|
815
|
-
// Extract file path if present (and not null/undefined)
|
|
816
|
-
let fileInfo = '';
|
|
817
|
-
if (toolArgs.file_path && toolArgs.file_path !== null && toolArgs.file_path !== undefined) {
|
|
818
|
-
fileInfo = `(${this.shortenFilePath(String(toolArgs.file_path))})`;
|
|
819
|
-
}
|
|
820
|
-
else if (toolArgs.path && toolArgs.path !== null && toolArgs.path !== undefined) {
|
|
821
|
-
fileInfo = `(${this.shortenFilePath(String(toolArgs.path))})`;
|
|
822
|
-
}
|
|
823
|
-
else if (toolArgs.notebook_path && toolArgs.notebook_path !== null && toolArgs.notebook_path !== undefined) {
|
|
824
|
-
fileInfo = `(${this.shortenFilePath(String(toolArgs.notebook_path))})`;
|
|
825
|
-
}
|
|
826
|
-
const toolMessage = `{green-fg}⏺{/green-fg} {white-fg}${this.escapeForBlessed(toolName)}${fileInfo ? ' ' + this.escapeForBlessed(fileInfo) : ''}{/white-fg}`;
|
|
827
|
-
this.appendToContent(toolMessage);
|
|
828
|
-
this.currentToolCallContent.push(toolMessage);
|
|
829
|
-
}
|
|
830
|
-
showToolCallResult(toolName, result) {
|
|
831
|
-
// Format result with indentation: ⎿ first line\n continuation...
|
|
832
|
-
// Shorten all file paths in the result
|
|
833
|
-
const shortenedResult = this.shortenAllPathsInText(result);
|
|
834
|
-
const lines = shortenedResult.split('\n');
|
|
835
|
-
const maxLines = 3;
|
|
836
|
-
const displayLines = lines.slice(0, maxLines);
|
|
837
|
-
const hiddenLines = Math.max(0, lines.length - maxLines);
|
|
838
|
-
let resultMessage = ' {gray-fg}⎿{/gray-fg} ' + this.escapeForBlessed(displayLines[0] || '');
|
|
839
|
-
for (let i = 1; i < displayLines.length; i++) {
|
|
840
|
-
resultMessage += '\n ' + this.escapeForBlessed(displayLines[i]);
|
|
841
|
-
}
|
|
842
|
-
if (hiddenLines > 0) {
|
|
843
|
-
resultMessage += `\n {gray-fg}… +${hiddenLines} lines (ctrl+o to expand){/gray-fg}`;
|
|
844
|
-
}
|
|
845
|
-
this.appendToContent(resultMessage);
|
|
846
|
-
this.appendToContent('');
|
|
847
|
-
this.currentToolCallContent.push(resultMessage);
|
|
848
|
-
}
|
|
849
|
-
showToolCallError(toolName, error) {
|
|
850
|
-
// Format error with red dot
|
|
851
|
-
const errorMessage = `{red-fg}⏺ Error in ${this.escapeForBlessed(toolName)}{/red-fg}\n {gray-fg}⎿{/gray-fg} {red-fg}${this.escapeForBlessed(error)}{/red-fg}`;
|
|
852
|
-
this.appendToContent(errorMessage);
|
|
853
|
-
this.appendToContent('');
|
|
854
|
-
this.currentToolCallContent.push(errorMessage);
|
|
855
|
-
}
|
|
856
|
-
endStreamingResponse() {
|
|
857
|
-
// Finalize the streaming content by adding it properly
|
|
858
|
-
if (this.currentStreamingContent) {
|
|
859
|
-
// The content is already displayed, just need to update base
|
|
860
|
-
this.baseContentLines = this.contentBox.getLines().slice();
|
|
861
|
-
}
|
|
862
|
-
// Add a blank line after the response
|
|
863
|
-
this.appendToContent('');
|
|
864
|
-
this.currentStreamingContent = '';
|
|
865
|
-
this.currentToolCallContent = [];
|
|
866
|
-
this.currentStreamingLineCount = 0;
|
|
867
|
-
}
|
|
868
|
-
destroy() {
|
|
869
|
-
try {
|
|
870
|
-
if (this.tipRotationInterval) {
|
|
871
|
-
clearInterval(this.tipRotationInterval);
|
|
872
|
-
this.tipRotationInterval = null;
|
|
873
|
-
}
|
|
874
|
-
// Hide all widgets before destroying
|
|
875
|
-
this.commandSuggestionBox.hide();
|
|
876
|
-
this.tipBox.hide();
|
|
877
|
-
this.inputBox.hide();
|
|
878
|
-
this.contentBox.hide();
|
|
879
|
-
// Reset terminal state
|
|
880
|
-
if (this.screen) {
|
|
881
|
-
this.screen.program.clear();
|
|
882
|
-
this.screen.program.disableMouse();
|
|
883
|
-
this.screen.program.showCursor();
|
|
884
|
-
this.screen.program.normalBuffer();
|
|
885
|
-
// Destroy the screen (suppress any errors)
|
|
886
|
-
try {
|
|
887
|
-
this.screen.destroy();
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
// Ignore terminfo errors on cleanup
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
catch {
|
|
895
|
-
// Silently handle any cleanup errors
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
exports.TerminalUI = TerminalUI;
|
|
900
|
-
//# sourceMappingURL=terminal-ui.js.map
|