@miphamai/cli 0.5.7 → 0.5.9

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/src/ui/input.tsx CHANGED
@@ -1,7 +1,9 @@
1
- import React, { useState, useEffect, useRef } from 'react'
1
+ import React, { useState, useEffect, useRef, useMemo } from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import TextInput from 'ink-text-input'
4
4
  import { VimMotionEngine, type VimMode } from './vim-motions.js'
5
+ import { getCommandList, type CommandEntry } from './commands.js'
6
+ import { CommandPicker } from './command-picker.js'
5
7
 
6
8
  interface InputBarProps {
7
9
  onSubmit: (input: string) => void
@@ -111,6 +113,15 @@ export function InputBar({ onSubmit, isLoading }: InputBarProps) {
111
113
  const [completionVerb, setCompletionVerb] = useState<string | null>(null)
112
114
  const prevLoading = useRef(isLoading)
113
115
 
116
+ // ── Slash command hints ──
117
+ const allCommands = useMemo(() => getCommandList(), [])
118
+ const slashHints = useMemo(() => {
119
+ if (!value.startsWith('/')) return []
120
+ const filter = value.slice(1).toLowerCase()
121
+ if (!filter) return allCommands.slice(0, 12) // show first 12 when just "/"
122
+ return allCommands.filter((c) => c.name.toLowerCase().includes(filter)).slice(0, 8)
123
+ }, [value, allCommands])
124
+
114
125
  // Rotate gerunds while loading
115
126
  useEffect(() => {
116
127
  if (!isLoading) return
@@ -242,39 +253,97 @@ export function InputBar({ onSubmit, isLoading }: InputBarProps) {
242
253
  // The cursor hint is informational only; the user repositions manually.
243
254
  })
244
255
 
256
+ // ── Command picker state ──
257
+ const [pickerActive, setPickerActive] = useState(false)
258
+ const prevValueRef = useRef(value)
259
+
260
+ // Auto-activate picker when user types "/"
261
+ useEffect(() => {
262
+ if (value.startsWith('/') && !prevValueRef.current.startsWith('/') && vimMode === 'insert') {
263
+ setPickerActive(true)
264
+ }
265
+ // Dismiss picker when user clears the / prefix
266
+ if (!value.startsWith('/') && pickerActive) {
267
+ setPickerActive(false)
268
+ }
269
+ prevValueRef.current = value
270
+ }, [value, vimMode])
271
+
245
272
  const handleSubmit = (val: string) => {
246
273
  if (!val.trim() || isLoading) return
247
274
  onSubmit(val)
248
275
  setValue('')
276
+ setPickerActive(false)
277
+ }
278
+
279
+ // ── Picker mode: CommandPicker overlay ──
280
+ if (pickerActive && vimMode === 'insert') {
281
+ return (
282
+ <Box flexDirection="column" marginTop={1}>
283
+ <CommandPicker
284
+ initialFilter={value}
285
+ onSelect={(cmdName) => {
286
+ // Fill the command name and submit
287
+ onSubmit(cmdName)
288
+ setValue('')
289
+ setPickerActive(false)
290
+ }}
291
+ onClose={() => {
292
+ setPickerActive(false)
293
+ // Keep the current typed text so user can continue
294
+ }}
295
+ />
296
+ </Box>
297
+ )
249
298
  }
250
299
 
251
300
  return (
252
- <Box marginTop={1}>
253
- <Box marginRight={1}>
254
- <Text color={vimMode === 'normal' ? 'magenta' : isLoading ? 'yellow' : 'cyan'}>
255
- {vimMode === 'normal' ? ':' : '>'}
256
- </Text>
301
+ <Box flexDirection="column" marginTop={1}>
302
+ <Box>
303
+ <Box marginRight={1}>
304
+ <Text color={vimMode === 'normal' ? 'magenta' : isLoading ? 'yellow' : 'cyan'}>
305
+ {vimMode === 'normal' ? ':' : '>'}
306
+ </Text>
307
+ </Box>
308
+ <TextInput
309
+ value={value}
310
+ onChange={(val) => {
311
+ // Block text changes during search mode — keys go to search query
312
+ if (searchMode) return
313
+ setValue(val)
314
+ }}
315
+ onSubmit={handleSubmit}
316
+ placeholder={
317
+ searchMode
318
+ ? `/${searchQuery}`
319
+ : vimMode === 'normal'
320
+ ? '[NORMAL] h/j/k/l w/b 0/$ dd yy p u / (Esc to insert)'
321
+ : isLoading
322
+ ? `${verb}...`
323
+ : completionVerb
324
+ ? completionVerb
325
+ : 'Type a message (Esc to cancel)...'
326
+ }
327
+ />
257
328
  </Box>
258
- <TextInput
259
- value={value}
260
- onChange={(val) => {
261
- // Block text changes during search mode — keys go to search query
262
- if (searchMode) return
263
- setValue(val)
264
- }}
265
- onSubmit={handleSubmit}
266
- placeholder={
267
- searchMode
268
- ? `/${searchQuery}`
269
- : vimMode === 'normal'
270
- ? '[NORMAL] h/j/k/l w/b 0/$ dd yy p u / (Esc to insert)'
271
- : isLoading
272
- ? `${verb}...`
273
- : completionVerb
274
- ? completionVerb
275
- : 'Type a message (Esc to cancel)...'
276
- }
277
- />
329
+ {/* Slash command hints — shown when typing / in INSERT mode (only when picker is NOT active) */}
330
+ {slashHints.length > 0 && vimMode === 'insert' && !pickerActive && (
331
+ <Box marginTop={1} flexDirection="column" gap={1}>
332
+ <Text dimColor>Commands: </Text>
333
+ {slashHints.map((cmd, i) => (
334
+ <Text key={cmd.name} color="cyan">
335
+ {cmd.name}
336
+ </Text>
337
+ ))}
338
+ <Text dimColor>
339
+ (
340
+ {slashHints.length === allCommands.length
341
+ ? 'all'
342
+ : `${slashHints.length} of ${allCommands.length}`}
343
+ )
344
+ </Text>
345
+ </Box>
346
+ )}
278
347
  </Box>
279
348
  )
280
349
  }
package/dist/mipham DELETED
Binary file