openhorse 0.1.23 → 0.2.0
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.d.ts +1 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +125 -1136
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/ink-ui/App.d.ts +6 -0
- package/dist/ink-ui/App.d.ts.map +1 -0
- package/dist/ink-ui/App.js +12 -0
- package/dist/ink-ui/App.js.map +1 -0
- package/dist/ink-ui/components/Markdown.d.ts +9 -0
- package/dist/ink-ui/components/Markdown.d.ts.map +1 -0
- package/dist/ink-ui/components/Markdown.js +188 -0
- package/dist/ink-ui/components/Markdown.js.map +1 -0
- package/dist/ink-ui/components/PixelHorseBanner.d.ts +8 -0
- package/dist/ink-ui/components/PixelHorseBanner.d.ts.map +1 -0
- package/dist/ink-ui/components/PixelHorseBanner.js +97 -0
- package/dist/ink-ui/components/PixelHorseBanner.js.map +1 -0
- package/dist/ink-ui/components/PromptInput.d.ts +9 -0
- package/dist/ink-ui/components/PromptInput.d.ts.map +1 -0
- package/dist/ink-ui/components/PromptInput.js +24 -0
- package/dist/ink-ui/components/PromptInput.js.map +1 -0
- package/dist/ink-ui/components/SelectList.d.ts +15 -0
- package/dist/ink-ui/components/SelectList.d.ts.map +1 -0
- package/dist/ink-ui/components/SelectList.js +39 -0
- package/dist/ink-ui/components/SelectList.js.map +1 -0
- package/dist/ink-ui/components/StatusLine.d.ts +9 -0
- package/dist/ink-ui/components/StatusLine.d.ts.map +1 -0
- package/dist/ink-ui/components/StatusLine.js +71 -0
- package/dist/ink-ui/components/StatusLine.js.map +1 -0
- package/dist/ink-ui/components/TerminalCursor.d.ts +13 -0
- package/dist/ink-ui/components/TerminalCursor.d.ts.map +1 -0
- package/dist/ink-ui/components/TerminalCursor.js +74 -0
- package/dist/ink-ui/components/TerminalCursor.js.map +1 -0
- package/dist/ink-ui/components/Transcript.d.ts +8 -0
- package/dist/ink-ui/components/Transcript.d.ts.map +1 -0
- package/dist/ink-ui/components/Transcript.js +29 -0
- package/dist/ink-ui/components/Transcript.js.map +1 -0
- package/dist/ink-ui/controllers/chat-controller.d.ts +15 -0
- package/dist/ink-ui/controllers/chat-controller.d.ts.map +1 -0
- package/dist/ink-ui/controllers/chat-controller.js +410 -0
- package/dist/ink-ui/controllers/chat-controller.js.map +1 -0
- package/dist/ink-ui/launch.d.ts +3 -0
- package/dist/ink-ui/launch.d.ts.map +1 -0
- package/dist/ink-ui/launch.js +14 -0
- package/dist/ink-ui/launch.js.map +1 -0
- package/dist/ink-ui/runtime/prompt-layout.d.ts +11 -0
- package/dist/ink-ui/runtime/prompt-layout.d.ts.map +1 -0
- package/dist/ink-ui/runtime/prompt-layout.js +51 -0
- package/dist/ink-ui/runtime/prompt-layout.js.map +1 -0
- package/dist/ink-ui/screens/ReplScreen.d.ts +14 -0
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -0
- package/dist/ink-ui/screens/ReplScreen.js +470 -0
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -0
- package/dist/ink-ui/types.d.ts +43 -0
- package/dist/ink-ui/types.d.ts.map +1 -0
- package/dist/ink-ui/types.js +3 -0
- package/dist/ink-ui/types.js.map +1 -0
- package/dist/services/config.js +2 -2
- package/dist/services/config.js.map +1 -1
- package/dist/services/global-config.d.ts +1 -1
- package/dist/services/global-config.d.ts.map +1 -1
- package/package.json +5 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* openhorse -
|
|
4
|
-
*
|
|
5
|
-
* 简洁版 REPL,使用 readline + keypress 事件处理
|
|
3
|
+
* openhorse - Ink/React CLI entry
|
|
6
4
|
*/
|
|
7
5
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
6
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -10,79 +8,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
9
|
require("dotenv/config");
|
|
12
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
-
const readline_1 = __importDefault(require("readline"));
|
|
14
11
|
const fs_1 = require("fs");
|
|
15
12
|
const path_1 = require("path");
|
|
16
13
|
const init_1 = require("./init");
|
|
17
14
|
const llm_1 = require("./services/llm");
|
|
18
|
-
const tools_1 = require("./tools");
|
|
19
|
-
const mcp_1 = require("./tools/mcp");
|
|
20
15
|
const config_1 = require("./services/config");
|
|
21
16
|
const config_dir_1 = require("./services/config-dir");
|
|
22
17
|
const global_config_1 = require("./services/global-config");
|
|
23
|
-
const model_context_1 = require("./services/model-context");
|
|
24
18
|
const session_storage_1 = require("./services/session-storage");
|
|
25
19
|
const storage_1 = require("./memory/storage");
|
|
26
20
|
const skills_1 = require("./skills");
|
|
27
21
|
const framework_1 = require("./framework");
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const ui_v2_1 = require("./ui-v2");
|
|
38
|
-
const turn_controller_1 = require("./runtime/turn-controller");
|
|
39
|
-
// Get version from package.json
|
|
22
|
+
const tools_1 = require("./tools");
|
|
23
|
+
const mcp_1 = require("./tools/mcp");
|
|
24
|
+
const model_context_1 = require("./services/model-context");
|
|
25
|
+
const launch_1 = require("./ink-ui/launch");
|
|
26
|
+
const BRAND = chalk_1.default.hex('#FF6B35');
|
|
27
|
+
const ACCENT = chalk_1.default.hex('#00D4AA');
|
|
28
|
+
const DIM = chalk_1.default.dim;
|
|
29
|
+
const ERROR = chalk_1.default.red;
|
|
30
|
+
const WARN = chalk_1.default.yellow;
|
|
40
31
|
const VERSION = (() => {
|
|
41
32
|
try {
|
|
42
33
|
const pkgPath = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
43
34
|
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
44
|
-
return pkg.version || '0.
|
|
35
|
+
return pkg.version || '0.2.0';
|
|
45
36
|
}
|
|
46
37
|
catch {
|
|
47
|
-
return '0.
|
|
38
|
+
return '0.2.0';
|
|
48
39
|
}
|
|
49
40
|
})();
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// 颜色常量
|
|
52
|
-
// ============================================================================
|
|
53
|
-
const BRAND = chalk_1.default.hex('#FF6B35');
|
|
54
|
-
const ACCENT = chalk_1.default.hex('#00D4AA');
|
|
55
|
-
const DIM = chalk_1.default.dim;
|
|
56
|
-
const ERROR = chalk_1.default.red;
|
|
57
|
-
const WARN = chalk_1.default.yellow;
|
|
58
|
-
const SUCCESS = chalk_1.default.green;
|
|
59
|
-
// ============================================================================
|
|
60
|
-
// CLI Help
|
|
61
|
-
// ============================================================================
|
|
62
41
|
function showCliHelp() {
|
|
63
42
|
console.log();
|
|
64
43
|
console.log(BRAND('openhorse') + DIM(` v${VERSION}`));
|
|
65
44
|
console.log(DIM(' Universal Agent Harness Framework'));
|
|
66
45
|
console.log();
|
|
67
46
|
console.log(ACCENT('Usage:'));
|
|
68
|
-
console.log(' openhorse
|
|
69
|
-
console.log(' openhorse --help
|
|
70
|
-
console.log(' openhorse --version
|
|
71
|
-
console.log(' openhorse --ui
|
|
47
|
+
console.log(' openhorse Start the Ink/React interactive UI');
|
|
48
|
+
console.log(' openhorse --help Show this help message');
|
|
49
|
+
console.log(' openhorse --version Show version');
|
|
50
|
+
console.log(' openhorse --ui ink Start the Ink UI explicitly');
|
|
72
51
|
console.log();
|
|
73
52
|
console.log(ACCENT('Options:'));
|
|
74
53
|
console.log(' -h, --help Show help');
|
|
75
54
|
console.log(' -v, --version Show version');
|
|
76
|
-
console.log(' --ui <mode> UI renderer:
|
|
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');
|
|
55
|
+
console.log(' --ui <mode> UI renderer: ink');
|
|
84
56
|
console.log();
|
|
85
|
-
console.log(DIM('
|
|
57
|
+
console.log(DIM('Legacy readline/v2 renderers were removed in v0.2.0.'));
|
|
86
58
|
console.log();
|
|
87
59
|
}
|
|
88
60
|
function parseCliUIRenderer(args) {
|
|
@@ -95,1131 +67,148 @@ function parseCliUIRenderer(args) {
|
|
|
95
67
|
: undefined;
|
|
96
68
|
if (value === undefined)
|
|
97
69
|
continue;
|
|
98
|
-
if (value === '
|
|
99
|
-
return
|
|
70
|
+
if (value === 'ink')
|
|
71
|
+
return 'ink';
|
|
72
|
+
if (value === 'legacy' || value === 'v2') {
|
|
73
|
+
console.log(WARN(`Renderer "${value}" was removed in v0.2.0; starting Ink UI instead.`));
|
|
74
|
+
return 'ink';
|
|
75
|
+
}
|
|
100
76
|
console.error(ERROR(`Invalid --ui value: ${value}`));
|
|
101
|
-
console.error(DIM('Expected
|
|
77
|
+
console.error(DIM('Expected: ink'));
|
|
102
78
|
process.exit(1);
|
|
103
79
|
}
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// 全局状态
|
|
108
|
-
// ============================================================================
|
|
109
|
-
let llm = null;
|
|
110
|
-
let store;
|
|
111
|
-
let currentSession = null;
|
|
112
|
-
let runtime;
|
|
113
|
-
const turnController = new turn_controller_1.TurnController();
|
|
114
|
-
let isShuttingDown = false;
|
|
115
|
-
// 输入状态
|
|
116
|
-
let currentInput = '';
|
|
117
|
-
let inputHistory = [];
|
|
118
|
-
let historyIndex = -1;
|
|
119
|
-
let historyMode = 'none';
|
|
120
|
-
let searchQuery = '';
|
|
121
|
-
let resumePickerState = null;
|
|
122
|
-
let resumePickerLines = [];
|
|
123
|
-
let resumePickerReservedHeight = 0;
|
|
124
|
-
const DEFAULT_RESUME_PICKER_VISIBLE_ITEMS = 10;
|
|
125
|
-
let shortcutsOverlayVisible = false;
|
|
126
|
-
let shortcutsOverlayLines = [];
|
|
127
|
-
let shortcutsOverlayReservedHeight = 0;
|
|
128
|
-
function ensureCurrentSession() {
|
|
129
|
-
if (!currentSession) {
|
|
130
|
-
currentSession = (0, session_storage_1.createSession)(process.cwd(), store.getSnapshot().currentModel || store.getSnapshot().config.model);
|
|
131
|
-
(0, global_config_1.incrementSessionCount)();
|
|
132
|
-
}
|
|
133
|
-
return currentSession;
|
|
134
|
-
}
|
|
135
|
-
function setCurrentSession(session) {
|
|
136
|
-
currentSession = session;
|
|
137
|
-
}
|
|
138
|
-
function getCurrentSession() {
|
|
139
|
-
return currentSession;
|
|
140
|
-
}
|
|
141
|
-
function echoSubmittedInput(input) {
|
|
142
|
-
(0, command_panel_1.clearRenderedInput)();
|
|
143
|
-
const rendered = isV2UI()
|
|
144
|
-
? (0, user_input_1.renderUserInputEchoFrame)(input)
|
|
145
|
-
: (0, user_input_1.renderUserInputEcho)(input);
|
|
146
|
-
(0, command_panel_1.writeLinePreservingInput)(rendered);
|
|
147
|
-
if (isV2UI()) {
|
|
148
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function isExitCommandInput(input) {
|
|
152
|
-
const parsed = (0, parser_1.parseInput)(input.trim());
|
|
153
|
-
if (!parsed.isCommand)
|
|
154
|
-
return false;
|
|
155
|
-
const cmd = (0, commands_1.findCommand)(parsed.name);
|
|
156
|
-
return cmd?.name === 'exit';
|
|
157
|
-
}
|
|
158
|
-
function submitInput(input) {
|
|
159
|
-
const submittedInput = input;
|
|
160
|
-
currentInput = '';
|
|
161
|
-
echoSubmittedInput(submittedInput);
|
|
162
|
-
(0, global_config_1.addToInputHistory)(submittedInput);
|
|
163
|
-
inputHistory = (0, global_config_1.getInputHistory)();
|
|
164
|
-
if (turnController.hasActiveTurn()) {
|
|
165
|
-
const text = submittedInput.trim();
|
|
166
|
-
const parsed = (0, parser_1.parseInput)(text);
|
|
167
|
-
if (isExitCommandInput(text)) {
|
|
168
|
-
void shutdownCli();
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (parsed.isCommand) {
|
|
172
|
-
console.log(DIM('Command ignored while agent is running. Press Ctrl+C to interrupt first.'));
|
|
173
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (text) {
|
|
177
|
-
turnController.clearExitIntent();
|
|
178
|
-
turnController.requestRevision(submittedInput);
|
|
179
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
void runInputTurn(submittedInput);
|
|
184
|
-
}
|
|
185
|
-
async function runInputTurn(input) {
|
|
186
|
-
let nextInput = input;
|
|
187
|
-
while (nextInput) {
|
|
188
|
-
const turn = turnController.beginTurn(nextInput);
|
|
189
|
-
store.setProcessing(true);
|
|
190
|
-
try {
|
|
191
|
-
await handleInput(nextInput, turn.abortSignal, { redrawPrompt: false, updateStatus: false });
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
console.log(ERROR(`Input error: ${err.message || String(err)}`));
|
|
195
|
-
}
|
|
196
|
-
finally {
|
|
197
|
-
const revision = turnController.finishTurn(turn.id);
|
|
198
|
-
store.setProcessing(false);
|
|
199
|
-
if (revision && revision.trim()) {
|
|
200
|
-
(0, command_panel_1.writeLinePreservingInput)(DIM('Interrupted. Restarting with latest instruction...'));
|
|
201
|
-
nextInput = revision;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
nextInput = undefined;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
updateStatusBar();
|
|
209
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
210
|
-
}
|
|
211
|
-
function parseKey(char, key) {
|
|
212
|
-
const sequence = key?.sequence || char || '';
|
|
213
|
-
if (sequence === '\x1b[13;2u') {
|
|
214
|
-
return { name: 'enter', ctrl: false, shift: true, meta: false, sequence };
|
|
215
|
-
}
|
|
216
|
-
if (sequence === '\x1b[13;3u') {
|
|
217
|
-
return { name: 'enter', ctrl: false, shift: false, meta: true, sequence };
|
|
218
|
-
}
|
|
219
|
-
if (!key) {
|
|
220
|
-
// 手动解析
|
|
221
|
-
if (char === '\r' || char === '\n')
|
|
222
|
-
return { name: 'enter', ctrl: false, shift: false, meta: false, sequence: char || '' };
|
|
223
|
-
if (char === '\x1b')
|
|
224
|
-
return { name: 'escape', ctrl: false, shift: false, meta: false, sequence: char };
|
|
225
|
-
if (char === '\t')
|
|
226
|
-
return { name: 'tab', ctrl: false, shift: false, meta: false, sequence: char };
|
|
227
|
-
if (char === '\x7f' || char === '\b')
|
|
228
|
-
return { name: 'backspace', ctrl: false, shift: false, meta: false, sequence: char };
|
|
229
|
-
return { name: char || '', ctrl: false, shift: false, meta: false, sequence: char || '' };
|
|
230
|
-
}
|
|
231
|
-
// 统一 "return" 和 "enter"
|
|
232
|
-
if (key.name === 'return') {
|
|
233
|
-
key.name = 'enter';
|
|
234
|
-
}
|
|
235
|
-
if (key.name === 'enter' && (key.shift || key.meta)) {
|
|
236
|
-
return key;
|
|
237
|
-
}
|
|
238
|
-
// 如果 key.name 为空,使用 char 作为 name
|
|
239
|
-
if (!key.name && char) {
|
|
240
|
-
key.name = char;
|
|
241
|
-
}
|
|
242
|
-
return key;
|
|
243
|
-
}
|
|
244
|
-
function handleKeypress(char, key) {
|
|
245
|
-
const k = parseKey(char, key);
|
|
246
|
-
if (k.ctrl && k.name === 'l') {
|
|
247
|
-
clearTerminalView();
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (k.ctrl && k.name === 'c') {
|
|
251
|
-
void handleCtrlC();
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
// 命令面板模式
|
|
255
|
-
if ((0, command_panel_1.isPanelVisible)()) {
|
|
256
|
-
handlePanelKeypress(k, char);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
// 文件补全模式
|
|
260
|
-
if ((0, file_completion_1.isFileCompletionVisible)()) {
|
|
261
|
-
handleFileCompletionKeypress(k, char);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
// Resume session picker mode
|
|
265
|
-
if (resumePickerState?.visible) {
|
|
266
|
-
handleResumePickerKeypress(k, char);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
if (shortcutsOverlayVisible) {
|
|
270
|
-
handleShortcutsOverlayKeypress(k, char);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
// 历史导航模式
|
|
274
|
-
if (historyMode !== 'none') {
|
|
275
|
-
handleHistoryKeypress(k, char);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
// 正常输入模式
|
|
279
|
-
handleNormalKeypress(k, char);
|
|
280
|
-
}
|
|
281
|
-
function handlePanelKeypress(k, char) {
|
|
282
|
-
switch (k.name) {
|
|
283
|
-
case 'up':
|
|
284
|
-
(0, command_panel_1.navigatePanel)('up');
|
|
285
|
-
break;
|
|
286
|
-
case 'down':
|
|
287
|
-
(0, command_panel_1.navigatePanel)('down');
|
|
288
|
-
break;
|
|
289
|
-
case 'tab':
|
|
290
|
-
const completedCmd = (0, command_panel_1.completeSelectedCommand)();
|
|
291
|
-
if (completedCmd) {
|
|
292
|
-
currentInput = completedCmd;
|
|
293
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
case 'enter':
|
|
297
|
-
const cmd = (0, command_panel_1.selectCommand)();
|
|
298
|
-
if (cmd) {
|
|
299
|
-
submitInput(cmd);
|
|
300
|
-
(0, command_panel_1.clearPendingCommand)();
|
|
301
|
-
}
|
|
302
|
-
break;
|
|
303
|
-
case 'escape':
|
|
304
|
-
(0, command_panel_1.hideCommandPanel)();
|
|
305
|
-
currentInput = '';
|
|
306
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
307
|
-
break;
|
|
308
|
-
case 'backspace':
|
|
309
|
-
currentInput = currentInput.slice(0, -1);
|
|
310
|
-
if (currentInput.length <= 1) {
|
|
311
|
-
(0, command_panel_1.hideCommandPanel)();
|
|
312
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
316
|
-
(0, command_panel_1.updatePanelFilter)(currentInput.slice(1));
|
|
317
|
-
}
|
|
318
|
-
break;
|
|
319
|
-
default:
|
|
320
|
-
// 添加字符到过滤
|
|
321
|
-
if (char && char.length === 1 && !k.ctrl) {
|
|
322
|
-
currentInput += char;
|
|
323
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
324
|
-
(0, command_panel_1.updatePanelFilter)(currentInput.slice(1));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
function handleHistoryKeypress(k, char) {
|
|
329
|
-
if (historyMode === 'search') {
|
|
330
|
-
// 搜索模式:输入搜索词
|
|
331
|
-
switch (k.name) {
|
|
332
|
-
case 'enter':
|
|
333
|
-
if (inputHistory.length > 0 && historyIndex >= 0) {
|
|
334
|
-
currentInput = inputHistory[historyIndex]?.content || '';
|
|
335
|
-
}
|
|
336
|
-
historyMode = 'none';
|
|
337
|
-
searchQuery = '';
|
|
338
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
339
|
-
break;
|
|
340
|
-
case 'escape':
|
|
341
|
-
historyMode = 'none';
|
|
342
|
-
searchQuery = '';
|
|
343
|
-
currentInput = '';
|
|
344
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
345
|
-
break;
|
|
346
|
-
case 'backspace':
|
|
347
|
-
searchQuery = searchQuery.slice(0, -1);
|
|
348
|
-
updateHistorySearch();
|
|
349
|
-
break;
|
|
350
|
-
default:
|
|
351
|
-
if (char && char.length === 1 && !k.ctrl) {
|
|
352
|
-
searchQuery += char;
|
|
353
|
-
updateHistorySearch();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
// 导航模式
|
|
359
|
-
switch (k.name) {
|
|
360
|
-
case 'up':
|
|
361
|
-
historyIndex = Math.min(inputHistory.length - 1, historyIndex + 1);
|
|
362
|
-
currentInput = inputHistory[historyIndex]?.content || '';
|
|
363
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
364
|
-
break;
|
|
365
|
-
case 'down':
|
|
366
|
-
historyIndex = Math.max(-1, historyIndex - 1);
|
|
367
|
-
if (historyIndex < 0) {
|
|
368
|
-
currentInput = '';
|
|
369
|
-
historyMode = 'none';
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
currentInput = inputHistory[historyIndex]?.content || '';
|
|
373
|
-
}
|
|
374
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
375
|
-
break;
|
|
376
|
-
case 'enter':
|
|
377
|
-
historyMode = 'none';
|
|
378
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
379
|
-
break;
|
|
380
|
-
case 'escape':
|
|
381
|
-
historyMode = 'none';
|
|
382
|
-
currentInput = '';
|
|
383
|
-
historyIndex = -1;
|
|
384
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
function handleFileCompletionKeypress(k, char) {
|
|
390
|
-
switch (k.name) {
|
|
391
|
-
case 'up':
|
|
392
|
-
(0, file_completion_1.navigateFiles)('up');
|
|
393
|
-
break;
|
|
394
|
-
case 'down':
|
|
395
|
-
(0, file_completion_1.navigateFiles)('down');
|
|
396
|
-
break;
|
|
397
|
-
case 'tab':
|
|
398
|
-
const completedPath = (0, file_completion_1.completeFile)();
|
|
399
|
-
if (completedPath) {
|
|
400
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + completedPath;
|
|
401
|
-
(0, file_completion_1.hideFileCompletion)();
|
|
402
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
// 目录:继续显示面板,更新 query
|
|
406
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
407
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
408
|
-
}
|
|
409
|
-
break;
|
|
410
|
-
case 'enter':
|
|
411
|
-
const selectedPath = (0, file_completion_1.selectFile)();
|
|
412
|
-
if (selectedPath) {
|
|
413
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + selectedPath;
|
|
414
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
case 'escape':
|
|
418
|
-
(0, file_completion_1.hideFileCompletion)();
|
|
419
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
420
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
421
|
-
break;
|
|
422
|
-
case 'backspace':
|
|
423
|
-
const query = (0, file_completion_1.getFileQuery)();
|
|
424
|
-
if (query.length > 0) {
|
|
425
|
-
(0, file_completion_1.updateFileQuery)(query.slice(0, -1));
|
|
426
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
427
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
(0, file_completion_1.hideFileCompletion)();
|
|
431
|
-
currentInput = (0, file_completion_1.getBaseInput)();
|
|
432
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
433
|
-
}
|
|
434
|
-
break;
|
|
435
|
-
default:
|
|
436
|
-
// 添加字符到路径查询
|
|
437
|
-
if (char && char.length === 1 && !k.ctrl) {
|
|
438
|
-
const newQuery = (0, file_completion_1.getFileQuery)() + char;
|
|
439
|
-
(0, file_completion_1.updateFileQuery)(newQuery);
|
|
440
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + newQuery;
|
|
441
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function handleResumePickerKeypress(k, char) {
|
|
446
|
-
if (!resumePickerState?.visible)
|
|
447
|
-
return;
|
|
448
|
-
const pageSize = resumePickerState.maxVisibleItems ?? DEFAULT_RESUME_PICKER_VISIBLE_ITEMS;
|
|
449
|
-
switch (k.name) {
|
|
450
|
-
case 'up':
|
|
451
|
-
resumePickerState.selectedIndex = Math.max(0, resumePickerState.selectedIndex - 1);
|
|
452
|
-
renderResumePicker();
|
|
453
|
-
break;
|
|
454
|
-
case 'down':
|
|
455
|
-
resumePickerState.selectedIndex = Math.min(resumePickerState.sessions.length - 1, resumePickerState.selectedIndex + 1);
|
|
456
|
-
renderResumePicker();
|
|
457
|
-
break;
|
|
458
|
-
case 'pageup':
|
|
459
|
-
resumePickerState.selectedIndex = Math.max(0, resumePickerState.selectedIndex - pageSize);
|
|
460
|
-
renderResumePicker();
|
|
461
|
-
break;
|
|
462
|
-
case 'pagedown':
|
|
463
|
-
resumePickerState.selectedIndex = Math.min(resumePickerState.sessions.length - 1, resumePickerState.selectedIndex + pageSize);
|
|
464
|
-
renderResumePicker();
|
|
465
|
-
break;
|
|
466
|
-
case 'home':
|
|
467
|
-
resumePickerState.selectedIndex = 0;
|
|
468
|
-
renderResumePicker();
|
|
469
|
-
break;
|
|
470
|
-
case 'end':
|
|
471
|
-
resumePickerState.selectedIndex = Math.max(0, resumePickerState.sessions.length - 1);
|
|
472
|
-
renderResumePicker();
|
|
473
|
-
break;
|
|
474
|
-
case 'enter':
|
|
475
|
-
restoreSelectedResumeSession();
|
|
476
|
-
break;
|
|
477
|
-
case 'escape':
|
|
478
|
-
hideResumePicker();
|
|
479
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
480
|
-
break;
|
|
481
|
-
default:
|
|
482
|
-
if (char && /^[0-9]$/.test(char)) {
|
|
483
|
-
const visibleOffset = char === '0' ? 9 : Number(char) - 1;
|
|
484
|
-
const windowStart = (0, ui_v2_1.getSessionWindowStart)(resumePickerState.selectedIndex, resumePickerState.sessions.length, pageSize);
|
|
485
|
-
const index = windowStart + visibleOffset;
|
|
486
|
-
if (visibleOffset >= 0 && visibleOffset < pageSize && index < resumePickerState.sessions.length) {
|
|
487
|
-
resumePickerState.selectedIndex = index;
|
|
488
|
-
restoreSelectedResumeSession();
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
function showResumePicker(options) {
|
|
494
|
-
resumePickerState = {
|
|
495
|
-
visible: true,
|
|
496
|
-
sessions: options.sessions,
|
|
497
|
-
selectedIndex: 0,
|
|
498
|
-
title: options.title,
|
|
499
|
-
showProject: options.showProject,
|
|
500
|
-
moreCount: options.moreCount,
|
|
501
|
-
allProjects: options.allProjects,
|
|
502
|
-
maxVisibleItems: options.maxVisibleItems ?? DEFAULT_RESUME_PICKER_VISIBLE_ITEMS,
|
|
503
|
-
};
|
|
504
|
-
renderResumePicker();
|
|
505
|
-
}
|
|
506
|
-
function renderResumePicker() {
|
|
507
|
-
if (!resumePickerState?.visible)
|
|
508
|
-
return;
|
|
509
|
-
clearResumePicker({ release: false });
|
|
510
|
-
const lines = (0, ui_v2_1.renderSessionPicker)({
|
|
511
|
-
title: resumePickerState.title,
|
|
512
|
-
sessions: resumePickerState.sessions,
|
|
513
|
-
selectedIndex: resumePickerState.selectedIndex,
|
|
514
|
-
maxVisibleItems: resumePickerState.maxVisibleItems ?? DEFAULT_RESUME_PICKER_VISIBLE_ITEMS,
|
|
515
|
-
width: process.stdout.columns || 80,
|
|
516
|
-
showProject: resumePickerState.showProject,
|
|
517
|
-
moreCount: resumePickerState.moreCount,
|
|
518
|
-
footer: ' ↑↓ Scroll PgUp/PgDn Enter Resume 1-9/0 Quick Esc Cancel',
|
|
519
|
-
theme: {
|
|
520
|
-
accent: ACCENT,
|
|
521
|
-
dim: DIM,
|
|
522
|
-
selected: text => chalk_1.default.bgHex('#1E293B').hex('#E2E8F0')(text),
|
|
523
|
-
},
|
|
524
|
-
});
|
|
525
|
-
const offset = getResumePickerOffsetRows();
|
|
526
|
-
reserveResumePickerSpace(lines.length + offset);
|
|
527
|
-
resumePickerLines = lines;
|
|
528
|
-
process.stdout.write('\x1b7');
|
|
529
|
-
if (offset > 0) {
|
|
530
|
-
process.stdout.write(`\x1b[${offset}B\r`);
|
|
531
|
-
}
|
|
532
|
-
process.stdout.write('\x1b[J');
|
|
533
|
-
for (let index = 0; index < lines.length; index++) {
|
|
534
|
-
if (index > 0 || offset === 0) {
|
|
535
|
-
process.stdout.write('\n');
|
|
536
|
-
}
|
|
537
|
-
process.stdout.write('\r' + lines[index]);
|
|
538
|
-
}
|
|
539
|
-
process.stdout.write('\x1b8');
|
|
540
|
-
}
|
|
541
|
-
function hideResumePicker() {
|
|
542
|
-
clearResumePicker({ release: true });
|
|
543
|
-
resumePickerState = null;
|
|
544
|
-
}
|
|
545
|
-
function clearResumePicker(options = {}) {
|
|
546
|
-
const height = Math.max(resumePickerReservedHeight, resumePickerLines.length);
|
|
547
|
-
if (height <= 0)
|
|
548
|
-
return;
|
|
549
|
-
process.stdout.write('\x1b7');
|
|
550
|
-
const offset = getResumePickerOffsetRows();
|
|
551
|
-
if (offset > 0) {
|
|
552
|
-
process.stdout.write(`\x1b[${offset}B\r`);
|
|
553
|
-
}
|
|
554
|
-
process.stdout.write('\x1b[J');
|
|
555
|
-
process.stdout.write('\x1b8');
|
|
556
|
-
resumePickerLines = [];
|
|
557
|
-
if (options.release) {
|
|
558
|
-
resumePickerReservedHeight = 0;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function reserveResumePickerSpace(requiredHeight) {
|
|
562
|
-
if (requiredHeight <= resumePickerReservedHeight)
|
|
563
|
-
return;
|
|
564
|
-
const extraLines = requiredHeight - resumePickerReservedHeight;
|
|
565
|
-
process.stdout.write('\n'.repeat(extraLines));
|
|
566
|
-
process.stdout.write(`\x1b[${extraLines}A`);
|
|
567
|
-
resumePickerReservedHeight = requiredHeight;
|
|
568
|
-
}
|
|
569
|
-
function getResumePickerOffsetRows() {
|
|
570
|
-
return isV2UI() ? 2 : 1;
|
|
571
|
-
}
|
|
572
|
-
function restoreSelectedResumeSession() {
|
|
573
|
-
if (!resumePickerState?.visible)
|
|
574
|
-
return;
|
|
575
|
-
const session = resumePickerState.sessions[resumePickerState.selectedIndex];
|
|
576
|
-
const suffix = resumePickerState.allProjects ? ' --all' : '';
|
|
577
|
-
hideResumePicker();
|
|
578
|
-
currentInput = '';
|
|
579
|
-
(0, command_panel_1.clearRenderedInput)();
|
|
580
|
-
runInputTurn(`/resume ${session.id}${suffix}`).catch(err => {
|
|
581
|
-
console.log(ERROR(`Resume error: ${err.message || String(err)}`));
|
|
582
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
function handleShortcutsOverlayKeypress(k, char) {
|
|
586
|
-
if (k.name === 'escape' || k.name === 'enter' || k.name === '?') {
|
|
587
|
-
hideShortcutsOverlay();
|
|
588
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
if (char && char.length === 1 && !k.ctrl && !k.meta) {
|
|
592
|
-
hideShortcutsOverlay();
|
|
593
|
-
handleNormalKeypress(k, char);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
function toggleV2Shortcuts() {
|
|
597
|
-
if (shortcutsOverlayVisible) {
|
|
598
|
-
hideShortcutsOverlay();
|
|
599
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
showShortcutsOverlay();
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
function showShortcutsOverlay() {
|
|
606
|
-
if (!isV2UI())
|
|
607
|
-
return;
|
|
608
|
-
shortcutsOverlayVisible = true;
|
|
609
|
-
renderShortcutsOverlay();
|
|
610
|
-
}
|
|
611
|
-
function renderShortcutsOverlay() {
|
|
612
|
-
if (!shortcutsOverlayVisible)
|
|
613
|
-
return;
|
|
614
|
-
clearShortcutsOverlay({ release: false });
|
|
615
|
-
const lines = (0, ui_v2_1.renderV2Shortcuts)(process.stdout.columns || 80).split('\n');
|
|
616
|
-
const offset = getShortcutsOverlayOffsetRows();
|
|
617
|
-
reserveShortcutsOverlaySpace(lines.length + offset);
|
|
618
|
-
shortcutsOverlayLines = lines;
|
|
619
|
-
process.stdout.write('\x1b7');
|
|
620
|
-
if (offset > 0) {
|
|
621
|
-
process.stdout.write(`\x1b[${offset}B\r`);
|
|
622
|
-
}
|
|
623
|
-
process.stdout.write('\x1b[J');
|
|
624
|
-
for (let index = 0; index < lines.length; index++) {
|
|
625
|
-
if (index > 0 || offset === 0) {
|
|
626
|
-
process.stdout.write('\n');
|
|
627
|
-
}
|
|
628
|
-
process.stdout.write('\r' + lines[index]);
|
|
629
|
-
}
|
|
630
|
-
process.stdout.write('\x1b8');
|
|
631
|
-
}
|
|
632
|
-
function hideShortcutsOverlay() {
|
|
633
|
-
clearShortcutsOverlay({ release: true });
|
|
634
|
-
shortcutsOverlayVisible = false;
|
|
635
|
-
}
|
|
636
|
-
function clearShortcutsOverlay(options = {}) {
|
|
637
|
-
const height = Math.max(shortcutsOverlayReservedHeight, shortcutsOverlayLines.length);
|
|
638
|
-
if (height <= 0)
|
|
639
|
-
return;
|
|
640
|
-
process.stdout.write('\x1b7');
|
|
641
|
-
const offset = getShortcutsOverlayOffsetRows();
|
|
642
|
-
if (offset > 0) {
|
|
643
|
-
process.stdout.write(`\x1b[${offset}B\r`);
|
|
644
|
-
}
|
|
645
|
-
process.stdout.write('\x1b[J');
|
|
646
|
-
process.stdout.write('\x1b8');
|
|
647
|
-
shortcutsOverlayLines = [];
|
|
648
|
-
if (options.release) {
|
|
649
|
-
shortcutsOverlayReservedHeight = 0;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
function reserveShortcutsOverlaySpace(requiredHeight) {
|
|
653
|
-
if (requiredHeight <= shortcutsOverlayReservedHeight)
|
|
654
|
-
return;
|
|
655
|
-
const extraLines = requiredHeight - shortcutsOverlayReservedHeight;
|
|
656
|
-
process.stdout.write('\n'.repeat(extraLines));
|
|
657
|
-
process.stdout.write(`\x1b[${extraLines}A`);
|
|
658
|
-
shortcutsOverlayReservedHeight = requiredHeight;
|
|
659
|
-
}
|
|
660
|
-
function getShortcutsOverlayOffsetRows() {
|
|
661
|
-
return isV2UI() ? 2 : 1;
|
|
662
|
-
}
|
|
663
|
-
function handleNormalKeypress(k, char) {
|
|
664
|
-
switch (k.name) {
|
|
665
|
-
case 'enter':
|
|
666
|
-
if (k.shift || k.meta) {
|
|
667
|
-
if (isV2UI()) {
|
|
668
|
-
insertInputNewline();
|
|
669
|
-
}
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
// 多行模式:添加行
|
|
673
|
-
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
674
|
-
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
675
|
-
(0, multiline_input_1.addMultilineLine)(currentInput);
|
|
676
|
-
currentInput = '';
|
|
677
|
-
if (isV2UI()) {
|
|
678
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
process.stdout.write('\r\x1b[2K');
|
|
682
|
-
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
else {
|
|
686
|
-
// 结束多行,发送完整输入
|
|
687
|
-
(0, multiline_input_1.addMultilineLine)(currentInput);
|
|
688
|
-
const fullInput = (0, multiline_input_1.getMultilineInput)();
|
|
689
|
-
(0, multiline_input_1.resetMultiline)();
|
|
690
|
-
if (fullInput.trim()) {
|
|
691
|
-
submitInput(fullInput);
|
|
692
|
-
}
|
|
693
|
-
currentInput = '';
|
|
694
|
-
}
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
// 检查是否进入多行模式
|
|
698
|
-
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
699
|
-
(0, multiline_input_1.enterMultiline)(currentInput);
|
|
700
|
-
currentInput = '';
|
|
701
|
-
if (isV2UI()) {
|
|
702
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
process.stdout.write('\r\x1b[2K');
|
|
706
|
-
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
707
|
-
}
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
// 正常发送输入
|
|
711
|
-
if (currentInput.trim()) {
|
|
712
|
-
submitInput(currentInput);
|
|
713
|
-
}
|
|
714
|
-
break;
|
|
715
|
-
case 'backspace':
|
|
716
|
-
currentInput = currentInput.slice(0, -1);
|
|
717
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
718
|
-
break;
|
|
719
|
-
case 'up':
|
|
720
|
-
// 进入历史导航
|
|
721
|
-
if (currentInput === '' && inputHistory.length > 0) {
|
|
722
|
-
historyMode = 'navigate';
|
|
723
|
-
historyIndex = 0;
|
|
724
|
-
currentInput = inputHistory[0]?.content || '';
|
|
725
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
726
|
-
}
|
|
727
|
-
break;
|
|
728
|
-
case 'r':
|
|
729
|
-
if (k.ctrl) {
|
|
730
|
-
// Ctrl+R:搜索历史
|
|
731
|
-
historyMode = 'search';
|
|
732
|
-
searchQuery = '';
|
|
733
|
-
showHistorySearchPrompt();
|
|
734
|
-
}
|
|
735
|
-
else if (char) {
|
|
736
|
-
currentInput += char;
|
|
737
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
738
|
-
}
|
|
739
|
-
break;
|
|
740
|
-
case 'c':
|
|
741
|
-
if (k.ctrl) {
|
|
742
|
-
// Ctrl+C:取消多行或退出
|
|
743
|
-
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
744
|
-
(0, multiline_input_1.resetMultiline)();
|
|
745
|
-
currentInput = '';
|
|
746
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
747
|
-
console.log(DIM('(cancelled)'));
|
|
748
|
-
}
|
|
749
|
-
else {
|
|
750
|
-
handleCtrlC();
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
else if (char) {
|
|
754
|
-
currentInput += char;
|
|
755
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
756
|
-
}
|
|
757
|
-
break;
|
|
758
|
-
case 'd':
|
|
759
|
-
if (k.ctrl && currentInput === '') {
|
|
760
|
-
// Ctrl+D:退出(空输入时)
|
|
761
|
-
void shutdownCli();
|
|
762
|
-
}
|
|
763
|
-
else if (char) {
|
|
764
|
-
currentInput += char;
|
|
765
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
766
|
-
}
|
|
767
|
-
break;
|
|
768
|
-
case '/':
|
|
769
|
-
// Issue #30 fix: 只在输入为空时触发命令面板(即 `/` 是第一个字符)
|
|
770
|
-
// 避免在 URL(http://)、路径(src/)、正则等场景误触发
|
|
771
|
-
if (currentInput === '' && !turnController.hasActiveTurn()) {
|
|
772
|
-
currentInput = '/';
|
|
773
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
774
|
-
(0, command_panel_1.showCommandPanel)('');
|
|
775
|
-
}
|
|
776
|
-
else {
|
|
777
|
-
// 正常添加 `/` 到输入
|
|
778
|
-
currentInput += '/';
|
|
779
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
780
|
-
}
|
|
781
|
-
break;
|
|
782
|
-
case '@':
|
|
783
|
-
// 显示文件补全
|
|
784
|
-
if (turnController.hasActiveTurn()) {
|
|
785
|
-
currentInput += '@';
|
|
786
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
787
|
-
}
|
|
788
|
-
else {
|
|
789
|
-
const baseInput = currentInput;
|
|
790
|
-
currentInput += '@';
|
|
791
|
-
(0, file_completion_1.showFileCompletion)('', baseInput);
|
|
792
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
793
|
-
}
|
|
794
|
-
break;
|
|
795
|
-
case '?':
|
|
796
|
-
if (currentInput === '' && store.getSnapshot().config.ui?.renderer === 'v2' && !turnController.hasActiveTurn()) {
|
|
797
|
-
toggleV2Shortcuts();
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
currentInput += '?';
|
|
801
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
802
|
-
}
|
|
803
|
-
break;
|
|
804
|
-
default:
|
|
805
|
-
// 普通字符
|
|
806
|
-
if (char && char.length === 1 && !k.ctrl && !k.meta) {
|
|
807
|
-
currentInput += char;
|
|
808
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
function insertInputNewline() {
|
|
813
|
-
currentInput += '\n';
|
|
814
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
815
|
-
}
|
|
816
|
-
function updateHistorySearch() {
|
|
817
|
-
if (searchQuery) {
|
|
818
|
-
const allHistory = (0, global_config_1.getInputHistory)();
|
|
819
|
-
const matches = allHistory.filter(h => h.content.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
820
|
-
historyIndex = 0;
|
|
821
|
-
currentInput = matches[0]?.content || '';
|
|
822
|
-
// Don't replace inputHistory — just display filtered results
|
|
823
|
-
}
|
|
824
|
-
else {
|
|
825
|
-
historyIndex = -1;
|
|
826
|
-
currentInput = '';
|
|
827
|
-
}
|
|
828
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput, `[Search: ${searchQuery}]`);
|
|
829
|
-
}
|
|
830
|
-
function showHistorySearchPrompt() {
|
|
831
|
-
(0, command_panel_1.clearRenderedInput)();
|
|
832
|
-
(0, command_panel_1.redrawInputWithPrompt)('', '[Search: ]');
|
|
833
|
-
}
|
|
834
|
-
function isV2UI() {
|
|
835
|
-
return store.getSnapshot().config.ui?.renderer === 'v2';
|
|
836
|
-
}
|
|
837
|
-
function clearTerminalView() {
|
|
838
|
-
(0, command_panel_1.hideCommandPanel)();
|
|
839
|
-
(0, file_completion_1.hideFileCompletion)();
|
|
840
|
-
hideShortcutsOverlay();
|
|
841
|
-
(0, command_panel_1.resetRenderLength)();
|
|
842
|
-
process.stdout.write('\x1Bc');
|
|
843
|
-
showBanner();
|
|
844
|
-
if (isV2UI()) {
|
|
845
|
-
console.log(DIM(' View cleared. Conversation context is preserved.'));
|
|
846
|
-
console.log((0, ui_v2_1.renderV2FooterHint)(process.stdout.columns || 80));
|
|
847
|
-
}
|
|
848
|
-
else {
|
|
849
|
-
console.log(DIM(' View cleared. Conversation context is preserved.'));
|
|
850
|
-
}
|
|
851
|
-
console.log();
|
|
852
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
853
|
-
}
|
|
854
|
-
function cancelActiveInputMode() {
|
|
855
|
-
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
856
|
-
(0, multiline_input_1.resetMultiline)();
|
|
857
|
-
currentInput = '';
|
|
858
|
-
console.log(DIM('(cancelled)'));
|
|
859
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
860
|
-
return true;
|
|
861
|
-
}
|
|
862
|
-
if ((0, command_panel_1.isPanelVisible)()) {
|
|
863
|
-
(0, command_panel_1.hideCommandPanel)();
|
|
864
|
-
(0, command_panel_1.clearPendingCommand)();
|
|
865
|
-
currentInput = '';
|
|
866
|
-
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
867
|
-
return true;
|
|
868
|
-
}
|
|
869
|
-
if ((0, file_completion_1.isFileCompletionVisible)()) {
|
|
870
|
-
(0, file_completion_1.hideFileCompletion)();
|
|
871
|
-
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
872
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
873
|
-
return true;
|
|
874
|
-
}
|
|
875
|
-
if (resumePickerState?.visible) {
|
|
876
|
-
hideResumePicker();
|
|
877
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
878
|
-
return true;
|
|
879
|
-
}
|
|
880
|
-
if (shortcutsOverlayVisible) {
|
|
881
|
-
hideShortcutsOverlay();
|
|
882
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
883
|
-
return true;
|
|
884
|
-
}
|
|
885
|
-
if (historyMode !== 'none') {
|
|
886
|
-
historyMode = 'none';
|
|
887
|
-
searchQuery = '';
|
|
888
|
-
historyIndex = -1;
|
|
889
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
890
|
-
return true;
|
|
891
|
-
}
|
|
892
|
-
return false;
|
|
893
|
-
}
|
|
894
|
-
async function shutdownCli() {
|
|
895
|
-
if (isShuttingDown)
|
|
896
|
-
return;
|
|
897
|
-
isShuttingDown = true;
|
|
898
|
-
// 保存会话摘要后退出
|
|
899
|
-
if (currentSession) {
|
|
900
|
-
const messages = (0, session_storage_1.readSessionMessages)(currentSession.id);
|
|
901
|
-
if (messages.length > 0) {
|
|
902
|
-
(0, session_storage_1.updateSessionSummary)(currentSession.id, messages);
|
|
903
|
-
}
|
|
904
|
-
(0, session_storage_1.endSession)(currentSession.id);
|
|
905
|
-
}
|
|
906
|
-
// 关闭 stdin raw mode 并退出
|
|
907
|
-
if (process.stdin.isTTY) {
|
|
908
|
-
process.stdin.setRawMode(false);
|
|
909
|
-
}
|
|
910
|
-
process.stdin.pause();
|
|
911
|
-
console.log();
|
|
912
|
-
console.log(DIM('Shutting down...'));
|
|
913
|
-
mcp_1.mcpManager.disconnectAll();
|
|
914
|
-
await runtime.shutdown();
|
|
915
|
-
console.log(SUCCESS('Goodbye! 🐴'));
|
|
916
|
-
process.exit(0);
|
|
917
|
-
}
|
|
918
|
-
async function handleCtrlC() {
|
|
919
|
-
if (cancelActiveInputMode()) {
|
|
920
|
-
turnController.clearExitIntent();
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
if (turnController.hasActiveTurn()) {
|
|
924
|
-
const shouldExit = turnController.registerExitIntent();
|
|
925
|
-
turnController.interruptActiveTurn();
|
|
926
|
-
if (shouldExit) {
|
|
927
|
-
await shutdownCli();
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
(0, command_panel_1.writeLinePreservingInput)(DIM('Interrupted. Press Ctrl+C again to exit.'));
|
|
931
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
if (turnController.registerExitIntent()) {
|
|
935
|
-
await shutdownCli();
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
(0, command_panel_1.writeLinePreservingInput)(DIM('Press Ctrl+C again to exit.'));
|
|
939
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
940
|
-
}
|
|
941
|
-
// ============================================================================
|
|
942
|
-
// Banner
|
|
943
|
-
// ============================================================================
|
|
944
|
-
function showBanner() {
|
|
945
|
-
const config = store.getSnapshot().config;
|
|
946
|
-
const baseUrl = config.apiBaseUrl || '';
|
|
947
|
-
const provider = baseUrl.includes('anthropic') ? 'Anthropic'
|
|
948
|
-
: baseUrl.includes('openai') ? 'OpenAI'
|
|
949
|
-
: baseUrl.includes('dashscope') ? 'Alibaba Cloud'
|
|
950
|
-
: 'Custom';
|
|
951
|
-
if (config.ui?.renderer === 'v2') {
|
|
952
|
-
console.log();
|
|
953
|
-
console.log((0, ui_v2_1.renderV2ShellHeader)({
|
|
954
|
-
provider,
|
|
955
|
-
model: config.model,
|
|
956
|
-
projectPath: process.cwd(),
|
|
957
|
-
status: llm ? 'ready' : 'loading',
|
|
958
|
-
statusText: llm ? 'ready' : 'Set OPENHORSE_API_KEY',
|
|
959
|
-
version: VERSION,
|
|
960
|
-
}));
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
console.log();
|
|
964
|
-
console.log((0, box_1.renderHeaderBox)({
|
|
965
|
-
provider,
|
|
966
|
-
model: config.model,
|
|
967
|
-
endpoint: baseUrl,
|
|
968
|
-
status: llm ? 'ready' : 'loading',
|
|
969
|
-
statusText: llm ? undefined : 'Set OPENHORSE_API_KEY in .env',
|
|
970
|
-
version: VERSION,
|
|
971
|
-
}));
|
|
80
|
+
return 'ink';
|
|
972
81
|
}
|
|
973
|
-
|
|
974
|
-
// 输入处理
|
|
975
|
-
// ============================================================================
|
|
976
|
-
function getPrompt() {
|
|
977
|
-
const mode = store.getSnapshot().permissionMode;
|
|
978
|
-
const modeText = (0, types_1.getModeDisplayText)(mode);
|
|
979
|
-
const modeIndicator = modeText ? `[${modeText}] ` : '';
|
|
980
|
-
return ACCENT('❯ ') + DIM(modeIndicator);
|
|
981
|
-
}
|
|
982
|
-
async function handleInput(input, abortSignal, options = {}) {
|
|
983
|
-
const redrawPrompt = options.redrawPrompt !== false;
|
|
984
|
-
const shouldUpdateStatus = options.updateStatus !== false;
|
|
985
|
-
const text = input.trim();
|
|
986
|
-
if (!text)
|
|
987
|
-
return;
|
|
988
|
-
// 不在这里打印输入,readline 已经显示了
|
|
989
|
-
// 直接交给 executeChat 处理,它有自己的 spinner 和流式输出
|
|
990
|
-
const ctx = {
|
|
991
|
-
cwd: process.cwd(),
|
|
992
|
-
config: store.getSnapshot().config,
|
|
993
|
-
store,
|
|
994
|
-
llm,
|
|
995
|
-
runtime,
|
|
996
|
-
sessionId: currentSession?.id,
|
|
997
|
-
ensureSession: ensureCurrentSession,
|
|
998
|
-
setSession: setCurrentSession,
|
|
999
|
-
getSession: getCurrentSession,
|
|
1000
|
-
abortSignal,
|
|
1001
|
-
writeOutput: command_panel_1.writeOutputPreservingInput,
|
|
1002
|
-
writeLine: command_panel_1.writeLinePreservingInput,
|
|
1003
|
-
};
|
|
1004
|
-
let pendingSessionPicker;
|
|
1005
|
-
try {
|
|
1006
|
-
const parsed = (0, parser_1.parseInput)(text);
|
|
1007
|
-
if (parsed.isCommand) {
|
|
1008
|
-
const cmd = (0, commands_1.findCommand)(parsed.name);
|
|
1009
|
-
if (cmd) {
|
|
1010
|
-
const result = await cmd.execute(ctx, parsed.args);
|
|
1011
|
-
pendingSessionPicker = result.sessionPicker;
|
|
1012
|
-
if (result.continueAsChat) {
|
|
1013
|
-
await (0, commands_1.executeChat)(ctx, result.chatInput ?? parsed.args);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
else {
|
|
1017
|
-
if ((0, skills_1.hasMatchingSkill)(text)) {
|
|
1018
|
-
await (0, commands_1.executeChat)(ctx, text);
|
|
1019
|
-
}
|
|
1020
|
-
else {
|
|
1021
|
-
console.log();
|
|
1022
|
-
console.log(ERROR(`Unknown command: /${parsed.name}`));
|
|
1023
|
-
const suggestions = (0, parser_1.buildCommandSuggestions)(parsed.name);
|
|
1024
|
-
if (suggestions.length > 0) {
|
|
1025
|
-
console.log(DIM(`Did you mean: ${suggestions.map(s => `/${s}`).join(', ')}?`));
|
|
1026
|
-
}
|
|
1027
|
-
console.log();
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
else {
|
|
1032
|
-
// 直接 chat - executeChat 有自己的 spinner 和流式输出
|
|
1033
|
-
await (0, commands_1.executeChat)(ctx, text);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
catch (err) {
|
|
1037
|
-
console.log(ERROR(`Error: ${err.message || String(err)}`));
|
|
1038
|
-
}
|
|
1039
|
-
// 显示状态栏
|
|
1040
|
-
if (shouldUpdateStatus) {
|
|
1041
|
-
updateStatusBar();
|
|
1042
|
-
}
|
|
1043
|
-
// 重新显示 prompt
|
|
1044
|
-
if (redrawPrompt) {
|
|
1045
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
1046
|
-
}
|
|
1047
|
-
if (pendingSessionPicker) {
|
|
1048
|
-
showResumePicker(pendingSessionPicker);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* 更新状态栏显示
|
|
1053
|
-
*/
|
|
1054
|
-
function updateStatusBar() {
|
|
1055
|
-
const snapshot = store.getSnapshot();
|
|
1056
|
-
const usage = snapshot.tokenUsage;
|
|
1057
|
-
const costStats = snapshot.costTracker.getSessionStats();
|
|
1058
|
-
const mcpStatus = mcp_1.mcpManager.getStatus();
|
|
1059
|
-
const stats = {
|
|
1060
|
-
model: snapshot.currentModel,
|
|
1061
|
-
tokens: usage ? usage.promptTokens + usage.completionTokens : 0,
|
|
1062
|
-
promptTokens: usage?.promptTokens || 0,
|
|
1063
|
-
completionTokens: usage?.completionTokens || 0,
|
|
1064
|
-
cost: costStats.totalCost,
|
|
1065
|
-
ctxPercent: (0, model_context_1.calculateCtxPercent)(usage ? usage.promptTokens + usage.completionTokens : 0, snapshot.currentModel || 'gpt-4o'),
|
|
1066
|
-
mcpConnected: mcpStatus.filter(s => s.connected).length,
|
|
1067
|
-
mcpTotal: mcpStatus.length,
|
|
1068
|
-
};
|
|
1069
|
-
// 在 prompt 上一行显示状态栏
|
|
1070
|
-
if (snapshot.config.ui?.renderer === 'v2') {
|
|
1071
|
-
(0, command_panel_1.setInputStatusText)((0, ui_v2_1.renderV2StatusBadge)({
|
|
1072
|
-
...stats,
|
|
1073
|
-
sessionId: currentSession?.id,
|
|
1074
|
-
modeText: (0, types_1.getModeDisplayText)(snapshot.permissionMode) || undefined,
|
|
1075
|
-
width: process.stdout.columns || 80,
|
|
1076
|
-
}));
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
console.log((0, status_bar_1.renderStatusBar)(stats));
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
// 接口变量(用于兼容性,主要逻辑通过 keypress 处理)
|
|
1083
|
-
let rl = null;
|
|
1084
|
-
// ============================================================================
|
|
1085
|
-
// 主入口
|
|
1086
|
-
// ============================================================================
|
|
1087
|
-
async function main() {
|
|
1088
|
-
// Parse command line arguments
|
|
1089
|
-
const args = process.argv.slice(2);
|
|
1090
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
1091
|
-
showCliHelp();
|
|
1092
|
-
process.exit(0);
|
|
1093
|
-
}
|
|
1094
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
1095
|
-
console.log(`openhorse v${VERSION}`);
|
|
1096
|
-
process.exit(0);
|
|
1097
|
-
}
|
|
82
|
+
async function bootstrapRuntime(uiRenderer) {
|
|
1098
83
|
(0, config_dir_1.ensureConfigDir)();
|
|
1099
84
|
(0, global_config_1.recordFirstStartTime)();
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1103
|
-
(0, command_panel_1.setInputPromptRenderer)(cliConfig.ui?.renderer === 'v2' ? 'v2' : 'legacy');
|
|
1104
|
-
(0, command_panel_1.setInputRenderContextProvider)(() => (cliConfig.ui?.renderer === 'v2' && (0, multiline_input_1.isMultilineActive)()
|
|
1105
|
-
? { prefixLines: (0, multiline_input_1.getMultilineLines)() }
|
|
1106
|
-
: {}));
|
|
1107
|
-
(0, file_completion_1.setFileCompletionPromptRenderer)(cliConfig.ui?.renderer === 'v2' ? 'v2' : 'legacy');
|
|
1108
|
-
// Load project memory
|
|
1109
|
-
const memories = (0, storage_1.loadAllMemories)(projectPath);
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
const config = (0, config_1.loadConfig)({ ui: { renderer: uiRenderer } });
|
|
87
|
+
const memories = (0, storage_1.loadAllMemories)(cwd);
|
|
1110
88
|
const memoryContent = memories.length > 0
|
|
1111
|
-
? memories.map(
|
|
89
|
+
? memories.map(memory => `## ${memory.name} (${memory.type})\n${memory.content}`).join('\n\n')
|
|
1112
90
|
: '';
|
|
1113
|
-
// Load skills (builtin + user + project) and render the prompt section
|
|
1114
91
|
let skillsContent = '';
|
|
1115
92
|
try {
|
|
1116
93
|
const registry = (0, skills_1.getSkillsRegistry)();
|
|
1117
94
|
skillsContent = registry.generateSystemPromptInjection();
|
|
1118
95
|
}
|
|
1119
|
-
catch
|
|
1120
|
-
|
|
96
|
+
catch {
|
|
97
|
+
skillsContent = '';
|
|
1121
98
|
}
|
|
1122
|
-
store = new framework_1.Store({
|
|
1123
|
-
config
|
|
99
|
+
const store = new framework_1.Store({
|
|
100
|
+
config,
|
|
1124
101
|
tools: (0, tools_1.getRuntimeTools)(),
|
|
1125
|
-
currentModel:
|
|
102
|
+
currentModel: config.model,
|
|
1126
103
|
memoryContent,
|
|
1127
104
|
skillsContent,
|
|
1128
105
|
});
|
|
1129
|
-
// Mirror tool-state (todos/plan) into Store so the UI can observe it
|
|
1130
106
|
(0, framework_1.resetToolState)();
|
|
1131
|
-
(0, framework_1.subscribeToolState)(
|
|
107
|
+
(0, framework_1.subscribeToolState)(state => {
|
|
1132
108
|
store.setState({
|
|
1133
|
-
todos:
|
|
1134
|
-
planMode:
|
|
1135
|
-
currentPlan:
|
|
109
|
+
todos: state.todos,
|
|
110
|
+
planMode: state.planMode,
|
|
111
|
+
currentPlan: state.currentPlan,
|
|
1136
112
|
});
|
|
1137
113
|
});
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
(0, model_context_1.discoverModelContexts)(cliConfig.apiBaseUrl, cliConfig.apiKey)
|
|
1149
|
-
.then(models => {
|
|
1150
|
-
if (models.length > 0) {
|
|
1151
|
-
console.log(` Discovered ${models.length} models from API`);
|
|
1152
|
-
}
|
|
1153
|
-
})
|
|
1154
|
-
.catch(() => { }); // 静默失败,回退到内置数据库
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
catch (err) {
|
|
1158
|
-
console.log(WARN(`⚠ LLM initialization warning: ${err.message}`));
|
|
114
|
+
let llm = null;
|
|
115
|
+
if ((0, config_1.isConfigured)(config)) {
|
|
116
|
+
llm = new llm_1.LLMService({
|
|
117
|
+
apiKey: config.apiKey,
|
|
118
|
+
baseUrl: config.apiBaseUrl,
|
|
119
|
+
model: config.model,
|
|
120
|
+
fallbackModel: config.fallbackModel,
|
|
121
|
+
});
|
|
122
|
+
if (config.apiBaseUrl) {
|
|
123
|
+
(0, model_context_1.discoverModelContexts)(config.apiBaseUrl, config.apiKey).catch(() => undefined);
|
|
1159
124
|
}
|
|
1160
125
|
}
|
|
1161
|
-
const
|
|
1162
|
-
runtime = await (0, init_1.init)({
|
|
126
|
+
const runtime = await (0, init_1.init)({
|
|
1163
127
|
name: config.name,
|
|
1164
128
|
mode: config.mode,
|
|
1165
129
|
logLevel: config.logLevel,
|
|
1166
130
|
});
|
|
1167
131
|
await runtime.start();
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
store.setState({ tools: (0, tools_1.getRuntimeTools)() });
|
|
1172
|
-
})
|
|
1173
|
-
.catch(err => {
|
|
1174
|
-
console.error(WARN(`⚠ MCP startup error: ${err.message}`));
|
|
1175
|
-
});
|
|
1176
|
-
// Banner
|
|
1177
|
-
showBanner();
|
|
1178
|
-
// 提示
|
|
1179
|
-
console.log(SUCCESS('✔ System initialized'));
|
|
1180
|
-
console.log(DIM(' Type /help for commands, /exit to quit'));
|
|
1181
|
-
if (cliConfig.ui?.renderer === 'v2') {
|
|
1182
|
-
console.log(DIM(' UI v2 enabled: command palette and session picker'));
|
|
1183
|
-
console.log((0, ui_v2_1.renderV2FooterHint)(process.stdout.columns || 80));
|
|
1184
|
-
}
|
|
1185
|
-
if (!(0, config_1.isConfigured)(cliConfig)) {
|
|
1186
|
-
console.log(WARN(' ⚠ LLM not configured — set OPENHORSE_API_KEY'));
|
|
1187
|
-
}
|
|
1188
|
-
console.log();
|
|
1189
|
-
// 加载输入历史
|
|
1190
|
-
inputHistory = (0, global_config_1.getInputHistory)();
|
|
1191
|
-
// 启用 keypress 事件处理
|
|
1192
|
-
// 使用 emitKeypressEvents + setRawMode 实现交互式功能
|
|
1193
|
-
readline_1.default.emitKeypressEvents(process.stdin);
|
|
1194
|
-
if (process.stdin.isTTY) {
|
|
132
|
+
void (async () => {
|
|
133
|
+
const originalLog = console.log;
|
|
134
|
+
const originalError = console.error;
|
|
1195
135
|
try {
|
|
1196
|
-
|
|
136
|
+
console.log = () => undefined;
|
|
137
|
+
console.error = () => undefined;
|
|
138
|
+
await mcp_1.mcpManager.connectAll();
|
|
139
|
+
store.setState({ tools: (0, tools_1.getRuntimeTools)() });
|
|
1197
140
|
}
|
|
1198
|
-
catch
|
|
1199
|
-
|
|
141
|
+
catch {
|
|
142
|
+
// MCP failures are surfaced through /mcp and tool errors.
|
|
1200
143
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
144
|
+
finally {
|
|
145
|
+
console.log = originalLog;
|
|
146
|
+
console.error = originalError;
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
let currentSession = null;
|
|
150
|
+
let shuttingDown = false;
|
|
151
|
+
const ensureSession = () => {
|
|
152
|
+
if (!currentSession) {
|
|
153
|
+
currentSession = (0, session_storage_1.createSession)(cwd, store.getSnapshot().currentModel || store.getSnapshot().config.model);
|
|
154
|
+
(0, global_config_1.incrementSessionCount)();
|
|
155
|
+
}
|
|
156
|
+
return currentSession;
|
|
157
|
+
};
|
|
158
|
+
const setSession = (session) => {
|
|
159
|
+
currentSession = session;
|
|
160
|
+
};
|
|
161
|
+
const getSession = () => currentSession;
|
|
162
|
+
const shutdown = async () => {
|
|
163
|
+
if (shuttingDown)
|
|
164
|
+
return;
|
|
165
|
+
shuttingDown = true;
|
|
166
|
+
if (currentSession) {
|
|
167
|
+
const messages = (0, session_storage_1.readSessionMessages)(currentSession.id);
|
|
168
|
+
if (messages.length > 0) {
|
|
169
|
+
(0, session_storage_1.updateSessionSummary)(currentSession.id, messages);
|
|
170
|
+
}
|
|
171
|
+
(0, session_storage_1.endSession)(currentSession.id);
|
|
1213
172
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
173
|
+
await mcp_1.mcpManager.disconnectAll();
|
|
174
|
+
await runtime.shutdown();
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
cwd,
|
|
178
|
+
version: VERSION,
|
|
179
|
+
config,
|
|
180
|
+
store,
|
|
181
|
+
llm,
|
|
182
|
+
runtime,
|
|
183
|
+
isConfigured: (0, config_1.isConfigured)(config),
|
|
184
|
+
ensureSession,
|
|
185
|
+
setSession,
|
|
186
|
+
getSession,
|
|
187
|
+
shutdown,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async function main() {
|
|
191
|
+
const args = process.argv.slice(2);
|
|
192
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
193
|
+
showCliHelp();
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
197
|
+
console.log(`openhorse v${VERSION}`);
|
|
198
|
+
process.exit(0);
|
|
1218
199
|
}
|
|
1219
|
-
|
|
200
|
+
const uiRenderer = parseCliUIRenderer(args);
|
|
201
|
+
const runtime = await bootstrapRuntime(uiRenderer);
|
|
202
|
+
await (0, launch_1.launchInkUI)(runtime);
|
|
1220
203
|
}
|
|
1221
|
-
main().catch(
|
|
1222
|
-
console.error(ERROR('[OpenHorse] Fatal error:'),
|
|
204
|
+
main().catch(async (error) => {
|
|
205
|
+
console.error(ERROR('[OpenHorse] Fatal error:'), error);
|
|
206
|
+
try {
|
|
207
|
+
await mcp_1.mcpManager.disconnectAll();
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// ignore shutdown errors
|
|
211
|
+
}
|
|
1223
212
|
process.exit(1);
|
|
1224
213
|
});
|
|
1225
214
|
//# sourceMappingURL=cli.js.map
|