@miphamai/cli 0.27.0 → 0.29.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/skills/mipham/self-audit.mipham-skill.md +171 -0
- package/src/core/auto-memory.ts +616 -0
- package/src/core/crsi-sandbox.ts +551 -0
- package/src/core/engine.ts +20 -0
- package/src/ui/app.tsx +41 -10
- package/src/ui/chat.tsx +13 -5
- package/src/ui/input.tsx +61 -8
package/src/ui/chat.tsx
CHANGED
|
@@ -28,6 +28,7 @@ function toolColor(name: string): string {
|
|
|
28
28
|
return 'white'
|
|
29
29
|
case 'Write':
|
|
30
30
|
case 'Edit':
|
|
31
|
+
case 'Update':
|
|
31
32
|
return 'red'
|
|
32
33
|
case 'Agent':
|
|
33
34
|
case 'Task':
|
|
@@ -59,12 +60,19 @@ const MessageRow = React.memo(
|
|
|
59
60
|
>
|
|
60
61
|
{msg.toolMeta ? (
|
|
61
62
|
<Box flexDirection="column">
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
|
|
63
|
+
{/* Tool call line: ⏺ ToolName(detail) — Claude Code parity */}
|
|
64
|
+
{msg.toolMeta.name ? (
|
|
65
|
+
<Text color={toolColor(msg.toolMeta.name)}>
|
|
66
|
+
{msg.toolMeta.collapsed ? '⏺' : '⏺ ▼'} {msg.toolMeta.name}
|
|
67
|
+
{msg.toolMeta.input ? ` (${msg.toolMeta.input.slice(0, 120)})` : ''}
|
|
68
|
+
</Text>
|
|
69
|
+
) : null}
|
|
70
|
+
{/* Tool result line: ⎿ summary text */}
|
|
66
71
|
{msg.toolMeta.output ? (
|
|
67
|
-
<Text dimColor>
|
|
72
|
+
<Text dimColor>
|
|
73
|
+
{' ⎿ '}
|
|
74
|
+
{msg.toolMeta.output.slice(0, 300)}
|
|
75
|
+
</Text>
|
|
68
76
|
) : msg.content && !msg.toolMeta.collapsed ? (
|
|
69
77
|
<Text dimColor>{msg.content}</Text>
|
|
70
78
|
) : null}
|
package/src/ui/input.tsx
CHANGED
|
@@ -83,6 +83,16 @@ export function InputBar({
|
|
|
83
83
|
const [completionVerb, setCompletionVerb] = useState<string | null>(null)
|
|
84
84
|
const prevLoading = useRef(isLoading)
|
|
85
85
|
|
|
86
|
+
// ── Message history for arrow-key navigation (Claude Code parity) ──
|
|
87
|
+
const [submittedHistory, setSubmittedHistory] = useState<string[]>([])
|
|
88
|
+
const historyIndexRef = useRef(-1) // -1 = not browsing history
|
|
89
|
+
const savedDraftRef = useRef('') // saved user draft before browsing history
|
|
90
|
+
|
|
91
|
+
// Stabilize t ref — prevents stale closures in intervals and avoids
|
|
92
|
+
// unnecessary effect re-runs when the i18n context value object changes.
|
|
93
|
+
const tRef = useRef(t)
|
|
94
|
+
tRef.current = t
|
|
95
|
+
|
|
86
96
|
// ── Slash command hints ──
|
|
87
97
|
const allCommands = useMemo(() => getCommandList(), [])
|
|
88
98
|
const slashHints = useMemo(() => {
|
|
@@ -92,32 +102,33 @@ export function InputBar({
|
|
|
92
102
|
return allCommands.filter((c) => c.name.toLowerCase().includes(filter)).slice(0, 8)
|
|
93
103
|
}, [value, allCommands])
|
|
94
104
|
|
|
95
|
-
// Rotate gerunds while loading
|
|
105
|
+
// Rotate gerunds while loading — use tRef to avoid re-running when i18n context re-renders
|
|
96
106
|
useEffect(() => {
|
|
97
107
|
if (!isLoading) return
|
|
98
108
|
const interval = setInterval(() => {
|
|
99
|
-
setVerb(
|
|
100
|
-
},
|
|
109
|
+
setVerb(tRef.current(pick(LOADING_KEYS)))
|
|
110
|
+
}, 2000)
|
|
101
111
|
return () => clearInterval(interval)
|
|
102
|
-
}, [isLoading
|
|
112
|
+
}, [isLoading])
|
|
103
113
|
|
|
104
114
|
// Pick a fresh gerund when loading starts
|
|
105
115
|
useEffect(() => {
|
|
106
116
|
if (isLoading) {
|
|
107
|
-
setVerb(
|
|
117
|
+
setVerb(tRef.current(pick(LOADING_KEYS)))
|
|
108
118
|
setCompletionVerb(null)
|
|
109
119
|
}
|
|
110
|
-
}, [isLoading
|
|
120
|
+
}, [isLoading])
|
|
111
121
|
|
|
112
122
|
// Flash a past participle when loading stops
|
|
113
123
|
useEffect(() => {
|
|
114
124
|
if (prevLoading.current === true && isLoading === false) {
|
|
115
|
-
setCompletionVerb(
|
|
125
|
+
setCompletionVerb(tRef.current(pick(COMPLETED_KEYS)))
|
|
116
126
|
const timer = setTimeout(() => setCompletionVerb(null), 1500)
|
|
127
|
+
prevLoading.current = isLoading
|
|
117
128
|
return () => clearTimeout(timer)
|
|
118
129
|
}
|
|
119
130
|
prevLoading.current = isLoading
|
|
120
|
-
}, [isLoading
|
|
131
|
+
}, [isLoading])
|
|
121
132
|
|
|
122
133
|
const [vimMode, setVimMode] = useState<VimMode>('insert')
|
|
123
134
|
// NOTE: Dual mode state — React state (vimMode) drives UI re-renders (prompt color,
|
|
@@ -195,6 +206,39 @@ export function InputBar({
|
|
|
195
206
|
return
|
|
196
207
|
}
|
|
197
208
|
|
|
209
|
+
// ── Arrow-key history navigation (insert mode only, Claude Code parity) ──
|
|
210
|
+
if (vimEngine.current.mode === 'insert' && !searchMode) {
|
|
211
|
+
if (key.upArrow || key.downArrow) {
|
|
212
|
+
// Ignore if picker is active (command picker handles its own arrows)
|
|
213
|
+
if (value.startsWith('/')) return
|
|
214
|
+
|
|
215
|
+
if (key.upArrow) {
|
|
216
|
+
if (submittedHistory.length === 0) return
|
|
217
|
+
// Save current draft the first time we enter history browsing
|
|
218
|
+
if (historyIndexRef.current === -1) {
|
|
219
|
+
savedDraftRef.current = value
|
|
220
|
+
}
|
|
221
|
+
const newIndex = Math.min(historyIndexRef.current + 1, submittedHistory.length - 1)
|
|
222
|
+
historyIndexRef.current = newIndex
|
|
223
|
+
setValue(submittedHistory[submittedHistory.length - 1 - newIndex]!)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
if (key.downArrow) {
|
|
227
|
+
if (historyIndexRef.current === -1) return
|
|
228
|
+
const newIndex = historyIndexRef.current - 1
|
|
229
|
+
historyIndexRef.current = newIndex
|
|
230
|
+
if (newIndex === -1) {
|
|
231
|
+
// Back to the original draft
|
|
232
|
+
setValue(savedDraftRef.current)
|
|
233
|
+
savedDraftRef.current = ''
|
|
234
|
+
} else {
|
|
235
|
+
setValue(submittedHistory[submittedHistory.length - 1 - newIndex]!)
|
|
236
|
+
}
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
198
242
|
if (vimEngine.current.mode !== 'normal') return
|
|
199
243
|
|
|
200
244
|
// Handle search mode — collect query characters
|
|
@@ -288,6 +332,10 @@ export function InputBar({
|
|
|
288
332
|
|
|
289
333
|
const handleSubmit = (val: string) => {
|
|
290
334
|
if (!val.trim() || isLoading) return
|
|
335
|
+
// Save to message history for arrow-key navigation
|
|
336
|
+
setSubmittedHistory((prev) => [...prev, val])
|
|
337
|
+
historyIndexRef.current = -1
|
|
338
|
+
savedDraftRef.current = ''
|
|
291
339
|
onSubmit(val)
|
|
292
340
|
setValue('')
|
|
293
341
|
setPickerActive(false)
|
|
@@ -327,6 +375,11 @@ export function InputBar({
|
|
|
327
375
|
onChange={(val) => {
|
|
328
376
|
// Block text changes during search mode — keys go to search query
|
|
329
377
|
if (searchMode) return
|
|
378
|
+
// Reset history browsing when user starts typing
|
|
379
|
+
if (historyIndexRef.current !== -1) {
|
|
380
|
+
historyIndexRef.current = -1
|
|
381
|
+
savedDraftRef.current = ''
|
|
382
|
+
}
|
|
330
383
|
setValue(val)
|
|
331
384
|
}}
|
|
332
385
|
onSubmit={handleSubmit}
|