openhorse 0.1.14 → 0.1.16
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/dist/cli-dbg.js +717 -0
- package/dist/cli-debug.d.ts +7 -0
- package/dist/cli-debug.d.ts.map +1 -0
- package/dist/cli-debug.js +717 -0
- package/dist/cli-debug.js.map +1 -0
- package/dist/cli-debug2.d.ts +7 -0
- package/dist/cli-debug2.d.ts.map +1 -0
- package/dist/cli-debug2.js +717 -0
- package/dist/cli-debug2.js.map +1 -0
- package/dist/cli-debug3.d.ts +7 -0
- package/dist/cli-debug3.d.ts.map +1 -0
- package/dist/cli-debug3.js +719 -0
- package/dist/cli-debug3.js.map +1 -0
- package/dist/cli-patch.js +716 -0
- package/dist/cli-test.js +726 -0
- package/dist/cli.js +39 -42
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +54 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/framework/index.d.ts +2 -0
- package/dist/framework/index.d.ts.map +1 -1
- package/dist/framework/index.js +8 -1
- package/dist/framework/index.js.map +1 -1
- package/dist/framework/prompt.d.ts.map +1 -1
- package/dist/framework/prompt.js +1 -0
- package/dist/framework/prompt.js.map +1 -1
- package/dist/framework/query.d.ts +3 -0
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +35 -16
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/store.d.ts +3 -0
- package/dist/framework/store.d.ts.map +1 -1
- package/dist/framework/store.js.map +1 -1
- package/dist/harness/assembler.d.ts +5 -0
- package/dist/harness/assembler.d.ts.map +1 -0
- package/dist/harness/assembler.js +71 -0
- package/dist/harness/assembler.js.map +1 -0
- package/dist/harness/capsule.d.ts +4 -0
- package/dist/harness/capsule.d.ts.map +1 -0
- package/dist/harness/capsule.js +99 -0
- package/dist/harness/capsule.js.map +1 -0
- package/dist/harness/context-harness.d.ts +41 -0
- package/dist/harness/context-harness.d.ts.map +1 -0
- package/dist/harness/context-harness.js +139 -0
- package/dist/harness/context-harness.js.map +1 -0
- package/dist/harness/contract.d.ts +4 -0
- package/dist/harness/contract.d.ts.map +1 -0
- package/dist/harness/contract.js +62 -0
- package/dist/harness/contract.js.map +1 -0
- package/dist/harness/drift-guard.d.ts +12 -0
- package/dist/harness/drift-guard.d.ts.map +1 -0
- package/dist/harness/drift-guard.js +50 -0
- package/dist/harness/drift-guard.js.map +1 -0
- package/dist/harness/index.d.ts +10 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +21 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/ledger.d.ts +29 -0
- package/dist/harness/ledger.d.ts.map +1 -0
- package/dist/harness/ledger.js +122 -0
- package/dist/harness/ledger.js.map +1 -0
- package/dist/harness/types.d.ts +81 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +3 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/services/compact/auto-compact.d.ts +37 -8
- package/dist/services/compact/auto-compact.d.ts.map +1 -1
- package/dist/services/compact/auto-compact.js +80 -17
- package/dist/services/compact/auto-compact.js.map +1 -1
- package/dist/services/compact/compact.d.ts +3 -0
- package/dist/services/compact/compact.d.ts.map +1 -1
- package/dist/services/compact/compact.js +13 -1
- package/dist/services/compact/compact.js.map +1 -1
- package/dist/services/config.d.ts +10 -13
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +19 -32
- package/dist/services/config.js.map +1 -1
- package/dist/services/global-config.d.ts +8 -51
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js +2 -43
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/llm.d.ts +1 -9
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +12 -4
- package/dist/services/llm.js.map +1 -1
- package/dist/services/model-context.d.ts +44 -0
- package/dist/services/model-context.d.ts.map +1 -0
- package/dist/services/model-context.js +134 -0
- package/dist/services/model-context.js.map +1 -0
- package/dist/services/session-storage.d.ts +9 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +12 -0
- package/dist/services/session-storage.js.map +1 -1
- package/dist/tools/bash_security.d.ts +10 -0
- package/dist/tools/bash_security.d.ts.map +1 -1
- package/dist/tools/bash_security.js +30 -1
- package/dist/tools/bash_security.js.map +1 -1
- package/dist/tools/lsp.d.ts.map +1 -1
- package/dist/tools/lsp.js +9 -6
- package/dist/tools/lsp.js.map +1 -1
- package/dist/ui/command-panel.d.ts +1 -3
- package/dist/ui/command-panel.d.ts.map +1 -1
- package/dist/ui/command-panel.js +57 -32
- package/dist/ui/command-panel.js.map +1 -1
- package/dist/ui/stream-markdown.d.ts +18 -6
- package/dist/ui/stream-markdown.d.ts.map +1 -1
- package/dist/ui/stream-markdown.js +121 -32
- package/dist/ui/stream-markdown.js.map +1 -1
- package/dist/ui/user-input.d.ts +10 -0
- package/dist/ui/user-input.d.ts.map +1 -0
- package/dist/ui/user-input.js +66 -0
- package/dist/ui/user-input.js.map +1 -0
- package/dist/utils/chalk.d.ts +9 -0
- package/dist/utils/chalk.d.ts.map +1 -0
- package/dist/utils/chalk.js +16 -0
- package/dist/utils/chalk.js.map +1 -0
- package/package.json +1 -1
package/dist/cli-dbg.js
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* openhorse - 命令行交互入口
|
|
4
|
+
*
|
|
5
|
+
* 简洁版 REPL,使用 readline + keypress 事件处理
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
require("dotenv/config");
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const readline_1 = __importDefault(require("readline"));
|
|
14
|
+
const fs_1 = require("fs");
|
|
15
|
+
const path_1 = require("path");
|
|
16
|
+
const init_1 = require("./init");
|
|
17
|
+
const llm_1 = require("./services/llm");
|
|
18
|
+
const tools_1 = require("./tools");
|
|
19
|
+
const mcp_1 = require("./tools/mcp");
|
|
20
|
+
const config_1 = require("./services/config");
|
|
21
|
+
const config_dir_1 = require("./services/config-dir");
|
|
22
|
+
const global_config_1 = require("./services/global-config");
|
|
23
|
+
const session_storage_1 = require("./services/session-storage");
|
|
24
|
+
const storage_1 = require("./memory/storage");
|
|
25
|
+
const skills_1 = require("./skills");
|
|
26
|
+
const framework_1 = require("./framework");
|
|
27
|
+
const commands_1 = require("./commands");
|
|
28
|
+
const parser_1 = require("./commands/parser");
|
|
29
|
+
const types_1 = require("./commands/types");
|
|
30
|
+
const box_1 = require("./ui/box");
|
|
31
|
+
const command_panel_1 = require("./ui/command-panel");
|
|
32
|
+
const multiline_input_1 = require("./ui/multiline-input");
|
|
33
|
+
const file_completion_1 = require("./ui/file-completion");
|
|
34
|
+
const status_bar_1 = require("./ui/status-bar");
|
|
35
|
+
// Get version from package.json
|
|
36
|
+
const VERSION = (() => {
|
|
37
|
+
try {
|
|
38
|
+
const pkgPath = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
39
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
40
|
+
return pkg.version || '0.1.14';
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return '0.1.14';
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// 颜色常量
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Force chalk to use full 24-bit color. Must be set BEFORE any chalk.hex/bgHex calls.
|
|
50
|
+
chalk_1.default.level = 3; process.stderr.write("DEBUG chalk.level: " + chalk_1.default.level + "
|
|
51
|
+
");
|
|
52
|
+
const BRAND = chalk_1.default.hex('#FF6B35');
|
|
53
|
+
const ACCENT = chalk_1.default.hex('#00D4AA');
|
|
54
|
+
const DIM = chalk_1.default.dim;
|
|
55
|
+
const ERROR = chalk_1.default.red;
|
|
56
|
+
const WARN = chalk_1.default.yellow;
|
|
57
|
+
const SUCCESS = chalk_1.default.green;
|
|
58
|
+
/** User input background — dark slate fill (like Claude Code) */
|
|
59
|
+
const USER_INPUT_BG = chalk_1.default.bgHex('#1E293B').hex('#E2E8F0');
|
|
60
|
+
const USER_INPUT_ACCENT = chalk_1.default.bgHex('#1E293B').hex('#00D4AA');
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// CLI Help
|
|
63
|
+
// ============================================================================
|
|
64
|
+
function showCliHelp() {
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(BRAND('openhorse') + DIM(` v${VERSION}`));
|
|
67
|
+
console.log(DIM(' Universal Agent Harness Framework'));
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(ACCENT('Usage:'));
|
|
70
|
+
console.log(' openhorse Start interactive REPL');
|
|
71
|
+
console.log(' openhorse --help Show this help message');
|
|
72
|
+
console.log(' openhorse --version Show version');
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(ACCENT('Options:'));
|
|
75
|
+
console.log(' -h, --help Show help');
|
|
76
|
+
console.log(' -v, --version Show version');
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(ACCENT('Interactive Commands:'));
|
|
79
|
+
console.log(' /help Show available slash commands');
|
|
80
|
+
console.log(' /status Show system status');
|
|
81
|
+
console.log(' /model [name] Show or change model');
|
|
82
|
+
console.log(' /chat <msg> Send message to LLM');
|
|
83
|
+
console.log(' /exit Exit the REPL');
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(DIM('Type /help in REPL for full command list.'));
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// 全局状态
|
|
90
|
+
// ============================================================================
|
|
91
|
+
let llm = null;
|
|
92
|
+
let store;
|
|
93
|
+
let currentSession = null;
|
|
94
|
+
let runtime;
|
|
95
|
+
// 输入状态
|
|
96
|
+
let currentInput = '';
|
|
97
|
+
let inputHistory = [];
|
|
98
|
+
let historyIndex = -1;
|
|
99
|
+
let historyMode = 'none';
|
|
100
|
+
let searchQuery = '';
|
|
101
|
+
function parseKey(char, key) {
|
|
102
|
+
if (!key) {
|
|
103
|
+
// 手动解析
|
|
104
|
+
if (char === '\r' || char === '\n')
|
|
105
|
+
return { name: 'enter', ctrl: false, shift: false, meta: false, sequence: char || '' };
|
|
106
|
+
if (char === '\x1b')
|
|
107
|
+
return { name: 'escape', ctrl: false, shift: false, meta: false, sequence: char };
|
|
108
|
+
if (char === '\t')
|
|
109
|
+
return { name: 'tab', ctrl: false, shift: false, meta: false, sequence: char };
|
|
110
|
+
if (char === '\x7f' || char === '\b')
|
|
111
|
+
return { name: 'backspace', ctrl: false, shift: false, meta: false, sequence: char };
|
|
112
|
+
return { name: char || '', ctrl: false, shift: false, meta: false, sequence: char || '' };
|
|
113
|
+
}
|
|
114
|
+
// 统一 "return" 和 "enter"
|
|
115
|
+
if (key.name === 'return') {
|
|
116
|
+
key.name = 'enter';
|
|
117
|
+
}
|
|
118
|
+
// 如果 key.name 为空,使用 char 作为 name
|
|
119
|
+
if (!key.name && char) {
|
|
120
|
+
key.name = char;
|
|
121
|
+
}
|
|
122
|
+
return key;
|
|
123
|
+
}
|
|
124
|
+
function handleKeypress(char, key) {
|
|
125
|
+
const k = parseKey(char, key);
|
|
126
|
+
// 命令面板模式
|
|
127
|
+
if ((0, command_panel_1.isPanelVisible)()) {
|
|
128
|
+
handlePanelKeypress(k, char);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// 文件补全模式
|
|
132
|
+
if ((0, file_completion_1.isFileCompletionVisible)()) {
|
|
133
|
+
handleFileCompletionKeypress(k, char);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// 历史导航模式
|
|
137
|
+
if (historyMode !== 'none') {
|
|
138
|
+
handleHistoryKeypress(k, char);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// 正常输入模式
|
|
142
|
+
handleNormalKeypress(k, char);
|
|
143
|
+
}
|
|
144
|
+
function handlePanelKeypress(k, char) {
|
|
145
|
+
switch (k.name) {
|
|
146
|
+
case 'up':
|
|
147
|
+
(0, command_panel_1.navigatePanel)('up');
|
|
148
|
+
break;
|
|
149
|
+
case 'down':
|
|
150
|
+
(0, command_panel_1.navigatePanel)('down');
|
|
151
|
+
break;
|
|
152
|
+
case 'enter':
|
|
153
|
+
const cmd = (0, command_panel_1.selectCommand)();
|
|
154
|
+
if (cmd) {
|
|
155
|
+
currentInput = cmd;
|
|
156
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
157
|
+
// 直接执行命令
|
|
158
|
+
handleInput(currentInput).catch(err => {
|
|
159
|
+
console.log(ERROR(`Command error: ${err.message || String(err)}`));
|
|
160
|
+
});
|
|
161
|
+
currentInput = '';
|
|
162
|
+
(0, command_panel_1.clearPendingCommand)();
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'escape':
|
|
166
|
+
(0, command_panel_1.hideCommandPanel)();
|
|
167
|
+
currentInput = '';
|
|
168
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
169
|
+
break;
|
|
170
|
+
case 'backspace':
|
|
171
|
+
currentInput = currentInput.slice(0, -1);
|
|
172
|
+
if (currentInput.length <= 1) {
|
|
173
|
+
(0, command_panel_1.hideCommandPanel)();
|
|
174
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
(0, command_panel_1.updatePanelFilter)(currentInput.slice(1));
|
|
178
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
default:
|
|
182
|
+
// 添加字符到过滤
|
|
183
|
+
if (char && char.length === 1 && !k.ctrl) {
|
|
184
|
+
currentInput += char;
|
|
185
|
+
(0, command_panel_1.updatePanelFilter)(currentInput.slice(1));
|
|
186
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function handleHistoryKeypress(k, char) {
|
|
191
|
+
if (historyMode === 'search') {
|
|
192
|
+
// 搜索模式:输入搜索词
|
|
193
|
+
switch (k.name) {
|
|
194
|
+
case 'enter':
|
|
195
|
+
if (inputHistory.length > 0 && historyIndex >= 0) {
|
|
196
|
+
currentInput = inputHistory[historyIndex]?.content || '';
|
|
197
|
+
}
|
|
198
|
+
historyMode = 'none';
|
|
199
|
+
searchQuery = '';
|
|
200
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
201
|
+
break;
|
|
202
|
+
case 'escape':
|
|
203
|
+
historyMode = 'none';
|
|
204
|
+
searchQuery = '';
|
|
205
|
+
currentInput = '';
|
|
206
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
207
|
+
break;
|
|
208
|
+
case 'backspace':
|
|
209
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
210
|
+
updateHistorySearch();
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
if (char && char.length === 1 && !k.ctrl) {
|
|
214
|
+
searchQuery += char;
|
|
215
|
+
updateHistorySearch();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// 导航模式
|
|
221
|
+
switch (k.name) {
|
|
222
|
+
case 'up':
|
|
223
|
+
historyIndex = Math.min(inputHistory.length - 1, historyIndex + 1);
|
|
224
|
+
currentInput = inputHistory[historyIndex]?.content || '';
|
|
225
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
226
|
+
break;
|
|
227
|
+
case 'down':
|
|
228
|
+
historyIndex = Math.max(-1, historyIndex - 1);
|
|
229
|
+
if (historyIndex < 0) {
|
|
230
|
+
currentInput = '';
|
|
231
|
+
historyMode = 'none';
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
currentInput = inputHistory[historyIndex]?.content || '';
|
|
235
|
+
}
|
|
236
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
237
|
+
break;
|
|
238
|
+
case 'enter':
|
|
239
|
+
historyMode = 'none';
|
|
240
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
241
|
+
break;
|
|
242
|
+
case 'escape':
|
|
243
|
+
historyMode = 'none';
|
|
244
|
+
currentInput = '';
|
|
245
|
+
historyIndex = -1;
|
|
246
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function handleFileCompletionKeypress(k, char) {
|
|
252
|
+
switch (k.name) {
|
|
253
|
+
case 'up':
|
|
254
|
+
(0, file_completion_1.navigateFiles)('up');
|
|
255
|
+
break;
|
|
256
|
+
case 'down':
|
|
257
|
+
(0, file_completion_1.navigateFiles)('down');
|
|
258
|
+
break;
|
|
259
|
+
case 'tab':
|
|
260
|
+
const completedPath = (0, file_completion_1.completeFile)();
|
|
261
|
+
if (completedPath) {
|
|
262
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + completedPath;
|
|
263
|
+
(0, file_completion_1.hideFileCompletion)();
|
|
264
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// 目录:继续显示面板,更新 query
|
|
268
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
269
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case 'enter':
|
|
273
|
+
const selectedPath = (0, file_completion_1.selectFile)();
|
|
274
|
+
if (selectedPath) {
|
|
275
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + selectedPath;
|
|
276
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
case 'escape':
|
|
280
|
+
(0, file_completion_1.hideFileCompletion)();
|
|
281
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
282
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
283
|
+
break;
|
|
284
|
+
case 'backspace':
|
|
285
|
+
const query = (0, file_completion_1.getFileQuery)();
|
|
286
|
+
if (query.length > 0) {
|
|
287
|
+
(0, file_completion_1.updateFileQuery)(query.slice(0, -1));
|
|
288
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
289
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
(0, file_completion_1.hideFileCompletion)();
|
|
293
|
+
currentInput = (0, file_completion_1.getBaseInput)();
|
|
294
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
default:
|
|
298
|
+
// 添加字符到路径查询
|
|
299
|
+
if (char && char.length === 1 && !k.ctrl) {
|
|
300
|
+
const newQuery = (0, file_completion_1.getFileQuery)() + char;
|
|
301
|
+
(0, file_completion_1.updateFileQuery)(newQuery);
|
|
302
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + newQuery;
|
|
303
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function handleNormalKeypress(k, char) {
|
|
308
|
+
switch (k.name) {
|
|
309
|
+
case 'enter':
|
|
310
|
+
// 多行模式:添加行
|
|
311
|
+
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
312
|
+
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
313
|
+
(0, multiline_input_1.addMultilineLine)(currentInput.slice(0, -1));
|
|
314
|
+
currentInput = '';
|
|
315
|
+
process.stdout.write('\r\x1b[2K');
|
|
316
|
+
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// 结束多行,发送完整输入
|
|
320
|
+
(0, multiline_input_1.addMultilineLine)(currentInput);
|
|
321
|
+
const fullInput = (0, multiline_input_1.getMultilineInput)();
|
|
322
|
+
(0, multiline_input_1.resetMultiline)();
|
|
323
|
+
if (fullInput.trim()) {
|
|
324
|
+
// 回显多行输入(带半透明背景)
|
|
325
|
+
process.stdout.write('\x1b[2K\r');
|
|
326
|
+
const lines = fullInput.split('\n');
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
console.log(USER_INPUT_ACCENT('❯ ') + USER_INPUT_BG(' ' + line + ' '));
|
|
329
|
+
}
|
|
330
|
+
// Issue #32 fix: 重置渲染长度,防止后续 redrawInputWithPrompt 清除用户输入
|
|
331
|
+
(0, command_panel_1.resetRenderLength)();
|
|
332
|
+
handleInput(fullInput).catch(err => {
|
|
333
|
+
console.log(ERROR(`Input error: ${err.message || String(err)}`));
|
|
334
|
+
});
|
|
335
|
+
(0, global_config_1.addToInputHistory)(fullInput);
|
|
336
|
+
inputHistory = (0, global_config_1.getInputHistory)();
|
|
337
|
+
}
|
|
338
|
+
currentInput = '';
|
|
339
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// 检查是否进入多行模式
|
|
344
|
+
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
345
|
+
(0, multiline_input_1.enterMultiline)(currentInput);
|
|
346
|
+
currentInput = '';
|
|
347
|
+
process.stdout.write('\r\x1b[2K');
|
|
348
|
+
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// 正常发送输入
|
|
352
|
+
if (currentInput.trim()) {
|
|
353
|
+
// 先清除输入行的 prompt,然后打印用户输入(保存到终端历史)
|
|
354
|
+
process.stdout.write('\x1b[2K\r'); // 清除当前 prompt 行
|
|
355
|
+
// Echo user input with semi-transparent background fill (Claude Code style)
|
|
356
|
+
const echoLines = currentInput.split('\n');
|
|
357
|
+
for (const echoLine of echoLines) {
|
|
358
|
+
console.log(USER_INPUT_ACCENT('❯ ') + USER_INPUT_BG(' ' + echoLine + ' '));
|
|
359
|
+
}
|
|
360
|
+
// Issue #32 fix: 重置渲染长度,防止后续 redrawInputWithPrompt 清除用户输入行
|
|
361
|
+
// 因为 console.log 打印后光标在新行,redrawInputWithPrompt 会从当前位置向上清除
|
|
362
|
+
(0, command_panel_1.resetRenderLength)();
|
|
363
|
+
handleInput(currentInput);
|
|
364
|
+
(0, global_config_1.addToInputHistory)(currentInput);
|
|
365
|
+
inputHistory = (0, global_config_1.getInputHistory)();
|
|
366
|
+
currentInput = '';
|
|
367
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
case 'backspace':
|
|
371
|
+
currentInput = currentInput.slice(0, -1);
|
|
372
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
373
|
+
break;
|
|
374
|
+
case 'up':
|
|
375
|
+
// 进入历史导航
|
|
376
|
+
if (currentInput === '' && inputHistory.length > 0) {
|
|
377
|
+
historyMode = 'navigate';
|
|
378
|
+
historyIndex = 0;
|
|
379
|
+
currentInput = inputHistory[0]?.content || '';
|
|
380
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
case 'r':
|
|
384
|
+
if (k.ctrl) {
|
|
385
|
+
// Ctrl+R:搜索历史
|
|
386
|
+
historyMode = 'search';
|
|
387
|
+
searchQuery = '';
|
|
388
|
+
showHistorySearchPrompt();
|
|
389
|
+
}
|
|
390
|
+
else if (char) {
|
|
391
|
+
currentInput += char;
|
|
392
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
case 'c':
|
|
396
|
+
if (k.ctrl) {
|
|
397
|
+
// Ctrl+C:取消多行或退出
|
|
398
|
+
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
399
|
+
(0, multiline_input_1.resetMultiline)();
|
|
400
|
+
currentInput = '';
|
|
401
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
402
|
+
console.log(DIM('(cancelled)'));
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
handleCtrlC();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else if (char) {
|
|
409
|
+
currentInput += char;
|
|
410
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
case 'd':
|
|
414
|
+
if (k.ctrl && currentInput === '') {
|
|
415
|
+
// Ctrl+D:退出(空输入时)
|
|
416
|
+
handleCtrlC();
|
|
417
|
+
}
|
|
418
|
+
else if (char) {
|
|
419
|
+
currentInput += char;
|
|
420
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
421
|
+
}
|
|
422
|
+
break;
|
|
423
|
+
case '/':
|
|
424
|
+
// Issue #30 fix: 只在输入为空时触发命令面板(即 `/` 是第一个字符)
|
|
425
|
+
// 避免在 URL(http://)、路径(src/)、正则等场景误触发
|
|
426
|
+
if (currentInput === '') {
|
|
427
|
+
currentInput = '/';
|
|
428
|
+
(0, command_panel_1.showCommandPanel)('');
|
|
429
|
+
// 命令面板已渲染,光标已在正确位置
|
|
430
|
+
// 重置渲染长度并写入 `/` 符号
|
|
431
|
+
(0, command_panel_1.resetRenderLength)();
|
|
432
|
+
process.stdout.write('/');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// 正常添加 `/` 到输入
|
|
436
|
+
currentInput += '/';
|
|
437
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
case '@':
|
|
441
|
+
// 显示文件补全
|
|
442
|
+
const baseInput = currentInput;
|
|
443
|
+
currentInput += '@';
|
|
444
|
+
(0, file_completion_1.showFileCompletion)('', baseInput);
|
|
445
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
446
|
+
break;
|
|
447
|
+
default:
|
|
448
|
+
// 普通字符
|
|
449
|
+
if (char && char.length === 1 && !k.ctrl && !k.meta) {
|
|
450
|
+
currentInput += char;
|
|
451
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function updateHistorySearch() {
|
|
456
|
+
if (searchQuery) {
|
|
457
|
+
const allHistory = (0, global_config_1.getInputHistory)();
|
|
458
|
+
const matches = allHistory.filter(h => h.content.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
459
|
+
historyIndex = 0;
|
|
460
|
+
currentInput = matches[0]?.content || '';
|
|
461
|
+
// Don't replace inputHistory — just display filtered results
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
historyIndex = -1;
|
|
465
|
+
currentInput = '';
|
|
466
|
+
}
|
|
467
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput, `[Search: ${searchQuery}]`);
|
|
468
|
+
}
|
|
469
|
+
function showHistorySearchPrompt() {
|
|
470
|
+
process.stdout.write('\r\x1b[2K');
|
|
471
|
+
process.stdout.write(ACCENT('❯ ') + DIM('[Search: ]'));
|
|
472
|
+
}
|
|
473
|
+
async function handleCtrlC() {
|
|
474
|
+
// 保存会话摘要后退出
|
|
475
|
+
if (currentSession) {
|
|
476
|
+
const messages = (0, session_storage_1.readSessionMessages)(currentSession.id);
|
|
477
|
+
if (messages.length > 0) {
|
|
478
|
+
(0, session_storage_1.updateSessionSummary)(currentSession.id, messages);
|
|
479
|
+
}
|
|
480
|
+
(0, session_storage_1.endSession)(currentSession.id);
|
|
481
|
+
}
|
|
482
|
+
// 关闭 stdin raw mode 并退出
|
|
483
|
+
if (process.stdin.isTTY) {
|
|
484
|
+
process.stdin.setRawMode(false);
|
|
485
|
+
}
|
|
486
|
+
process.stdin.pause();
|
|
487
|
+
console.log();
|
|
488
|
+
console.log(DIM('Shutting down...'));
|
|
489
|
+
mcp_1.mcpManager.disconnectAll();
|
|
490
|
+
await runtime.shutdown();
|
|
491
|
+
console.log(SUCCESS('Goodbye! 🐴'));
|
|
492
|
+
process.exit(0);
|
|
493
|
+
}
|
|
494
|
+
// ============================================================================
|
|
495
|
+
// Banner
|
|
496
|
+
// ============================================================================
|
|
497
|
+
function showBanner() {
|
|
498
|
+
const config = store.getSnapshot().config;
|
|
499
|
+
const baseUrl = config.apiBaseUrl || '';
|
|
500
|
+
console.log();
|
|
501
|
+
console.log((0, box_1.renderHeaderBox)({
|
|
502
|
+
provider: baseUrl.includes('anthropic') ? 'Anthropic'
|
|
503
|
+
: baseUrl.includes('openai') ? 'OpenAI'
|
|
504
|
+
: baseUrl.includes('dashscope') ? 'Alibaba Cloud'
|
|
505
|
+
: 'Custom',
|
|
506
|
+
model: config.model,
|
|
507
|
+
endpoint: baseUrl,
|
|
508
|
+
status: llm ? 'ready' : 'loading',
|
|
509
|
+
statusText: llm ? undefined : 'Set OPENHORSE_API_KEY in .env',
|
|
510
|
+
version: VERSION,
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
// ============================================================================
|
|
514
|
+
// 输入处理
|
|
515
|
+
// ============================================================================
|
|
516
|
+
function getPrompt() {
|
|
517
|
+
const mode = store.getSnapshot().permissionMode;
|
|
518
|
+
const modeText = (0, types_1.getModeDisplayText)(mode);
|
|
519
|
+
const modeIndicator = modeText ? `[${modeText}] ` : '';
|
|
520
|
+
return ACCENT('❯ ') + DIM(modeIndicator);
|
|
521
|
+
}
|
|
522
|
+
async function handleInput(input) {
|
|
523
|
+
const text = input.trim();
|
|
524
|
+
if (!text)
|
|
525
|
+
return;
|
|
526
|
+
// 不在这里打印输入,readline 已经显示了
|
|
527
|
+
// 直接交给 executeChat 处理,它有自己的 spinner 和流式输出
|
|
528
|
+
const ctx = {
|
|
529
|
+
cwd: process.cwd(),
|
|
530
|
+
config: store.getSnapshot().config,
|
|
531
|
+
store,
|
|
532
|
+
llm,
|
|
533
|
+
runtime,
|
|
534
|
+
sessionId: currentSession?.id,
|
|
535
|
+
};
|
|
536
|
+
try {
|
|
537
|
+
const parsed = (0, parser_1.parseInput)(text);
|
|
538
|
+
if (parsed.isCommand) {
|
|
539
|
+
const cmd = (0, commands_1.findCommand)(parsed.name);
|
|
540
|
+
if (cmd) {
|
|
541
|
+
const result = await cmd.execute(ctx, parsed.args);
|
|
542
|
+
// executeChat 会被 cmd.execute 调用,如果需要
|
|
543
|
+
if (!result.continueAsChat) {
|
|
544
|
+
// 命令完成后的输出已经在 cmd.execute 中处理
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
console.log();
|
|
549
|
+
console.log(ERROR(`Unknown command: /${parsed.name}`));
|
|
550
|
+
const suggestions = (0, parser_1.buildCommandSuggestions)(parsed.name);
|
|
551
|
+
if (suggestions.length > 0) {
|
|
552
|
+
console.log(DIM(`Did you mean: ${suggestions.map(s => `/${s}`).join(', ')}?`));
|
|
553
|
+
}
|
|
554
|
+
console.log();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// 直接 chat - executeChat 有自己的 spinner 和流式输出
|
|
559
|
+
await (0, commands_1.executeChat)(ctx, text);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
console.log(ERROR(`Error: ${err.message || String(err)}`));
|
|
564
|
+
}
|
|
565
|
+
// 显示状态栏
|
|
566
|
+
updateStatusBar();
|
|
567
|
+
// 重新显示 prompt
|
|
568
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 更新状态栏显示
|
|
572
|
+
*/
|
|
573
|
+
function updateStatusBar() {
|
|
574
|
+
const snapshot = store.getSnapshot();
|
|
575
|
+
const usage = snapshot.tokenUsage;
|
|
576
|
+
const costStats = snapshot.costTracker.getSessionStats();
|
|
577
|
+
const mcpStatus = mcp_1.mcpManager.getStatus();
|
|
578
|
+
const stats = {
|
|
579
|
+
model: snapshot.currentModel,
|
|
580
|
+
tokens: usage ? usage.promptTokens + usage.completionTokens : 0,
|
|
581
|
+
promptTokens: usage?.promptTokens || 0,
|
|
582
|
+
completionTokens: usage?.completionTokens || 0,
|
|
583
|
+
cost: costStats.totalCost,
|
|
584
|
+
ctxPercent: Math.round((snapshot.conversationHistory.length / 50) * 100), // 假设 50 条上限
|
|
585
|
+
mcpConnected: mcpStatus.filter(s => s.connected).length,
|
|
586
|
+
mcpTotal: mcpStatus.length,
|
|
587
|
+
};
|
|
588
|
+
// 在 prompt 上一行显示状态栏
|
|
589
|
+
console.log((0, status_bar_1.renderStatusBar)(stats));
|
|
590
|
+
}
|
|
591
|
+
// 接口变量(用于兼容性,主要逻辑通过 keypress 处理)
|
|
592
|
+
let rl = null;
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// 主入口
|
|
595
|
+
// ============================================================================
|
|
596
|
+
async function main() {
|
|
597
|
+
// Parse command line arguments
|
|
598
|
+
const args = process.argv.slice(2);
|
|
599
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
600
|
+
showCliHelp();
|
|
601
|
+
process.exit(0);
|
|
602
|
+
}
|
|
603
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
604
|
+
console.log(`openhorse v${VERSION}`);
|
|
605
|
+
process.exit(0);
|
|
606
|
+
}
|
|
607
|
+
(0, config_dir_1.ensureConfigDir)();
|
|
608
|
+
(0, global_config_1.recordFirstStartTime)();
|
|
609
|
+
const projectPath = process.cwd();
|
|
610
|
+
const cliConfig = (0, config_1.loadConfig)();
|
|
611
|
+
// Load project memory
|
|
612
|
+
const memories = (0, storage_1.loadAllMemories)(projectPath);
|
|
613
|
+
const memoryContent = memories.length > 0
|
|
614
|
+
? memories.map(m => `## ${m.name} (${m.type})\n${m.content}`).join('\n\n')
|
|
615
|
+
: '';
|
|
616
|
+
// Load skills (builtin + user + project) and render the prompt section
|
|
617
|
+
let skillsContent = '';
|
|
618
|
+
try {
|
|
619
|
+
const registry = (0, skills_1.getSkillsRegistry)();
|
|
620
|
+
skillsContent = registry.generateSystemPromptInjection();
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
console.error(WARN(`⚠ Skills load error: ${err.message}`));
|
|
624
|
+
}
|
|
625
|
+
store = new framework_1.Store({
|
|
626
|
+
config: cliConfig,
|
|
627
|
+
tools: tools_1.TOOLS,
|
|
628
|
+
currentModel: cliConfig.model,
|
|
629
|
+
memoryContent,
|
|
630
|
+
skillsContent,
|
|
631
|
+
});
|
|
632
|
+
// Mirror tool-state (todos/plan) into Store so the UI can observe it
|
|
633
|
+
(0, framework_1.resetToolState)();
|
|
634
|
+
(0, framework_1.subscribeToolState)((s) => {
|
|
635
|
+
store.setState({
|
|
636
|
+
todos: s.todos,
|
|
637
|
+
planMode: s.planMode,
|
|
638
|
+
currentPlan: s.currentPlan,
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
currentSession = (0, session_storage_1.createSession)(projectPath, cliConfig.model);
|
|
642
|
+
(0, global_config_1.incrementSessionCount)();
|
|
643
|
+
if ((0, config_1.isConfigured)(cliConfig)) {
|
|
644
|
+
try {
|
|
645
|
+
llm = new llm_1.LLMService({
|
|
646
|
+
apiKey: cliConfig.apiKey,
|
|
647
|
+
baseUrl: cliConfig.apiBaseUrl,
|
|
648
|
+
model: cliConfig.model,
|
|
649
|
+
fallbackModel: cliConfig.fallbackModel,
|
|
650
|
+
maxTokens: cliConfig.maxTokens,
|
|
651
|
+
temperature: cliConfig.temperature,
|
|
652
|
+
maxRetries: cliConfig.maxRetries,
|
|
653
|
+
retryBaseDelay: cliConfig.retryBaseDelay,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
catch (err) {
|
|
657
|
+
console.log(WARN(`⚠ LLM initialization warning: ${err.message}`));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const config = store.getSnapshot().config;
|
|
661
|
+
runtime = await (0, init_1.init)({
|
|
662
|
+
name: config.name,
|
|
663
|
+
mode: config.mode,
|
|
664
|
+
logLevel: config.logLevel,
|
|
665
|
+
});
|
|
666
|
+
await runtime.start();
|
|
667
|
+
// Auto-connect MCP servers from ~/.openhorse/mcp.json (non-blocking)
|
|
668
|
+
mcp_1.mcpManager.connectAll().catch(err => {
|
|
669
|
+
console.error(WARN(`⚠ MCP startup error: ${err.message}`));
|
|
670
|
+
});
|
|
671
|
+
// Banner
|
|
672
|
+
showBanner();
|
|
673
|
+
// 提示
|
|
674
|
+
console.log(SUCCESS('✔ System initialized'));
|
|
675
|
+
console.log(DIM(' Type /help for commands, /exit to quit'));
|
|
676
|
+
if (!(0, config_1.isConfigured)(cliConfig)) {
|
|
677
|
+
console.log(WARN(' ⚠ LLM not configured — set OPENHORSE_API_KEY'));
|
|
678
|
+
}
|
|
679
|
+
console.log();
|
|
680
|
+
// 加载输入历史
|
|
681
|
+
inputHistory = (0, global_config_1.getInputHistory)();
|
|
682
|
+
// 启用 keypress 事件处理
|
|
683
|
+
// 使用 emitKeypressEvents + setRawMode 实现交互式功能
|
|
684
|
+
readline_1.default.emitKeypressEvents(process.stdin);
|
|
685
|
+
if (process.stdin.isTTY) {
|
|
686
|
+
try {
|
|
687
|
+
process.stdin.setRawMode(true);
|
|
688
|
+
}
|
|
689
|
+
catch (err) {
|
|
690
|
+
console.error(ERROR(`setRawMode failed: ${err.message}`));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
console.log(WARN('⚠ stdin is not TTY - interactive features disabled'));
|
|
695
|
+
}
|
|
696
|
+
process.stdin.resume();
|
|
697
|
+
// Force chalk to use full 24-bit color support.
|
|
698
|
+
// chalk.level is evaluated at module load time (before raw mode),
|
|
699
|
+
// so it defaults to 0. Set to 3 after raw mode is enabled.
|
|
700
|
+
chalk_1.default.level = 3;
|
|
701
|
+
// 监听 keypress 事件
|
|
702
|
+
process.stdin.on('keypress', (char, key) => {
|
|
703
|
+
try {
|
|
704
|
+
handleKeypress(char, key);
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
console.error(ERROR(`Keypress error: ${err.message}`));
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
// 初始 prompt
|
|
711
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
712
|
+
}
|
|
713
|
+
main().catch(err => {
|
|
714
|
+
console.error(ERROR('[OpenHorse] Fatal error:'), err);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
});
|
|
717
|
+
//# sourceMappingURL=cli.js.map
|