@miphamai/cli 0.78.0 → 0.79.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/package.json +1 -1
- package/src/shared/package-info.ts +1 -1
- package/src/ui/app.tsx +1 -1
- package/src/ui/chat.tsx +1 -3
- package/src/ui/graft-status.tsx +4 -4
- package/src/ui/input.tsx +79 -20
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.
|
|
12
|
+
export const PACKAGE_VERSION = '0.79.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
package/src/ui/app.tsx
CHANGED
|
@@ -1244,7 +1244,7 @@ export function App({
|
|
|
1244
1244
|
)}
|
|
1245
1245
|
|
|
1246
1246
|
{/* Status line — Claude Code style */}
|
|
1247
|
-
<Box
|
|
1247
|
+
<Box flexDirection="column">
|
|
1248
1248
|
<Box flexDirection="row">
|
|
1249
1249
|
<Text color={PERMISSION_COLORS[permissionMode]}>
|
|
1250
1250
|
⏵⏵ {PERMISSION_LABELS[permissionMode]}
|
package/src/ui/chat.tsx
CHANGED
|
@@ -135,9 +135,7 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
135
135
|
</Box>
|
|
136
136
|
<Text dimColor>{t('ui.banner.tagline')}</Text>
|
|
137
137
|
<Box marginTop={1}>
|
|
138
|
-
<Text dimColor>
|
|
139
|
-
{t('ui.banner.start_message')} <Text color="yellow">/help</Text>
|
|
140
|
-
</Text>
|
|
138
|
+
<Text dimColor>{t('ui.banner.start_message')}</Text>
|
|
141
139
|
</Box>
|
|
142
140
|
<Box marginTop={1}>
|
|
143
141
|
<Text dimColor>{t('ui.banner.controls_hint')}</Text>
|
package/src/ui/graft-status.tsx
CHANGED
|
@@ -38,16 +38,16 @@ export function GraftStatusLine({ cwd, ctxPct }: { cwd: string; ctxPct?: number
|
|
|
38
38
|
<Box flexDirection="column">
|
|
39
39
|
{stats && fresh && (
|
|
40
40
|
<Box>
|
|
41
|
-
<Text
|
|
41
|
+
<Text>◤ </Text>
|
|
42
42
|
<Text color="blue">graft</Text>
|
|
43
|
-
<Text
|
|
43
|
+
<Text>
|
|
44
44
|
{' '}
|
|
45
45
|
· {stats.nodeCount} nodes / {stats.edgeCount} edges ·{' '}
|
|
46
46
|
</Text>
|
|
47
47
|
<Text color={fresh.color}>{fresh.label}</Text>
|
|
48
48
|
{saved > 0 && (
|
|
49
49
|
<>
|
|
50
|
-
<Text
|
|
50
|
+
<Text> · </Text>
|
|
51
51
|
<Text color="blue">~{saved.toLocaleString()} tok saved</Text>
|
|
52
52
|
</>
|
|
53
53
|
)}
|
|
@@ -55,7 +55,7 @@ export function GraftStatusLine({ cwd, ctxPct }: { cwd: string; ctxPct?: number
|
|
|
55
55
|
)}
|
|
56
56
|
{bottom.length > 0 && (
|
|
57
57
|
<Box>
|
|
58
|
-
<Text
|
|
58
|
+
<Text>▸ {bottom.join(' · ')}</Text>
|
|
59
59
|
</Box>
|
|
60
60
|
)}
|
|
61
61
|
</Box>
|
package/src/ui/input.tsx
CHANGED
|
@@ -86,6 +86,50 @@ export function normalizeInput(input: string): string {
|
|
|
86
86
|
return input.replace(/[\r\n\t]+/g, ' ')
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** Cursor-aware edit state — value plus the insertion point (0..value.length). */
|
|
90
|
+
export interface EditState {
|
|
91
|
+
value: string
|
|
92
|
+
cursor: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A single editing keystroke, normalized away from Ink's key object for testability. */
|
|
96
|
+
export type EditAction =
|
|
97
|
+
| { type: 'moveLeft' }
|
|
98
|
+
| { type: 'moveRight' }
|
|
99
|
+
| { type: 'backspace' }
|
|
100
|
+
| { type: 'delete' }
|
|
101
|
+
| { type: 'insert'; text: string }
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pure cursor-editing transition. Returns a new state (or the same state when
|
|
105
|
+
* the action is a no-op). Cursor is always clamped to [0, value.length].
|
|
106
|
+
*/
|
|
107
|
+
export function applyEdit(state: EditState, action: EditAction): EditState {
|
|
108
|
+
const { value, cursor } = state
|
|
109
|
+
switch (action.type) {
|
|
110
|
+
case 'moveLeft':
|
|
111
|
+
return { value, cursor: Math.max(0, cursor - 1) }
|
|
112
|
+
case 'moveRight':
|
|
113
|
+
return { value, cursor: Math.min(value.length, cursor + 1) }
|
|
114
|
+
case 'backspace':
|
|
115
|
+
if (cursor === 0) return state
|
|
116
|
+
return {
|
|
117
|
+
value: value.slice(0, cursor - 1) + value.slice(cursor),
|
|
118
|
+
cursor: cursor - 1,
|
|
119
|
+
}
|
|
120
|
+
case 'delete':
|
|
121
|
+
if (cursor >= value.length) return state
|
|
122
|
+
return { value: value.slice(0, cursor) + value.slice(cursor + 1), cursor }
|
|
123
|
+
case 'insert': {
|
|
124
|
+
if (!action.text) return state
|
|
125
|
+
return {
|
|
126
|
+
value: value.slice(0, cursor) + action.text + value.slice(cursor),
|
|
127
|
+
cursor: cursor + action.text.length,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
89
133
|
/** True when typing a leading `/` should auto-open the slash-command picker. */
|
|
90
134
|
export function shouldAutoOpenPicker(value: string, prevValue: string, enabled: boolean): boolean {
|
|
91
135
|
return enabled && value.startsWith('/') && !prevValue.startsWith('/')
|
|
@@ -99,8 +143,8 @@ export function shouldAutoOpenPicker(value: string, prevValue: string, enabled:
|
|
|
99
143
|
* 边界拆成多块,同一轮 synchronous flush 里后续块读到的仍是旧值,于是「覆盖
|
|
100
144
|
* 前块 / 插到中段」,表现为粘贴内容乱序、丢失、冻住。
|
|
101
145
|
*
|
|
102
|
-
* 这里用 ref 做同步真值:每块按当前 ref
|
|
103
|
-
* React 渲染时序,分块粘贴自然累积成完整文本。
|
|
146
|
+
* 这里用 ref 做同步真值:每块按当前 ref 在光标处原子插入(默认光标在末尾),
|
|
147
|
+
* 不依赖 React 渲染时序,分块粘贴自然累积成完整文本。
|
|
104
148
|
*/
|
|
105
149
|
function MiphamTextInput({
|
|
106
150
|
value,
|
|
@@ -117,10 +161,19 @@ function MiphamTextInput({
|
|
|
117
161
|
}) {
|
|
118
162
|
// 同步真值:valueRef 永远是最新文本;受控 value 仅在渲染时落后于 ref。
|
|
119
163
|
const valueRef = useRef(value)
|
|
164
|
+
// 光标插入点(0..value.length):cursorRef 供 useInput 同步读,cursor state 驱动渲染。
|
|
165
|
+
const [cursor, setCursor] = useState(value.length)
|
|
166
|
+
const cursorRef = useRef(cursor)
|
|
120
167
|
|
|
121
|
-
//
|
|
168
|
+
// 外部改动(箭头历史回填、提交/Esc 清空)时把真值对齐回受控 prop,光标归位到末尾。
|
|
169
|
+
// 组件自身 edit 在 onChange 前已同步更新 valueRef,故 ref 与 prop 相等时跳过,
|
|
170
|
+
// 避免把「中段插入」后的光标错误拉回末尾。
|
|
122
171
|
useEffect(() => {
|
|
123
|
-
valueRef.current
|
|
172
|
+
if (valueRef.current !== value) {
|
|
173
|
+
valueRef.current = value
|
|
174
|
+
cursorRef.current = value.length
|
|
175
|
+
setCursor(value.length)
|
|
176
|
+
}
|
|
124
177
|
}, [value])
|
|
125
178
|
|
|
126
179
|
useInput(
|
|
@@ -133,21 +186,26 @@ function MiphamTextInput({
|
|
|
133
186
|
onSubmit(valueRef.current)
|
|
134
187
|
return
|
|
135
188
|
}
|
|
136
|
-
if (key.backspace || key.delete) {
|
|
137
|
-
if (valueRef.current.length > 0) {
|
|
138
|
-
const next = valueRef.current.slice(0, -1)
|
|
139
|
-
valueRef.current = next
|
|
140
|
-
onChange(next)
|
|
141
|
-
}
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
189
|
|
|
145
|
-
//
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
190
|
+
// 把按键归一化为一次光标编辑:左/右移动,退格/删除,或光标处插入(打字/粘贴)。
|
|
191
|
+
const state: EditState = { value: valueRef.current, cursor: cursorRef.current }
|
|
192
|
+
let action: EditAction | null = null
|
|
193
|
+
if (key.leftArrow) action = { type: 'moveLeft' }
|
|
194
|
+
else if (key.rightArrow) action = { type: 'moveRight' }
|
|
195
|
+
else if (key.backspace) action = { type: 'backspace' }
|
|
196
|
+
else if (key.delete) action = { type: 'delete' }
|
|
197
|
+
else {
|
|
198
|
+
const cleaned = normalizeInput(input)
|
|
199
|
+
if (cleaned) action = { type: 'insert', text: cleaned }
|
|
200
|
+
}
|
|
201
|
+
if (!action) return
|
|
202
|
+
|
|
203
|
+
const next = applyEdit(state, action)
|
|
204
|
+
valueRef.current = next.value
|
|
205
|
+
cursorRef.current = next.cursor
|
|
206
|
+
setCursor(next.cursor)
|
|
207
|
+
// 纯移动不通知父组件(文本未变),只在文本变化时 onChange。
|
|
208
|
+
if (next.value !== state.value) onChange(next.value)
|
|
151
209
|
},
|
|
152
210
|
{ isActive: focus },
|
|
153
211
|
)
|
|
@@ -158,8 +216,9 @@ function MiphamTextInput({
|
|
|
158
216
|
<Text dimColor>{placeholder}</Text>
|
|
159
217
|
) : (
|
|
160
218
|
<>
|
|
161
|
-
{value}
|
|
162
|
-
<Text inverse> </Text>
|
|
219
|
+
{value.slice(0, cursor)}
|
|
220
|
+
<Text inverse>{value[cursor] ?? ' '}</Text>
|
|
221
|
+
{value.slice(cursor + 1)}
|
|
163
222
|
</>
|
|
164
223
|
)}
|
|
165
224
|
</Text>
|