@miphamai/cli 0.32.1 → 0.32.3

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.32.1",
3
+ "version": "0.32.3",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -11,11 +11,23 @@ export interface AgentEntry {
11
11
  status: 'running' | 'completed'
12
12
  }
13
13
 
14
+ export interface AgentProgress {
15
+ name: string
16
+ description: string
17
+ startTime: number
18
+ tokensUsed?: number
19
+ isTask?: boolean
20
+ id?: string
21
+ }
22
+
14
23
  interface AgentFooterProps {
15
24
  agents: AgentEntry[]
16
- gitBranch: string
17
25
  /** Tick counter for live elapsed-time re-renders. */
18
26
  tick: number
27
+ /** Active foreground tool: shown as [ToolName detail...] before agent lines. */
28
+ activeTool?: { name: string; detail: string } | null
29
+ /** Inline agent progress from tool_use streaming: ✻ Gerund… (elapsed · tokens). */
30
+ agentProgress?: AgentProgress | null
19
31
  }
20
32
 
21
33
  /** Format elapsed seconds into human-readable string (e.g. "2m 51s", "38s"). */
@@ -33,20 +45,95 @@ function formatTokens(n: number): string {
33
45
  return String(n)
34
46
  }
35
47
 
36
- export function AgentFooter({ agents, gitBranch, tick: _tick }: AgentFooterProps) {
48
+ /** Map tool name to Ink color for the [Tool ...] indicator. */
49
+ function toolIndicatorColor(name: string): string {
50
+ switch (name) {
51
+ case 'Bash':
52
+ case 'Read':
53
+ case 'Glob':
54
+ case 'Grep':
55
+ return 'white'
56
+ case 'Write':
57
+ case 'Edit':
58
+ case 'Update':
59
+ return 'red'
60
+ case 'Agent':
61
+ case 'Task':
62
+ case 'Skill':
63
+ case 'Workflow':
64
+ return 'magenta'
65
+ case 'WebSearch':
66
+ case 'WebFetch':
67
+ return 'blue'
68
+ default:
69
+ return 'white'
70
+ }
71
+ }
72
+
73
+ /** Animate dots for the active tool indicator: cycles 1→2→3→1 dots. */
74
+ function animatedDots(tick: number): string {
75
+ const n = (tick % 3) + 1
76
+ return '.'.repeat(n)
77
+ }
78
+
79
+ /** Gerund verbs for agent progress — rotated every 2 seconds for visual liveliness. */
80
+ const GERUNDS = [
81
+ 'Pondering',
82
+ 'Contemplating',
83
+ 'Cerebrating',
84
+ 'Ruminating',
85
+ 'Deliberating',
86
+ 'Cogitating',
87
+ 'Perambulating',
88
+ 'Computing',
89
+ 'Processing',
90
+ 'Analyzing',
91
+ 'Synthesizing',
92
+ 'Orchestrating',
93
+ 'Forging',
94
+ 'Illuminating',
95
+ 'Manifesting',
96
+ 'Transmogrifying',
97
+ ]
98
+
99
+ /** Pick a gerund that changes every 2 seconds (driven by tick). */
100
+ function rotatingGerund(tick: number): string {
101
+ return GERUNDS[Math.floor(tick / 2) % GERUNDS.length]!
102
+ }
103
+
104
+ export function AgentFooter({ agents, tick, activeTool, agentProgress }: AgentFooterProps) {
37
105
  const { t } = useI18n()
38
106
  const hasAgents = agents.length > 0
39
- const hasBranch = gitBranch.length > 0
107
+ const hasTool = !!activeTool
108
+ const hasProgress = !!agentProgress
40
109
 
41
- if (!hasAgents && !hasBranch) return null
110
+ if (!hasAgents && !hasTool && !hasProgress) return null
42
111
 
43
112
  return (
44
113
  <Box flexDirection="column" marginTop={1}>
45
- {/* Git branch indicator */}
46
- {hasBranch && (
114
+ {/* Active foreground tool indicator — Claude Code style [Bash command...] */}
115
+ {hasTool && (
47
116
  <Box>
117
+ <Text color={toolIndicatorColor(activeTool!.name)}>
118
+ {' '}[{activeTool!.name} {activeTool!.detail.slice(0, 60)}
119
+ {animatedDots(tick)}]
120
+ </Text>
121
+ </Box>
122
+ )}
123
+
124
+ {/* Inline agent progress — ✻ Gerund… (elapsed · ↓ tokens) in orange */}
125
+ {hasProgress && (
126
+ <Box>
127
+ <Text color="#FFA500">
128
+ {' '}✻ {rotatingGerund(tick)}…
129
+ </Text>
48
130
  <Text dimColor>
49
- {' '}⏺ {gitBranch}
131
+ {' ('}
132
+ {formatElapsed(Math.floor((Date.now() - agentProgress!.startTime) / 1000))}
133
+ {agentProgress!.tokensUsed && agentProgress!.tokensUsed > 0
134
+ ? ` · ↓ ${formatTokens(agentProgress!.tokensUsed)} tokens`
135
+ : ''}
136
+ {')'}
50
137
  </Text>
51
138
  </Box>
52
139
  )}
package/src/ui/app.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import TextInput from 'ink-text-input'
4
- import { readFileSync } from 'node:fs'
5
4
  import type { QueryEngine } from '../core/engine'
6
5
  import type { RemoteEngine } from '../daemon/remote-engine'
7
6
  import type { MiphamConfig } from '../shared/index.ts'
@@ -183,28 +182,24 @@ export function App({
183
182
  const [agentProgress, setAgentProgress] = useState<AgentProgress | null>(null)
184
183
  // Multi-agent tracking: keyed by agent/task ID, shows all running + recently completed agents
185
184
  const [runningAgents, setRunningAgents] = useState<Record<string, AgentEntry>>({})
186
- const [gitBranch, setGitBranch] = useState('')
187
185
  const [agentTick, setAgentTick] = useState(0)
188
-
189
- // Detect git branch on mount
190
- useEffect(() => {
191
- try {
192
- const head = readFileSync('.git/HEAD', 'utf8').trim()
193
- const m = head.match(/^ref: refs\/heads\/(.+)$/)
194
- setGitBranch(m ? m[1]! : head.slice(0, 7))
195
- } catch {
196
- // Not a git repo — leave empty
197
- }
198
- }, [])
199
-
200
- // Tick timer for agent elapsed displays (re-renders every second while agents are running)
186
+ /** Active foreground tool indicator: [Bash command...], [Update file.ts...], etc. */
187
+ const [activeTool, setActiveTool] = useState<{ name: string; detail: string } | null>(null)
188
+ // Refs for immediate state (bypasses React batching so footer renders between rapid chunks)
189
+ const activeToolRef = useRef<{ name: string; detail: string } | null>(null)
190
+ const agentProgressRef = useRef<AgentProgress | null>(null)
191
+
192
+ // Tick timer for agent elapsed displays (re-renders every second while agents are running).
193
+ // Uses refs so the timer doesn't stop between state batches.
201
194
  useEffect(() => {
202
195
  const hasRunning =
203
- Object.values(runningAgents).some((a) => a.status === 'running') || agentProgress !== null
196
+ Object.values(runningAgents).some((a) => a.status === 'running') ||
197
+ agentProgressRef.current !== null ||
198
+ activeToolRef.current !== null
204
199
  if (!hasRunning) return
205
200
  const i = setInterval(() => setAgentTick((t) => t + 1), 1000)
206
201
  return () => clearInterval(i)
207
- }, [runningAgents, agentProgress])
202
+ }, [runningAgents, agentProgress, activeTool])
208
203
 
209
204
  // Sync running agents from BackgroundAgentRegistry into React state.
210
205
  // Called after Agent tool results and task notifications to keep the footer current.
@@ -447,6 +442,13 @@ export function App({
447
442
  setMessages((prev) => [...prev, { role: 'user', content: input }])
448
443
  setIsLoading(true)
449
444
 
445
+ // Start agent progress indicator immediately for ALL processing
446
+ const progressStart = Date.now()
447
+ const progress: AgentProgress = { name: '', description: '', startTime: progressStart, tokensUsed: 0 }
448
+ agentProgressRef.current = progress
449
+ setAgentProgress(progress)
450
+ setAgentTick((t) => t + 1)
451
+
450
452
  const controller = new AbortController()
451
453
  abortRef.current = controller
452
454
 
@@ -520,15 +522,24 @@ export function App({
520
522
  const isAgent = toolName === 'Agent' || toolName === 'Task'
521
523
  const detail = formatToolDetail(toolName, chunk.toolUse.input)
522
524
 
525
+ // Show [ToolName detail...] activity indicator for ALL tools (via ref for immediate render)
526
+ const toolEntry = { name: toolDisplayName(toolName), detail }
527
+ activeToolRef.current = toolEntry
528
+ setActiveTool(toolEntry)
529
+ setAgentTick((t) => t + 1)
530
+
523
531
  if (isAgent) {
524
- setAgentProgress({
532
+ const ap: AgentProgress = {
525
533
  name: detail || (chunk.toolUse.input.subagent_type as string) || 'General-purpose',
526
534
  description:
527
535
  (chunk.toolUse.input.description as string) ||
528
536
  (chunk.toolUse.input.prompt as string) ||
529
537
  '',
530
538
  startTime: Date.now(),
531
- })
539
+ }
540
+ agentProgressRef.current = ap
541
+ setAgentProgress(ap)
542
+ setAgentTick((t) => t + 1)
532
543
  }
533
544
 
534
545
  // Show tool call as a visible, collapsed message
@@ -552,7 +563,10 @@ export function App({
552
563
  }
553
564
 
554
565
  if (chunk.type === 'tool_result') {
555
- setAgentProgress(null)
566
+ // Clear tool indicator but keep agent progress (AI is still processing)
567
+ activeToolRef.current = null
568
+ setActiveTool(null)
569
+ setAgentTick((t) => t + 1)
556
570
  // Sync background agents (e.g. Agent tool may have spawned them)
557
571
  syncBgAgents()
558
572
  const output = chunk.content ? String(chunk.content).trim() : ''
@@ -589,12 +603,14 @@ export function App({
589
603
  }
590
604
  }
591
605
 
592
- if (chunk.type === 'usage' && agentProgress) {
606
+ if (chunk.type === 'usage') {
593
607
  const totalTokens = (chunk.inputTokens || 0) + (chunk.outputTokens || 0)
594
- if (totalTokens > 0) {
595
- setAgentProgress((prev) =>
596
- prev ? { ...prev, tokensUsed: (prev.tokensUsed || 0) + totalTokens } : null,
597
- )
608
+ if (totalTokens > 0 && agentProgressRef.current) {
609
+ agentProgressRef.current = {
610
+ ...agentProgressRef.current,
611
+ tokensUsed: (agentProgressRef.current.tokensUsed || 0) + totalTokens,
612
+ }
613
+ setAgentProgress({ ...agentProgressRef.current })
598
614
  }
599
615
  }
600
616
 
@@ -638,6 +654,12 @@ export function App({
638
654
  flushStreamBuffer()
639
655
  setIsLoading(false)
640
656
  abortRef.current = null
657
+ // Clear all progress/tool indicators
658
+ agentProgressRef.current = null
659
+ setAgentProgress(null)
660
+ activeToolRef.current = null
661
+ setActiveTool(null)
662
+ setAgentTick((t) => t + 1)
641
663
  // Auto-save checkpoint after each AI response
642
664
  if (assistantContent) {
643
665
  engine.getContext().saveCheckpoint('post-turn')
@@ -834,11 +856,12 @@ export function App({
834
856
  </Box>
835
857
  )}
836
858
 
837
- {/* Agent status footer — shows running background agents */}
859
+ {/* Agent status footer — shows active tool indicator + inline agent progress + running background agents */}
838
860
  <AgentFooter
839
861
  agents={Object.values(runningAgents)}
840
- gitBranch={gitBranch}
841
862
  tick={agentTick}
863
+ activeTool={activeTool}
864
+ agentProgress={agentProgress}
842
865
  />
843
866
 
844
867
  {/* Status line — Claude Code style */}