@ai-zen/cli 1.0.0-alpha.2 → 1.0.0-alpha.20
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/CHANGELOG.md +356 -0
- package/README.md +99 -28
- package/README.zh.md +99 -28
- package/dist/agent-creator.d.ts +11 -0
- package/dist/agent-creator.d.ts.map +1 -1
- package/dist/agent-creator.js +20 -11
- package/dist/agent-creator.js.map +1 -1
- package/dist/agents-store.d.ts.map +1 -1
- package/dist/agents-store.js +40 -3
- package/dist/agents-store.js.map +1 -1
- package/dist/auto-migrate-confirm.d.ts +43 -0
- package/dist/auto-migrate-confirm.d.ts.map +1 -0
- package/dist/auto-migrate-confirm.js +46 -0
- package/dist/auto-migrate-confirm.js.map +1 -0
- package/dist/config-editor.d.ts +4 -4
- package/dist/config-editor.d.ts.map +1 -1
- package/dist/config-editor.js +6 -8
- package/dist/config-editor.js.map +1 -1
- package/dist/config.d.ts +23 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +26 -2
- package/dist/config.js.map +1 -1
- package/dist/conversation-commands/registry.d.ts.map +1 -1
- package/dist/conversation-commands/registry.js +4 -1
- package/dist/conversation-commands/registry.js.map +1 -1
- package/dist/conversation-persist-plugin.d.ts +0 -1
- package/dist/conversation-persist-plugin.d.ts.map +1 -1
- package/dist/conversation-persist-plugin.js +2 -3
- package/dist/conversation-persist-plugin.js.map +1 -1
- package/dist/conversation-repository.d.ts +5 -1
- package/dist/conversation-repository.d.ts.map +1 -1
- package/dist/conversation-repository.js +1 -2
- package/dist/conversation-repository.js.map +1 -1
- package/dist/handoff-service.d.ts +19 -0
- package/dist/handoff-service.d.ts.map +1 -0
- package/dist/handoff-service.js +86 -0
- package/dist/handoff-service.js.map +1 -0
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/model-switch.d.ts +58 -0
- package/dist/model-switch.d.ts.map +1 -0
- package/dist/model-switch.js +61 -0
- package/dist/model-switch.js.map +1 -0
- package/dist/reasoning-effort.d.ts +77 -0
- package/dist/reasoning-effort.d.ts.map +1 -0
- package/dist/reasoning-effort.js +137 -0
- package/dist/reasoning-effort.js.map +1 -0
- package/dist/reasoning-presets.d.ts +34 -0
- package/dist/reasoning-presets.d.ts.map +1 -0
- package/dist/reasoning-presets.js +85 -0
- package/dist/reasoning-presets.js.map +1 -0
- package/dist/sdk-log.d.ts +48 -0
- package/dist/sdk-log.d.ts.map +1 -0
- package/dist/sdk-log.js +67 -0
- package/dist/sdk-log.js.map +1 -0
- package/dist/session-pointer.d.ts +22 -9
- package/dist/session-pointer.d.ts.map +1 -1
- package/dist/session-pointer.js +55 -23
- package/dist/session-pointer.js.map +1 -1
- package/dist/stdio-runner.d.ts.map +1 -1
- package/dist/stdio-runner.js +15 -13
- package/dist/stdio-runner.js.map +1 -1
- package/dist/sub-agent-guard-plugin.d.ts +5 -5
- package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
- package/dist/sub-agent-guard-plugin.js +7 -7
- package/dist/sub-agent-guard-plugin.js.map +1 -1
- package/dist/tui/chat-session.d.ts +83 -59
- package/dist/tui/chat-session.d.ts.map +1 -1
- package/dist/tui/chat-session.js +161 -105
- package/dist/tui/chat-session.js.map +1 -1
- package/dist/tui/components.d.ts +7 -2
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +3 -3
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/config-wizard/engine.js +1 -1
- package/dist/tui/config-wizard/engine.js.map +1 -1
- package/dist/tui/config-wizard/fields.d.ts +3 -1
- package/dist/tui/config-wizard/fields.d.ts.map +1 -1
- package/dist/tui/config-wizard/fields.js +58 -6
- package/dist/tui/config-wizard/fields.js.map +1 -1
- package/dist/tui/config-wizard/menu.d.ts.map +1 -1
- package/dist/tui/config-wizard/menu.js +2 -3
- package/dist/tui/config-wizard/menu.js.map +1 -1
- package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-defaults.js +4 -11
- package/dist/tui/config-wizard/step-defaults.js.map +1 -1
- package/dist/tui/config-wizard/types.d.ts +18 -3
- package/dist/tui/config-wizard/types.d.ts.map +1 -1
- package/dist/tui/config-wizard/wizard.d.ts.map +1 -1
- package/dist/tui/config-wizard/wizard.js +4 -3
- package/dist/tui/config-wizard/wizard.js.map +1 -1
- package/dist/tui/highlight.d.ts +3 -2
- package/dist/tui/highlight.d.ts.map +1 -1
- package/dist/tui/highlight.js +3 -2
- package/dist/tui/highlight.js.map +1 -1
- package/dist/tui/index.d.ts +1 -0
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +87 -40
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input-editing.d.ts +49 -0
- package/dist/tui/input-editing.d.ts.map +1 -0
- package/dist/tui/input-editing.js +87 -0
- package/dist/tui/input-editing.js.map +1 -0
- package/dist/tui/key-debug.d.ts +62 -0
- package/dist/tui/key-debug.d.ts.map +1 -0
- package/dist/tui/key-debug.js +92 -0
- package/dist/tui/key-debug.js.map +1 -0
- package/dist/tui/mouse.d.ts +55 -0
- package/dist/tui/mouse.d.ts.map +1 -0
- package/dist/tui/mouse.js +74 -0
- package/dist/tui/mouse.js.map +1 -0
- package/dist/tui/screens/chat-commands.d.ts +12 -2
- package/dist/tui/screens/chat-commands.d.ts.map +1 -1
- package/dist/tui/screens/chat-commands.js +204 -17
- package/dist/tui/screens/chat-commands.js.map +1 -1
- package/dist/tui/screens/chat.d.ts +2 -5
- package/dist/tui/screens/chat.d.ts.map +1 -1
- package/dist/tui/screens/chat.js +460 -98
- package/dist/tui/screens/chat.js.map +1 -1
- package/dist/tui/screens/frame-buffer.d.ts +47 -0
- package/dist/tui/screens/frame-buffer.d.ts.map +1 -0
- package/dist/tui/screens/frame-buffer.js +78 -0
- package/dist/tui/screens/frame-buffer.js.map +1 -0
- package/dist/tui/screens/handoff-choice.d.ts +37 -0
- package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
- package/dist/tui/screens/handoff-choice.js +41 -0
- package/dist/tui/screens/handoff-choice.js.map +1 -0
- package/dist/tui/screens/index.d.ts +8 -8
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +7 -7
- package/dist/tui/screens/index.js.map +1 -1
- package/dist/tui/screens/input.d.ts +35 -0
- package/dist/tui/screens/input.d.ts.map +1 -1
- package/dist/tui/screens/input.js +66 -0
- package/dist/tui/screens/input.js.map +1 -1
- package/dist/tui/screens/layout.d.ts +13 -3
- package/dist/tui/screens/layout.d.ts.map +1 -1
- package/dist/tui/screens/layout.js +15 -8
- package/dist/tui/screens/layout.js.map +1 -1
- package/dist/tui/screens/lines.d.ts +12 -41
- package/dist/tui/screens/lines.d.ts.map +1 -1
- package/dist/tui/screens/lines.js +87 -95
- package/dist/tui/screens/lines.js.map +1 -1
- package/dist/tui/screens/messages-view.d.ts +10 -0
- package/dist/tui/screens/messages-view.d.ts.map +1 -0
- package/dist/tui/screens/messages-view.js +115 -0
- package/dist/tui/screens/messages-view.js.map +1 -0
- package/dist/tui/screens/notice.d.ts +32 -0
- package/dist/tui/screens/notice.d.ts.map +1 -0
- package/dist/tui/screens/notice.js +46 -0
- package/dist/tui/screens/notice.js.map +1 -0
- package/dist/tui/screens/render.d.ts +2 -4
- package/dist/tui/screens/render.d.ts.map +1 -1
- package/dist/tui/screens/render.js +7 -43
- package/dist/tui/screens/render.js.map +1 -1
- package/dist/tui/screens/splash.d.ts.map +1 -1
- package/dist/tui/screens/splash.js +7 -1
- package/dist/tui/screens/splash.js.map +1 -1
- package/dist/tui/text.d.ts +12 -2
- package/dist/tui/text.d.ts.map +1 -1
- package/dist/tui/text.js +17 -3
- package/dist/tui/text.js.map +1 -1
- package/dist/tui/theme.d.ts +7 -1
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +15 -3
- package/dist/tui/theme.js.map +1 -1
- package/dist/types.d.ts +7 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
- package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
- package/dist/auto-migrate-confirm-plugin.js +0 -61
- package/dist/auto-migrate-confirm-plugin.js.map +0 -1
- package/dist/migration-service.d.ts +0 -15
- package/dist/migration-service.d.ts.map +0 -1
- package/dist/migration-service.js +0 -59
- package/dist/migration-service.js.map +0 -1
- package/dist/tui/screens/state.d.ts +0 -13
- package/dist/tui/screens/state.d.ts.map +0 -1
- package/dist/tui/screens/state.js +0 -75
- package/dist/tui/screens/state.js.map +0 -1
package/dist/tui/index.js
CHANGED
|
@@ -11,12 +11,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
*/
|
|
12
12
|
import { render, useApp } from "ink";
|
|
13
13
|
import { AgentRepository } from "@ai-zen/agents-sdk";
|
|
14
|
-
import { AGENTS_DIR, readConfig, saveConfig } from "../config.js";
|
|
14
|
+
import { AGENTS_DIR, ensureConfigDir, readConfig, saveConfig } from "../config.js";
|
|
15
15
|
import { resolveCredential } from "../config-editor.js";
|
|
16
16
|
import { ConfigWizard } from "./config-wizard/index.js";
|
|
17
17
|
import { conversationRepository } from "../conversation-repository.js";
|
|
18
18
|
import { readLastSession } from "../session-pointer.js";
|
|
19
19
|
import { Splash, Chat } from "./screens/index.js";
|
|
20
|
+
import { keyDebugStart } from "./key-debug.js";
|
|
21
|
+
import { disableMouseTracking, enableMouseTracking } from "./mouse.js";
|
|
22
|
+
import { installSdkLogger } from "../sdk-log.js";
|
|
20
23
|
/** ANSI:清屏 + 光标归位 + 显示光标(屏幕间切换时从头绘制,避免行偏移) */
|
|
21
24
|
const CLEAR_SCREEN = "\x1b[2J\x1b[H\x1b[?25h";
|
|
22
25
|
/**
|
|
@@ -27,6 +30,13 @@ const CLEAR_SCREEN = "\x1b[2J\x1b[H\x1b[?25h";
|
|
|
27
30
|
* 保证退出即干净交还 shell(Windows 下 TTY 写为异步,用回调 + 兜底定时器双保险)。
|
|
28
31
|
*/
|
|
29
32
|
function farewell() {
|
|
33
|
+
// 先关鼠标上报:不关的话,回到 shell 后每次滚轮都会在提示符前吐一串 `^[[<64;…M`
|
|
34
|
+
try {
|
|
35
|
+
disableMouseTracking();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* ignore */
|
|
39
|
+
}
|
|
30
40
|
try {
|
|
31
41
|
process.stdin.setRawMode?.(false);
|
|
32
42
|
}
|
|
@@ -62,13 +72,20 @@ let activeInstance = null;
|
|
|
62
72
|
export const RENDER_OPTIONS = {
|
|
63
73
|
exitOnCtrlC: false,
|
|
64
74
|
alternateScreen: false,
|
|
75
|
+
// 只重绘「变化的行」,而不是每帧擦除 + 重写整屏(Ink 默认 false,走 createStandard)。
|
|
76
|
+
// 流式高频下把每帧输出量从 O(屏幕大小) 降到 O(变化量),显著减轻 SSH 带宽 / 背压压力。
|
|
77
|
+
incrementalRendering: true,
|
|
65
78
|
kittyKeyboard: { mode: "disabled" },
|
|
66
79
|
};
|
|
67
80
|
function mount(node) {
|
|
68
81
|
process.stdout.write(CLEAR_SCREEN);
|
|
82
|
+
// 鼠标上报随实例生命周期开关:本 TUI 会反复挂载(启动屏 → 对话 → 配置向导),
|
|
83
|
+
// 每个实例结束时都关掉,避免「已卸载却仍在收鼠标事件」。
|
|
84
|
+
enableMouseTracking();
|
|
69
85
|
const instance = render(node, RENDER_OPTIONS);
|
|
70
86
|
activeInstance = instance;
|
|
71
87
|
return instance.waitUntilExit().finally(() => {
|
|
88
|
+
disableMouseTracking();
|
|
72
89
|
if (activeInstance === instance)
|
|
73
90
|
activeInstance = null;
|
|
74
91
|
});
|
|
@@ -97,37 +114,48 @@ async function ensureCredential(modelId) {
|
|
|
97
114
|
const result = (await mount(_jsx(CredentialGate, { config: config, endpointId: credential.endpointId })));
|
|
98
115
|
return Boolean(result?.dirty);
|
|
99
116
|
}
|
|
100
|
-
|
|
117
|
+
/**
|
|
118
|
+
* 解析某个 Agent 绑定模型(**唯一真相源**)。
|
|
119
|
+
*
|
|
120
|
+
* 配置层已无 `defaultModel`、会话存档也不再记模型,所以这里缺失就直接报错,
|
|
121
|
+
* 绝不做任何隐式回退——回退正是「状态栏显示 A、实发请求 B」的根源。
|
|
122
|
+
*/
|
|
123
|
+
async function modelOf(agentId) {
|
|
124
|
+
const id = agentId || "default";
|
|
125
|
+
// 先跑一次配置初始化:bootstrap 会为缺 modelId 的历史 Agent 补上并落盘。
|
|
126
|
+
// 不做这一步就会读到“未绑定模型”的旧定义,把一次正常的升级变成启动错误。
|
|
101
127
|
const config = await readConfig();
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const agentId = config.defaultAgent ?? agents[0]?.id;
|
|
105
|
-
let modelId;
|
|
106
|
-
if (agentId) {
|
|
107
|
-
const agent = await repository.read(agentId);
|
|
108
|
-
modelId = agent?.modelId;
|
|
109
|
-
}
|
|
110
|
-
modelId = modelId ?? config.defaultModel ?? config.models[0]?.id;
|
|
128
|
+
const agent = await new AgentRepository(AGENTS_DIR).read(id);
|
|
129
|
+
const modelId = agent?.modelId;
|
|
111
130
|
if (!modelId) {
|
|
112
|
-
throw new Error(
|
|
131
|
+
throw new Error(`Agent「${id}」未绑定模型,请用 /config 为它选择模型`);
|
|
132
|
+
}
|
|
133
|
+
if (!config.models.some((model) => model.id === modelId)) {
|
|
134
|
+
throw new Error(`Agent「${id}」绑定的模型“${modelId}”已不在配置中,请用 /config 重新选择`);
|
|
113
135
|
}
|
|
114
|
-
return
|
|
136
|
+
return modelId;
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
async function modelExists(modelId) {
|
|
138
|
+
async function resolveTarget() {
|
|
118
139
|
const config = await readConfig();
|
|
119
|
-
|
|
140
|
+
const repository = new AgentRepository(AGENTS_DIR);
|
|
141
|
+
const agents = await repository.list();
|
|
142
|
+
const agentId = config.defaultAgent ?? agents[0]?.id;
|
|
143
|
+
if (!agentId)
|
|
144
|
+
throw new Error("没有可用的 Agent,请先在配置中创建(配置文件 config.json)");
|
|
145
|
+
// 校验模型可用(缺失时抛出可读错误,交给启动屏展示)
|
|
146
|
+
await modelOf(agentId);
|
|
147
|
+
return { agentId };
|
|
120
148
|
}
|
|
121
149
|
/** 读取「上一轮会话 id」指针,续接该会话(指针缺失或会话不存在时返回 null) */
|
|
122
150
|
async function resumeLastSession() {
|
|
123
|
-
const pointer = await readLastSession();
|
|
151
|
+
const pointer = await readLastSession(process.cwd());
|
|
124
152
|
if (!pointer)
|
|
125
153
|
return null;
|
|
126
154
|
const conversation = await conversationRepository.read(pointer.id);
|
|
127
155
|
if (!conversation)
|
|
128
156
|
return null;
|
|
157
|
+
// 模型由 Agent 定义决定(旧存档里的 modelId 一律忽略)
|
|
129
158
|
return targetFromMessages({
|
|
130
|
-
modelId: conversation.modelId,
|
|
131
159
|
agentId: conversation.agentId,
|
|
132
160
|
messages: conversation.messages,
|
|
133
161
|
conversationId: conversation.id,
|
|
@@ -137,7 +165,6 @@ async function resumeLastSession() {
|
|
|
137
165
|
/** 从已保存对话构造对话目标 */
|
|
138
166
|
function targetFromMessages(input) {
|
|
139
167
|
return {
|
|
140
|
-
modelId: input.modelId,
|
|
141
168
|
agentId: input.agentId,
|
|
142
169
|
messages: input.messages,
|
|
143
170
|
conversationId: input.conversationId,
|
|
@@ -153,28 +180,32 @@ async function runChat(target) {
|
|
|
153
180
|
let current = target;
|
|
154
181
|
let messages = target.messages;
|
|
155
182
|
let initialInput;
|
|
156
|
-
|
|
183
|
+
/** 重挂载后要展示一次的系统提示(如 `/model` 的写入结果,旧实例已卸载,只能传给新屏) */
|
|
184
|
+
let initialNotice;
|
|
157
185
|
while (true) {
|
|
158
|
-
const result = await mount(_jsx(Chat, {
|
|
186
|
+
const result = await mount(_jsx(Chat, { agentId: current.agentId, messages: messages, conversationId: current.conversationId, conversationName: current.conversationName, initialInput: initialInput, initialNotice: initialNotice }));
|
|
159
187
|
if (result && typeof result === "object") {
|
|
160
188
|
const value = result;
|
|
161
189
|
if (value.load) {
|
|
162
|
-
// /load
|
|
190
|
+
// /load:整体切换到另一段已保存对话(含 Agent),重挂载对话屏
|
|
163
191
|
current = {
|
|
164
|
-
modelId: value.load.modelId,
|
|
165
192
|
agentId: value.load.agentId,
|
|
166
193
|
conversationId: value.load.conversationId,
|
|
167
194
|
conversationName: value.load.conversationName,
|
|
168
195
|
};
|
|
169
196
|
messages = value.load.messages;
|
|
170
197
|
initialInput = undefined;
|
|
171
|
-
|
|
198
|
+
initialNotice = undefined;
|
|
172
199
|
continue;
|
|
173
200
|
}
|
|
174
201
|
if (value.restart) {
|
|
175
202
|
messages = value.messages ?? messages;
|
|
176
203
|
initialInput = value.initialInput;
|
|
177
|
-
|
|
204
|
+
initialNotice = value.notice;
|
|
205
|
+
// `/model` 换模型:Agent 定义里的 modelId 已变,需重建会话(阈值、工具集跟着变)
|
|
206
|
+
if (value.agentId) {
|
|
207
|
+
current = { ...current, agentId: value.agentId };
|
|
208
|
+
}
|
|
178
209
|
continue;
|
|
179
210
|
}
|
|
180
211
|
}
|
|
@@ -184,25 +215,24 @@ async function runChat(target) {
|
|
|
184
215
|
// ==================== 入口 ====================
|
|
185
216
|
export async function runTui() {
|
|
186
217
|
const uninstallSignals = installSignalSafety();
|
|
218
|
+
// 接管 SDK 全局日志:关掉库自带的 console 兜底。必须在任何 Ink 渲染之前——
|
|
219
|
+
// SDK 的 logger 默认是 console.log,直写 stdout 会把界面撕坏
|
|
220
|
+
// (`load_mcp` 甚至会把整份 manifest 打出来)。AIZ_DEBUG_SDK=1 可改为落盘。
|
|
221
|
+
installSdkLogger();
|
|
222
|
+
// 按键诊断:AIZ_DEBUG_KEYS=1 时把终端环境 + 每次按键写进日志(默认关闭,零开销)
|
|
223
|
+
keyDebugStart();
|
|
187
224
|
try {
|
|
225
|
+
// 配置初始化必须早于任何 Agent 读取:
|
|
226
|
+
// - 目录 / config.json / 默认 Agent / 出厂策略表都在这里释放;
|
|
227
|
+
// - 历史 Agent 缺 `modelId` 的补齐也在这里(`ensureDefaultAgent`)。
|
|
228
|
+
// 而「续接上一轮会话」这条路径不会经过 `resolveTarget()`,少了这一步,
|
|
229
|
+
// 升级后第一次启动会直接报「Agent 未绑定模型」。
|
|
230
|
+
await ensureConfigDir();
|
|
188
231
|
// 启动界面(一次性)
|
|
189
232
|
await mount(_jsx(Splash, {}));
|
|
190
233
|
// 直达对话:优先续接「上一轮会话」,否则新建对话
|
|
191
234
|
let target = await resumeLastSession();
|
|
192
|
-
if (
|
|
193
|
-
// 续接目标引用了已从配置中删除的模型:退回默认模型但保留历史,
|
|
194
|
-
// 避免卡在「模型不存在」的报错屏而无处可走
|
|
195
|
-
const fallback = await resolveTarget().catch(() => null);
|
|
196
|
-
target = fallback
|
|
197
|
-
? {
|
|
198
|
-
...fallback,
|
|
199
|
-
messages: target.messages,
|
|
200
|
-
conversationId: target.conversationId,
|
|
201
|
-
conversationName: target.conversationName,
|
|
202
|
-
}
|
|
203
|
-
: null;
|
|
204
|
-
}
|
|
205
|
-
else if (!target) {
|
|
235
|
+
if (!target) {
|
|
206
236
|
try {
|
|
207
237
|
target = await resolveTarget();
|
|
208
238
|
}
|
|
@@ -215,7 +245,17 @@ export async function runTui() {
|
|
|
215
245
|
return;
|
|
216
246
|
}
|
|
217
247
|
// 端点凭据预检:未配置 API Key 时先弹出输入屏,而不是「报错即退出」
|
|
218
|
-
|
|
248
|
+
// 模型从 Agent 定义读 —— 与 SDK 实发请求用的是同一个值
|
|
249
|
+
let modelId = null;
|
|
250
|
+
try {
|
|
251
|
+
modelId = await modelOf(target.agentId);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
process.stderr.write(`\n✖ 无法启动对话:${error?.message ?? error}\n`);
|
|
255
|
+
farewell();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!(await ensureCredential(modelId))) {
|
|
219
259
|
farewell();
|
|
220
260
|
return;
|
|
221
261
|
}
|
|
@@ -236,6 +276,13 @@ function installSignalSafety() {
|
|
|
236
276
|
catch {
|
|
237
277
|
/* 忽略 */
|
|
238
278
|
}
|
|
279
|
+
// 任何异常退出路径都要把终端鼠标上报关掉,否则终端会持续把滚轮写成转义序列
|
|
280
|
+
try {
|
|
281
|
+
disableMouseTracking();
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
/* 忽略 */
|
|
285
|
+
}
|
|
239
286
|
activeInstance = null;
|
|
240
287
|
};
|
|
241
288
|
const onSigint = () => {
|
package/dist/tui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAiB,MAAM,KAAK,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输入框编辑的纯逻辑层 —— 与 Claude Code / readline 的键位语义对齐。
|
|
3
|
+
*
|
|
4
|
+
* 全部函数都是「(文本, 光标) → (文本, 光标)」的纯变换,便于单测;
|
|
5
|
+
* chat.tsx 只负责把按键映射过来、套用结果、维护 kill ring 与撤销栈。
|
|
6
|
+
*
|
|
7
|
+
* 键位语义依据 Claude Code 官方文档(interactive-mode.md):
|
|
8
|
+
* - `Ctrl+W` 删到**前一空白**(标点不切分:`src/utils/foo.ts` 一次删掉整条路径)
|
|
9
|
+
* - `Alt+B/F/D` 按「词」处理(标点算分隔符:停在 `ts` / `foo` / `utils` / `src`)
|
|
10
|
+
*/
|
|
11
|
+
/** 一段可撤销的编辑状态(文本 + code point 光标) */
|
|
12
|
+
export interface EditState {
|
|
13
|
+
text: string;
|
|
14
|
+
cursor: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 删到前一空白(Ctrl+W):先吃掉光标前的空白,再吃掉一整段非空白。
|
|
18
|
+
*
|
|
19
|
+
* 与 `deleteWordLeft` 的区别在于标点:本函数只认空白为边界,
|
|
20
|
+
* 因此 `src/utils/foo.ts` 会被整段删除,而不是只删 `foo.ts`。
|
|
21
|
+
*/
|
|
22
|
+
export declare function deleteToPrevWhitespace(text: string, cursor: number): EditState;
|
|
23
|
+
/**
|
|
24
|
+
* 求「被删掉的文本」:比较编辑前后,取中间不重合的那一段。
|
|
25
|
+
*
|
|
26
|
+
* 用于把 `Ctrl+W/U/K` 删掉的内容放进 kill ring 供 `Ctrl+Y` 取回,
|
|
27
|
+
* 免去每个删除函数各自返回「删了什么」。
|
|
28
|
+
*/
|
|
29
|
+
export declare function killedText(before: string, after: string): string;
|
|
30
|
+
export declare const KILL_RING_SIZE = 5;
|
|
31
|
+
/**
|
|
32
|
+
* 记录一段被删除的文本。
|
|
33
|
+
*
|
|
34
|
+
* 空串不入环;与栈顶相同的连续删除(如连按 `Ctrl+W` 删同一个词)不重复入栈,
|
|
35
|
+
* 否则 `Alt+Y` 会在同一段文本上空转。
|
|
36
|
+
*/
|
|
37
|
+
export declare function pushKill(ring: string[], killed: string, limit?: number): string[];
|
|
38
|
+
/** 取环中第 index 段(越界返回 undefined);index 0 为最近一次删除 */
|
|
39
|
+
export declare function killAt(ring: string[], index: number): string | undefined;
|
|
40
|
+
/** 撤销栈上限:编辑很密,但每条只是一个短字符串,100 条足够 */
|
|
41
|
+
export declare const UNDO_LIMIT = 100;
|
|
42
|
+
/** 入栈一条编辑前快照;与栈顶相同的状态不重复入栈 */
|
|
43
|
+
export declare function pushUndo(stack: EditState[], state: EditState, limit?: number): EditState[];
|
|
44
|
+
/** 出栈一条快照(无历史时返回 null) */
|
|
45
|
+
export declare function popUndo(stack: EditState[]): {
|
|
46
|
+
state: EditState;
|
|
47
|
+
rest: EditState[];
|
|
48
|
+
} | null;
|
|
49
|
+
//# sourceMappingURL=input-editing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-editing.d.ts","sourceRoot":"","sources":["../../src/tui/input-editing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhE;AAGD,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,SAAiB,GAAG,MAAM,EAAE,CAIzF;AAED,mDAAmD;AACnD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAID,qCAAqC;AACrC,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,8BAA8B;AAC9B,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,SAAa,GAAG,SAAS,EAAE,CAI9F;AAED,0BAA0B;AAC1B,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG,IAAI,CAG1F"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输入框编辑的纯逻辑层 —— 与 Claude Code / readline 的键位语义对齐。
|
|
3
|
+
*
|
|
4
|
+
* 全部函数都是「(文本, 光标) → (文本, 光标)」的纯变换,便于单测;
|
|
5
|
+
* chat.tsx 只负责把按键映射过来、套用结果、维护 kill ring 与撤销栈。
|
|
6
|
+
*
|
|
7
|
+
* 键位语义依据 Claude Code 官方文档(interactive-mode.md):
|
|
8
|
+
* - `Ctrl+W` 删到**前一空白**(标点不切分:`src/utils/foo.ts` 一次删掉整条路径)
|
|
9
|
+
* - `Alt+B/F/D` 按「词」处理(标点算分隔符:停在 `ts` / `foo` / `utils` / `src`)
|
|
10
|
+
*/
|
|
11
|
+
function clampCursor(chars, cursor) {
|
|
12
|
+
return Math.max(0, Math.min(cursor, chars.length));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 删到前一空白(Ctrl+W):先吃掉光标前的空白,再吃掉一整段非空白。
|
|
16
|
+
*
|
|
17
|
+
* 与 `deleteWordLeft` 的区别在于标点:本函数只认空白为边界,
|
|
18
|
+
* 因此 `src/utils/foo.ts` 会被整段删除,而不是只删 `foo.ts`。
|
|
19
|
+
*/
|
|
20
|
+
export function deleteToPrevWhitespace(text, cursor) {
|
|
21
|
+
const chars = Array.from(text);
|
|
22
|
+
const cp = clampCursor(chars, cursor);
|
|
23
|
+
let from = cp;
|
|
24
|
+
while (from > 0 && /\s/.test(chars[from - 1]))
|
|
25
|
+
from -= 1;
|
|
26
|
+
while (from > 0 && !/\s/.test(chars[from - 1]))
|
|
27
|
+
from -= 1;
|
|
28
|
+
return {
|
|
29
|
+
text: chars.slice(0, from).join("") + chars.slice(cp).join(""),
|
|
30
|
+
cursor: from,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 求「被删掉的文本」:比较编辑前后,取中间不重合的那一段。
|
|
35
|
+
*
|
|
36
|
+
* 用于把 `Ctrl+W/U/K` 删掉的内容放进 kill ring 供 `Ctrl+Y` 取回,
|
|
37
|
+
* 免去每个删除函数各自返回「删了什么」。
|
|
38
|
+
*/
|
|
39
|
+
export function killedText(before, after) {
|
|
40
|
+
const a = Array.from(before);
|
|
41
|
+
const b = Array.from(after);
|
|
42
|
+
let prefix = 0;
|
|
43
|
+
while (prefix < b.length && a[prefix] === b[prefix])
|
|
44
|
+
prefix += 1;
|
|
45
|
+
let suffix = 0;
|
|
46
|
+
while (suffix < b.length - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix])
|
|
47
|
+
suffix += 1;
|
|
48
|
+
return a.slice(prefix, a.length - suffix).join("");
|
|
49
|
+
}
|
|
50
|
+
// ==================== kill ring(Ctrl+Y / Alt+Y)====================/** kill ring 最大长度(够 Alt+Y 循环几次即可,不做无限历史) */
|
|
51
|
+
export const KILL_RING_SIZE = 5;
|
|
52
|
+
/**
|
|
53
|
+
* 记录一段被删除的文本。
|
|
54
|
+
*
|
|
55
|
+
* 空串不入环;与栈顶相同的连续删除(如连按 `Ctrl+W` 删同一个词)不重复入栈,
|
|
56
|
+
* 否则 `Alt+Y` 会在同一段文本上空转。
|
|
57
|
+
*/
|
|
58
|
+
export function pushKill(ring, killed, limit = KILL_RING_SIZE) {
|
|
59
|
+
if (!killed)
|
|
60
|
+
return ring;
|
|
61
|
+
if (ring[0] === killed)
|
|
62
|
+
return ring;
|
|
63
|
+
return [killed, ...ring].slice(0, limit);
|
|
64
|
+
}
|
|
65
|
+
/** 取环中第 index 段(越界返回 undefined);index 0 为最近一次删除 */
|
|
66
|
+
export function killAt(ring, index) {
|
|
67
|
+
if (index < 0 || index >= ring.length)
|
|
68
|
+
return undefined;
|
|
69
|
+
return ring[index];
|
|
70
|
+
}
|
|
71
|
+
// ==================== 撤销栈(Ctrl+_)====================
|
|
72
|
+
/** 撤销栈上限:编辑很密,但每条只是一个短字符串,100 条足够 */
|
|
73
|
+
export const UNDO_LIMIT = 100;
|
|
74
|
+
/** 入栈一条编辑前快照;与栈顶相同的状态不重复入栈 */
|
|
75
|
+
export function pushUndo(stack, state, limit = UNDO_LIMIT) {
|
|
76
|
+
const top = stack[stack.length - 1];
|
|
77
|
+
if (top && top.text === state.text && top.cursor === state.cursor)
|
|
78
|
+
return stack;
|
|
79
|
+
return [...stack, state].slice(-limit);
|
|
80
|
+
}
|
|
81
|
+
/** 出栈一条快照(无历史时返回 null) */
|
|
82
|
+
export function popUndo(stack) {
|
|
83
|
+
if (stack.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
return { state: stack[stack.length - 1], rest: stack.slice(0, -1) };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=input-editing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-editing.js","sourceRoot":"","sources":["../../src/tui/input-editing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,SAAS,WAAW,CAAC,KAAe,EAAE,MAAc;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,MAAc;IACjE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAAE,IAAI,IAAI,CAAC,CAAC;IACzD,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAAE,IAAI,IAAI,CAAC,CAAC;IAC1D,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9D,MAAM,EAAE,IAAI;KACb,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,KAAa;IACtD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,CAAC,CAAC;IACjE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC;QAAE,MAAM,IAAI,CAAC,CAAC;IACxG,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,iHAAiH;AACjH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,MAAc,EAAE,KAAK,GAAG,cAAc;IAC7E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,MAAM,CAAC,IAAc,EAAE,KAAa;IAClD,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,uDAAuD;AAEvD,qCAAqC;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC;AAE9B,8BAA8B;AAC9B,MAAM,UAAU,QAAQ,CAAC,KAAkB,EAAE,KAAgB,EAAE,KAAK,GAAG,UAAU;IAC/E,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpC,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAChF,OAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,OAAO,CAAC,KAAkB;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按键诊断 —— 不同终端的按键编码差异极大(macOS 的 Option、PageUp/PageDown 尤其),
|
|
3
|
+
* 「某个键在我机器上没反应」这类问题靠猜毫无意义:要么键被 OS/终端吞掉,
|
|
4
|
+
* 要么终端发来的序列和我们认的不一样。两种情况的判定方法是同一个 —— 看日志里有几行。
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* AIZ_DEBUG_KEYS=1 aiz → 追加写到 <tmp>/aiz-key-debug.log
|
|
8
|
+
* AIZ_DEBUG_KEYS=1 AIZ_KEYS_FILE=/path aiz → 写到指定文件
|
|
9
|
+
*
|
|
10
|
+
* 判读:
|
|
11
|
+
* - 按了键但日志里没有新增行 → 键根本没到应用(被 OS 快捷键或终端键位映射截走)
|
|
12
|
+
* - 有行但 `input`/`flags` 与预期不符 → 序列认识,是匹配逻辑的问题
|
|
13
|
+
* - 有行且 `raw=false` → 终端没处于 raw mode,键被终端自己解释了(如滚动 scrollback)
|
|
14
|
+
*
|
|
15
|
+
* 刻意在 `useInput` 回调里记录而不是直接监听 `stdin` 的 data 事件:
|
|
16
|
+
* 后者会把流切成 flowing 模式,与 Ink 内部的 readable 读取抢数据。
|
|
17
|
+
*/
|
|
18
|
+
export declare const KEY_DEBUG_ENABLED: boolean;
|
|
19
|
+
/** 日志落盘路径(测试与用户都可能改写,故导出常量) */
|
|
20
|
+
export declare const KEY_DEBUG_FILE: string;
|
|
21
|
+
/** Ink `useInput` 的 key 对象里我们关心的字段(结构化,避免依赖 ink 的类型导出) */
|
|
22
|
+
export interface KeyFlags {
|
|
23
|
+
upArrow?: boolean;
|
|
24
|
+
downArrow?: boolean;
|
|
25
|
+
leftArrow?: boolean;
|
|
26
|
+
rightArrow?: boolean;
|
|
27
|
+
pageUp?: boolean;
|
|
28
|
+
pageDown?: boolean;
|
|
29
|
+
return?: boolean;
|
|
30
|
+
escape?: boolean;
|
|
31
|
+
tab?: boolean;
|
|
32
|
+
backspace?: boolean;
|
|
33
|
+
delete?: boolean;
|
|
34
|
+
home?: boolean;
|
|
35
|
+
end?: boolean;
|
|
36
|
+
ctrl?: boolean;
|
|
37
|
+
shift?: boolean;
|
|
38
|
+
meta?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** 终端环境摘要:排查「只在某终端失灵」时先看这一行 */
|
|
41
|
+
export declare function terminalSummary(env?: NodeJS.ProcessEnv): string;
|
|
42
|
+
/**
|
|
43
|
+
* 把一次按键格式化成单行记录。
|
|
44
|
+
*
|
|
45
|
+
* `input` 用 JSON.stringify 转义(控制字符会变成 `\u001b` 这类可读形式),
|
|
46
|
+
* `raw` 表示按下时 stdin 是否仍在 raw mode —— 为 false 时键会被终端自己吃掉。
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatKeyEvent(input: string, key: KeyFlags, raw: boolean): string;
|
|
49
|
+
/** 当前 stdin 是否处于 raw mode(非 TTY 或平台不支持时视为 false) */
|
|
50
|
+
export declare function stdinIsRaw(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* 记录一行到诊断日志;仅在 `AIZ_DEBUG_KEYS=1` 时生效。
|
|
53
|
+
*
|
|
54
|
+
* 同步写:调试期每次按键都写一行,量小;异步写反而可能在崩溃时丢最后几行
|
|
55
|
+
* —— 而那几行往往正是要看的那几行。
|
|
56
|
+
*/
|
|
57
|
+
export declare function keyDebugLog(line: string): void;
|
|
58
|
+
/** 会话开始时写一行环境摘要(同一次运行只写一次) */
|
|
59
|
+
export declare function keyDebugStart(): void;
|
|
60
|
+
/** 记录一次按键 */
|
|
61
|
+
export declare function keyDebug(input: string, key: KeyFlags): void;
|
|
62
|
+
//# sourceMappingURL=key-debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-debug.d.ts","sourceRoot":"","sources":["../../src/tui/key-debug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,eAAO,MAAM,iBAAiB,SAAqC,CAAC;AAEpE,+BAA+B;AAC/B,eAAO,MAAM,cAAc,QAAwD,CAAC;AAEpF,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+BAA+B;AAC/B,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAK5E;AAqBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAGjF;AAED,oDAAoD;AACpD,wBAAgB,UAAU,IAAI,OAAO,CAGpC;AAID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO9C;AAED,8BAA8B;AAC9B,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED,aAAa;AACb,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAG3D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按键诊断 —— 不同终端的按键编码差异极大(macOS 的 Option、PageUp/PageDown 尤其),
|
|
3
|
+
* 「某个键在我机器上没反应」这类问题靠猜毫无意义:要么键被 OS/终端吞掉,
|
|
4
|
+
* 要么终端发来的序列和我们认的不一样。两种情况的判定方法是同一个 —— 看日志里有几行。
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* AIZ_DEBUG_KEYS=1 aiz → 追加写到 <tmp>/aiz-key-debug.log
|
|
8
|
+
* AIZ_DEBUG_KEYS=1 AIZ_KEYS_FILE=/path aiz → 写到指定文件
|
|
9
|
+
*
|
|
10
|
+
* 判读:
|
|
11
|
+
* - 按了键但日志里没有新增行 → 键根本没到应用(被 OS 快捷键或终端键位映射截走)
|
|
12
|
+
* - 有行但 `input`/`flags` 与预期不符 → 序列认识,是匹配逻辑的问题
|
|
13
|
+
* - 有行且 `raw=false` → 终端没处于 raw mode,键被终端自己解释了(如滚动 scrollback)
|
|
14
|
+
*
|
|
15
|
+
* 刻意在 `useInput` 回调里记录而不是直接监听 `stdin` 的 data 事件:
|
|
16
|
+
* 后者会把流切成 flowing 模式,与 Ink 内部的 readable 读取抢数据。
|
|
17
|
+
*/
|
|
18
|
+
import { appendFileSync } from "node:fs";
|
|
19
|
+
export const KEY_DEBUG_ENABLED = process.env.AIZ_DEBUG_KEYS === "1";
|
|
20
|
+
/** 日志落盘路径(测试与用户都可能改写,故导出常量) */
|
|
21
|
+
export const KEY_DEBUG_FILE = process.env.AIZ_KEYS_FILE || "/tmp/aiz-key-debug.log";
|
|
22
|
+
/** 终端环境摘要:排查「只在某终端失灵」时先看这一行 */
|
|
23
|
+
export function terminalSummary(env = process.env) {
|
|
24
|
+
const keys = ["TERM", "TERM_PROGRAM", "TERM_PROGRAM_VERSION", "COLORTERM", "LANG", "COLUMNS", "LINES"];
|
|
25
|
+
const parts = keys.map((k) => `${k}=${env[k] ?? "-"}`);
|
|
26
|
+
parts.push(`platform=${process.platform}`, `node=${process.version}`);
|
|
27
|
+
return parts.join(" ");
|
|
28
|
+
}
|
|
29
|
+
const FLAG_NAMES = [
|
|
30
|
+
"ctrl",
|
|
31
|
+
"meta",
|
|
32
|
+
"shift",
|
|
33
|
+
"upArrow",
|
|
34
|
+
"downArrow",
|
|
35
|
+
"leftArrow",
|
|
36
|
+
"rightArrow",
|
|
37
|
+
"pageUp",
|
|
38
|
+
"pageDown",
|
|
39
|
+
"home",
|
|
40
|
+
"end",
|
|
41
|
+
"return",
|
|
42
|
+
"escape",
|
|
43
|
+
"tab",
|
|
44
|
+
"backspace",
|
|
45
|
+
"delete",
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* 把一次按键格式化成单行记录。
|
|
49
|
+
*
|
|
50
|
+
* `input` 用 JSON.stringify 转义(控制字符会变成 `\u001b` 这类可读形式),
|
|
51
|
+
* `raw` 表示按下时 stdin 是否仍在 raw mode —— 为 false 时键会被终端自己吃掉。
|
|
52
|
+
*/
|
|
53
|
+
export function formatKeyEvent(input, key, raw) {
|
|
54
|
+
const flags = FLAG_NAMES.filter((name) => key[name]).join(",");
|
|
55
|
+
return `key input=${JSON.stringify(input ?? "")} flags=[${flags}] raw=${raw}`;
|
|
56
|
+
}
|
|
57
|
+
/** 当前 stdin 是否处于 raw mode(非 TTY 或平台不支持时视为 false) */
|
|
58
|
+
export function stdinIsRaw() {
|
|
59
|
+
const stdin = process.stdin;
|
|
60
|
+
return Boolean(stdin.isRaw);
|
|
61
|
+
}
|
|
62
|
+
let started = false;
|
|
63
|
+
/**
|
|
64
|
+
* 记录一行到诊断日志;仅在 `AIZ_DEBUG_KEYS=1` 时生效。
|
|
65
|
+
*
|
|
66
|
+
* 同步写:调试期每次按键都写一行,量小;异步写反而可能在崩溃时丢最后几行
|
|
67
|
+
* —— 而那几行往往正是要看的那几行。
|
|
68
|
+
*/
|
|
69
|
+
export function keyDebugLog(line) {
|
|
70
|
+
if (!KEY_DEBUG_ENABLED)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
appendFileSync(KEY_DEBUG_FILE, `${new Date().toISOString()} ${line}\n`);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* 诊断失败绝不能影响主流程 */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** 会话开始时写一行环境摘要(同一次运行只写一次) */
|
|
80
|
+
export function keyDebugStart() {
|
|
81
|
+
if (!KEY_DEBUG_ENABLED || started)
|
|
82
|
+
return;
|
|
83
|
+
started = true;
|
|
84
|
+
keyDebugLog(`=== start ${terminalSummary()} file=${KEY_DEBUG_FILE} ===`);
|
|
85
|
+
}
|
|
86
|
+
/** 记录一次按键 */
|
|
87
|
+
export function keyDebug(input, key) {
|
|
88
|
+
if (!KEY_DEBUG_ENABLED)
|
|
89
|
+
return;
|
|
90
|
+
keyDebugLog(formatKeyEvent(input, key, stdinIsRaw()));
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=key-debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-debug.js","sourceRoot":"","sources":["../../src/tui/key-debug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC;AAEpE,+BAA+B;AAC/B,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,wBAAwB,CAAC;AAsBpF,+BAA+B;AAC/B,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACvG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,QAAQ,EAAE,EAAE,QAAQ,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,GAAuB;IACrC,MAAM;IACN,MAAM;IACN,OAAO;IACP,SAAS;IACT,WAAW;IACX,WAAW;IACX,YAAY;IACZ,QAAQ;IACR,UAAU;IACV,MAAM;IACN,KAAK;IACL,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,WAAW;IACX,QAAQ;CACT,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,GAAa,EAAE,GAAY;IACvE,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,OAAO,aAAa,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,KAAK,SAAS,GAAG,EAAE,CAAC;AAChF,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAA4B,CAAC;IACnD,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC,iBAAiB;QAAE,OAAO;IAC/B,IAAI,CAAC;QACH,cAAc,CAAC,cAAc,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;AACH,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,iBAAiB,IAAI,OAAO;QAAE,OAAO;IAC1C,OAAO,GAAG,IAAI,CAAC;IACf,WAAW,CAAC,aAAa,eAAe,EAAE,SAAS,cAAc,MAAM,CAAC,CAAC;AAC3E,CAAC;AAED,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,GAAa;IACnD,IAAI,CAAC,iBAAiB;QAAE,OAAO;IAC/B,WAAW,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 鼠标滚轮支持 —— 终端模式开关 + SGR 上报解析
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要自己实现:Ink 7 没有 `useMouse`(整个 `ink/build` 里 "mouse" 只出现在
|
|
5
|
+
* `measure-element.js` 的一句注释中),终端也默认**不把**鼠标事件发给应用——滚轮被
|
|
6
|
+
* 终端自己拿去滚 scrollback。要收到滚轮,必须由应用主动打开终端上报模式。
|
|
7
|
+
*
|
|
8
|
+
* 两种编码,只认后者:
|
|
9
|
+
* - `DECSET 1000`:上报「按钮按下 / 释放」(滚轮也算按钮),是收到滚轮的前提;
|
|
10
|
+
* - `DECSET 1006`:改用 SGR 扩展坐标 `CSI < b ; x ; y M`(释放为 `m`)。**必须开**:
|
|
11
|
+
* 老的 X10 编码(`CSI M` + 3 个原始字节)会被 Ink 的输入解析器拆成 `[M` 和三个
|
|
12
|
+
* 普通字符两条事件,后者会被当正文插进输入框(已实测重现)。
|
|
13
|
+
*
|
|
14
|
+
* 上报到 Ink 的形态(pty 实测):`useInput` 的 `char` 是 `"[<64;10;5M"`,`key` 的
|
|
15
|
+
* 所有标志位都是 false。也就是说**鼠标序列和用户打字走的是同一条路**——任何可能插入
|
|
16
|
+
* 文本的处理器都必须先拦一道(见 `isTerminalReply` 与各屏守卫)。
|
|
17
|
+
*
|
|
18
|
+
* 取舍(用户已知情并选择开启):
|
|
19
|
+
* - 点击 / 拖拽被应用吃掉,多数终端需按住 `Shift` 才回退到原生选词复制;
|
|
20
|
+
* - tmux / screen 需 `set -g mouse off`,否则滚轮先被多路复用器拦进 copy-mode;
|
|
21
|
+
* - 应用内滚动只动视口,不产生终端 scrollback。
|
|
22
|
+
*/
|
|
23
|
+
/** 开启上报:1000(按钮事件,含滚轮)+ 1006(SGR 扩展坐标) */
|
|
24
|
+
export declare const MOUSE_ENABLE = "\u001B[?1000h\u001B[?1006h";
|
|
25
|
+
/** 关闭上报。任何退出路径都必须下发,否则 shell 提示符后面会开始吐 `^[[<64;…M` */
|
|
26
|
+
export declare const MOUSE_DISABLE = "\u001B[?1000l\u001B[?1006l";
|
|
27
|
+
/** 滚轮一格滚动的行数(各终端应用的通行值) */
|
|
28
|
+
export declare const WHEEL_STEP = 3;
|
|
29
|
+
export interface WheelEvent {
|
|
30
|
+
/** `-1` = 向上滚(回看更早的内容),`1` = 向下滚(回到更新的内容) */
|
|
31
|
+
direction: -1 | 1;
|
|
32
|
+
/** 终端列(1-based),当前仅用于将来做「按鼠标位置定位」 */
|
|
33
|
+
x: number;
|
|
34
|
+
/** 终端行(1-based) */
|
|
35
|
+
y: number;
|
|
36
|
+
}
|
|
37
|
+
/** 是否为鼠标上报序列(滚轮、点击、拖拽、移动都算) */
|
|
38
|
+
export declare function isMouseSequence(input: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* 从鼠标序列中解出滚轮事件;不是滚轮(点击 / 拖拽 / 横向滚轮)返回 `null`。
|
|
41
|
+
*
|
|
42
|
+
* SGR 的按钮码:`64` 滚轮上、`65` 滚轮下、`66` / `67` 横向滚轮。横向在这个视口里
|
|
43
|
+
* 没有对应维度,直接丢弃——否则横向滚一下就莫名其妙跳几行。
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseWheel(input: string): WheelEvent | null;
|
|
46
|
+
/**
|
|
47
|
+
* 打开终端鼠标上报。
|
|
48
|
+
*
|
|
49
|
+
* 输出的是纯模式设置序列(不打印字符、不移动光标),因此可以安全地直接写 stdout,
|
|
50
|
+
* 不会与 Ink 的增量重绘打架。重复下发是幂等的。
|
|
51
|
+
*/
|
|
52
|
+
export declare function enableMouseTracking(stream?: NodeJS.WriteStream): void;
|
|
53
|
+
/** 关闭终端鼠标上报(幂等)。退出 / 信号 / 异常路径都要调用。 */
|
|
54
|
+
export declare function disableMouseTracking(stream?: NodeJS.WriteStream): void;
|
|
55
|
+
//# sourceMappingURL=mouse.d.ts.map
|