@miphamai/cli 0.79.0 → 0.80.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -43,17 +43,17 @@
43
43
  "dependencies": {
44
44
  "@larksuiteoapi/node-sdk": "^1.73.0",
45
45
  "commander": "^13.1.0",
46
- "ink": "^5.2.1",
46
+ "ink": "^7.1.1",
47
47
  "ink-text-input": "^6.0.0",
48
- "react": "^18.3.1",
49
- "react-devtools-core": "^4.28.5",
48
+ "react": "^19.3.0",
49
+ "react-devtools-core": "^8.0.0",
50
50
  "yaml": "^2.9.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@mipham/shared": "workspace:*",
54
54
  "@types/bun": "^1.3.14",
55
55
  "@types/node": "^22.19.19",
56
- "@types/react": "^18.3.29",
56
+ "@types/react": "^19.3.0",
57
57
  "ink-testing-library": "^4.0.0",
58
58
  "vitest": "^4.1.7"
59
59
  }
@@ -9,7 +9,7 @@
9
9
  export const PACKAGE_NAME = '@miphamai/cli' as const
10
10
 
11
11
  /** 当前发布版本 */
12
- export const PACKAGE_VERSION = '0.79.0' as const
12
+ export const PACKAGE_VERSION = '0.80.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/input.tsx CHANGED
@@ -130,6 +130,65 @@ export function applyEdit(state: EditState, action: EditAction): EditState {
130
130
  }
131
131
  }
132
132
 
133
+ /**
134
+ * Map an Ink key object + raw input to a cursor-editing action (null when the key
135
+ * isn't an edit). Extracted from the MiphamTextInput useInput handler so the macOS
136
+ * Backspace quirk is unit-testable.
137
+ *
138
+ * Ink 7 correctly distinguishes the physical Backspace key (terminal sends \x7f →
139
+ * `key.backspace`) from the forward-Delete key (\x1b[3~ → `key.delete`). Ink 5.2.1
140
+ * misparsed both as `key.delete`, which the old backspace fix worked around by
141
+ * treating `key.delete` as backward-delete.
142
+ */
143
+ export function keyToEditAction(
144
+ key: { leftArrow?: boolean; rightArrow?: boolean; backspace?: boolean; delete?: boolean },
145
+ input: string,
146
+ ): EditAction | null {
147
+ if (key.leftArrow) return { type: 'moveLeft' }
148
+ if (key.rightArrow) return { type: 'moveRight' }
149
+ if (key.backspace) return { type: 'backspace' }
150
+ if (key.delete) return { type: 'delete' }
151
+ const cleaned = normalizeInput(input)
152
+ if (cleaned) return { type: 'insert', text: cleaned }
153
+ return null
154
+ }
155
+
156
+ /** Browsing state for arrow-key history navigation. */
157
+ export interface HistoryNavState {
158
+ history: string[]
159
+ index: number // -1 = not browsing
160
+ savedDraft: string // draft saved on first up-arrow
161
+ }
162
+
163
+ /**
164
+ * Pure history-navigation transition. Returns the value to display plus the
165
+ * updated browsing state, or null when the key is a no-op (empty history for up,
166
+ * or not browsing for down).
167
+ */
168
+ export function navigateHistory(
169
+ state: HistoryNavState,
170
+ dir: 'up' | 'down',
171
+ draft: string,
172
+ ): { index: number; savedDraft: string; value: string } | null {
173
+ if (dir === 'up') {
174
+ if (state.history.length === 0) return null
175
+ const savedDraft = state.index === -1 ? draft : state.savedDraft
176
+ const index = Math.min(state.index + 1, state.history.length - 1)
177
+ return { index, savedDraft, value: state.history[state.history.length - 1 - index]! }
178
+ }
179
+ // down
180
+ if (state.index === -1) return null
181
+ const index = state.index - 1
182
+ if (index === -1) {
183
+ return { index, savedDraft: '', value: state.savedDraft }
184
+ }
185
+ return {
186
+ index,
187
+ savedDraft: state.savedDraft,
188
+ value: state.history[state.history.length - 1 - index]!,
189
+ }
190
+ }
191
+
133
192
  /** True when typing a leading `/` should auto-open the slash-command picker. */
134
193
  export function shouldAutoOpenPicker(value: string, prevValue: string, enabled: boolean): boolean {
135
194
  return enabled && value.startsWith('/') && !prevValue.startsWith('/')
@@ -188,16 +247,10 @@ function MiphamTextInput({
188
247
  }
189
248
 
190
249
  // 把按键归一化为一次光标编辑:左/右移动,退格/删除,或光标处插入(打字/粘贴)。
250
+ // macOS Backspace 发 \x7f,被 Ink 映射成 key.delete(非 key.backspace),
251
+ // 故 keyToEditAction 里两者都按向后删处理。
191
252
  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
- }
253
+ const action = keyToEditAction(key, input)
201
254
  if (!action) return
202
255
 
203
256
  const next = applyEdit(state, action)
@@ -379,30 +432,19 @@ export function InputBar({
379
432
  // Ignore if picker is active (command picker handles its own arrows)
380
433
  if (value.startsWith('/')) return
381
434
 
382
- if (key.upArrow) {
383
- if (submittedHistory.length === 0) return
384
- // Save current draft the first time we enter history browsing
385
- if (historyIndexRef.current === -1) {
386
- // Save the latest value from the ref — state `value` may lag a render.
387
- savedDraftRef.current = valueRef.current
388
- }
389
- const newIndex = Math.min(historyIndexRef.current + 1, submittedHistory.length - 1)
390
- historyIndexRef.current = newIndex
391
- setValue(submittedHistory[submittedHistory.length - 1 - newIndex]!)
392
- return
393
- }
394
- if (key.downArrow) {
395
- if (historyIndexRef.current === -1) return
396
- const newIndex = historyIndexRef.current - 1
397
- historyIndexRef.current = newIndex
398
- if (newIndex === -1) {
399
- // Back to the original draft
400
- setValue(savedDraftRef.current)
401
- savedDraftRef.current = ''
402
- } else {
403
- setValue(submittedHistory[submittedHistory.length - 1 - newIndex]!)
404
- }
405
- return
435
+ const result = navigateHistory(
436
+ {
437
+ history: submittedHistory,
438
+ index: historyIndexRef.current,
439
+ savedDraft: savedDraftRef.current,
440
+ },
441
+ key.upArrow ? 'up' : 'down',
442
+ valueRef.current,
443
+ )
444
+ if (result) {
445
+ historyIndexRef.current = result.index
446
+ savedDraftRef.current = result.savedDraft
447
+ setValue(result.value)
406
448
  }
407
449
  }
408
450
  })