@miphamai/cli 0.24.8 → 0.24.10

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.24.8",
3
+ "version": "0.24.10",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -31,6 +31,13 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
31
31
  const [selectedIndex, setSelectedIndex] = useState(0)
32
32
  const [peekingSessionId, setPeekingSessionId] = useState<string | null>(null)
33
33
  const [_groupBy, setGroupBy] = useState<'status' | 'directory'>('status')
34
+ const [feedback, setFeedback] = useState<string | null>(null)
35
+
36
+ // Flash a brief feedback message that auto-clears
37
+ const showFeedback = useCallback((msg: string) => {
38
+ setFeedback(msg)
39
+ setTimeout(() => setFeedback(null), 1800)
40
+ }, [])
34
41
 
35
42
  // Build a flat list of sessions in group order, with group headers
36
43
  const flatList = useMemo(() => {
@@ -99,26 +106,39 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
99
106
  // Ctrl+S — toggle group by (status ↔ directory)
100
107
  if (input === '\x13') {
101
108
  setGroupBy((prev) => (prev === 'status' ? 'directory' : 'status'))
109
+ showFeedback(`Grouped by ${_groupBy === 'status' ? 'directory' : 'status'}`)
102
110
  return
103
111
  }
104
112
 
105
113
  // Ctrl+R — rename selected session
106
114
  if (input === '\x12') {
107
- if (sessionsOnly.length === 0) return
115
+ if (sessionsOnly.length === 0) {
116
+ showFeedback('No sessions to rename')
117
+ return
118
+ }
108
119
  const current = sessionsOnly[selectedIndex]
109
120
  if (!current) return
110
121
  const newTitle = `session-${Date.now().toString(36)}`
111
122
  manager.rename(current.session.id, newTitle)
123
+ showFeedback(`Renamed to ${newTitle}`)
112
124
  return
113
125
  }
114
126
 
115
127
  if (input === 'j') {
128
+ if (sessionsOnly.length === 0) {
129
+ showFeedback('No sessions to navigate — spawn a background agent first')
130
+ return
131
+ }
116
132
  setSelectedIndex((prev) => Math.min(prev + 1, sessionsOnly.length - 1))
117
133
  setPeekingSessionId(null)
118
134
  return
119
135
  }
120
136
 
121
137
  if (input === 'k') {
138
+ if (sessionsOnly.length === 0) {
139
+ showFeedback('No sessions to navigate — spawn a background agent first')
140
+ return
141
+ }
122
142
  setSelectedIndex((prev) => Math.max(prev - 1, 0))
123
143
  setPeekingSessionId(null)
124
144
  return
@@ -126,7 +146,10 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
126
146
 
127
147
  // Space — toggle peek
128
148
  if (input === ' ') {
129
- if (sessionsOnly.length === 0) return
149
+ if (sessionsOnly.length === 0) {
150
+ showFeedback('No sessions to peek')
151
+ return
152
+ }
130
153
  const current = sessionsOnly[selectedIndex]
131
154
  if (!current) return
132
155
  setPeekingSessionId(peekingSessionId === current.session.id ? null : current.session.id)
@@ -135,7 +158,10 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
135
158
 
136
159
  // Enter — attach to selected session
137
160
  if (key.return) {
138
- if (sessionsOnly.length === 0) return
161
+ if (sessionsOnly.length === 0) {
162
+ showFeedback('No sessions to attach — spawn a background agent first')
163
+ return
164
+ }
139
165
  const current = sessionsOnly[selectedIndex]
140
166
  if (!current) return
141
167
  if (peekingSessionId) {
@@ -198,6 +224,13 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
198
224
  <Text dimColor>
199
225
  Use the Agent tool or type &quot;run this in background&quot; to spawn one.
200
226
  </Text>
227
+ {feedback && (
228
+ <Box marginTop={1}>
229
+ <Text color="yellow" dimColor>
230
+ ⚡ {feedback}
231
+ </Text>
232
+ </Box>
233
+ )}
201
234
  </Box>
202
235
  ) : (
203
236
  <Box flexDirection="column">
@@ -232,6 +265,15 @@ export function AgentViewDashboard({ manager, onAttach, onExit }: DashboardProps
232
265
  {peekData && (
233
266
  <SessionPeek session={peekData.session} recentMessages={peekData.recentMessages} />
234
267
  )}
268
+
269
+ {/* Feedback toast — flashes briefly on action */}
270
+ {feedback && (
271
+ <Box marginTop={1}>
272
+ <Text color="yellow" dimColor>
273
+ ⚡ {feedback}
274
+ </Text>
275
+ </Box>
276
+ )}
235
277
  </Box>
236
278
  )
237
279
  }
@@ -703,7 +703,7 @@
703
703
  "status": {
704
704
  "esc_to_interrupt": "esc to interrupt",
705
705
  "shift_tab_cycle": "shift+tab to cycle",
706
- "left_for_agents": "← for agents"
706
+ "left_for_agents": "ctrl+g agents to manage"
707
707
  }
708
708
  },
709
709
  "tools": {
@@ -703,7 +703,7 @@
703
703
  "status": {
704
704
  "esc_to_interrupt": "Esc 中断",
705
705
  "shift_tab_cycle": "Shift+Tab 切换",
706
- "left_for_agents": "← 代理"
706
+ "left_for_agents": "ctrl+g 管理代理"
707
707
  }
708
708
  },
709
709
  "tools": {
package/src/index.tsx CHANGED
@@ -488,6 +488,7 @@ export async function runApp(options: RunOptions): Promise<void> {
488
488
  pluginManager,
489
489
  version: options.version,
490
490
  sessionId: sessionName,
491
+ agentViewManager,
491
492
  }),
492
493
  }),
493
494
  )
package/src/ui/app.tsx CHANGED
@@ -12,6 +12,8 @@ import { ChatPanel } from './chat'
12
12
  import { InputBar } from './input'
13
13
  import { ModelPicker } from './picker'
14
14
  import { AgentFooter, type AgentEntry } from './agent-footer'
15
+ import { AgentViewDashboard } from '../agent-view/dashboard'
16
+ import type { AgentViewManager } from '../agent-view/agent-view-manager'
15
17
  import { WorkflowProgress } from './workflow-progress.js'
16
18
  import {
17
19
  getCommand,
@@ -33,6 +35,7 @@ interface AppProps {
33
35
  pluginManager?: PluginManager
34
36
  version?: string
35
37
  sessionId?: string
38
+ agentViewManager?: AgentViewManager
36
39
  }
37
40
 
38
41
  export interface ToolMeta {
@@ -121,6 +124,7 @@ export function App({
121
124
  pluginManager,
122
125
  version,
123
126
  sessionId,
127
+ agentViewManager,
124
128
  }: AppProps) {
125
129
  const { t } = useI18n()
126
130
  const PERMISSION_LABELS = useMemo<Record<PermissionMode, string>>(
@@ -139,6 +143,7 @@ export function App({
139
143
  const [providerId, setProviderId] = useState(initialProvider || config.defaultProvider)
140
144
  const [modelId, setModelId] = useState(initialModel || config.defaultModel)
141
145
  const [pickerOpen, setPickerOpen] = useState(false)
146
+ const [agentViewOpen, setAgentViewOpen] = useState(false)
142
147
  const [_sessionTitle, setSessionTitle] = useState('')
143
148
  const [_fastMode, setFastMode] = useState(false)
144
149
  const [_effort, setEffort] = useState('high')
@@ -580,99 +585,115 @@ export function App({
580
585
  {/* Workflow progress — auto-detects active workflows, renders nothing when idle */}
581
586
  <WorkflowProgress />
582
587
 
583
- {/* Chat panel */}
584
- <ChatPanel messages={messages} focusMode={focusMode} />
585
-
586
- {/* Input with separator lines */}
587
- {pickerOpen ? (
588
- <ModelPicker
589
- config={config}
590
- currentProvider={providerId}
591
- currentModel={modelId}
592
- onSelect={(newProvider, newModel) => {
593
- engine.switchProvider(newProvider, newModel)
594
- setProviderId(newProvider)
595
- setModelId(newModel)
596
- setPickerOpen(false)
597
- setMessages((prev) => [
598
- ...prev,
599
- { role: 'system', content: `✓ Switched to ${newProvider}/${newModel}` },
600
- ])
601
- }}
602
- onClose={() => setPickerOpen(false)}
588
+ {/* Agent View Dashboard — Ctrl+G overlay (replaces chat + input) */}
589
+ {agentViewOpen && agentViewManager ? (
590
+ <AgentViewDashboard
591
+ manager={agentViewManager}
592
+ onAttach={() => {}}
593
+ onExit={() => setAgentViewOpen(false)}
603
594
  />
604
595
  ) : (
605
- /* Input bar (hidden when picker is open) */
606
- <Box flexDirection="column">
607
- <Text dimColor>──────────────────────────────</Text>
608
- <InputBar
609
- onSubmit={handleSubmit}
610
- isLoading={isLoading}
611
- onTogglePicker={() => setPickerOpen((prev) => !prev)}
612
- onToggleFocus={() => setFocusMode((prev) => !prev)}
613
- onToggleExpand={() => {
614
- setMessages((prev) => {
615
- const msgs = [...prev]
616
- for (let i = msgs.length - 1; i >= 0; i--) {
617
- if (msgs[i]?.toolMeta) {
618
- const meta = msgs[i]!.toolMeta!
619
- if (meta.collapsed) {
620
- msgs[i] = {
621
- ...msgs[i]!,
622
- content: `🔧 ${meta.name}: ${meta.input}\n📋 Result: ${meta.output || '(pending)'}`,
623
- toolMeta: { ...meta, collapsed: false },
624
- }
625
- } else {
626
- const short =
627
- meta.input.length > 50 ? meta.input.slice(0, 50) + '...' : meta.input
628
- msgs[i] = {
629
- ...msgs[i]!,
630
- content: `⏺ ${meta.name} · ${short} (Ctrl+O to expand)`,
631
- toolMeta: { ...meta, collapsed: true },
596
+ <>
597
+ {/* Chat panel */}
598
+ <ChatPanel messages={messages} focusMode={focusMode} />
599
+
600
+ {/* Input with separator lines */}
601
+ {pickerOpen ? (
602
+ <ModelPicker
603
+ config={config}
604
+ currentProvider={providerId}
605
+ currentModel={modelId}
606
+ onSelect={(newProvider, newModel) => {
607
+ engine.switchProvider(newProvider, newModel)
608
+ setProviderId(newProvider)
609
+ setModelId(newModel)
610
+ setPickerOpen(false)
611
+ setMessages((prev) => [
612
+ ...prev,
613
+ { role: 'system', content: `✓ Switched to ${newProvider}/${newModel}` },
614
+ ])
615
+ }}
616
+ onClose={() => setPickerOpen(false)}
617
+ />
618
+ ) : (
619
+ /* Input bar (hidden when picker is open) */
620
+ <Box flexDirection="column">
621
+ <Text dimColor>──────────────────────────────</Text>
622
+ <InputBar
623
+ onSubmit={handleSubmit}
624
+ isLoading={isLoading}
625
+ onTogglePicker={() => setPickerOpen((prev) => !prev)}
626
+ onToggleFocus={() => setFocusMode((prev) => !prev)}
627
+ onToggleExpand={() => {
628
+ setMessages((prev) => {
629
+ const msgs = [...prev]
630
+ for (let i = msgs.length - 1; i >= 0; i--) {
631
+ if (msgs[i]?.toolMeta) {
632
+ const meta = msgs[i]!.toolMeta!
633
+ if (meta.collapsed) {
634
+ msgs[i] = {
635
+ ...msgs[i]!,
636
+ content: `🔧 ${meta.name}: ${meta.input}\n📋 Result: ${meta.output || '(pending)'}`,
637
+ toolMeta: { ...meta, collapsed: false },
638
+ }
639
+ } else {
640
+ const short =
641
+ meta.input.length > 50 ? meta.input.slice(0, 50) + '...' : meta.input
642
+ msgs[i] = {
643
+ ...msgs[i]!,
644
+ content: `⏺ ${meta.name} · ${short} (Ctrl+O to expand)`,
645
+ toolMeta: { ...meta, collapsed: true },
646
+ }
647
+ }
648
+ break
632
649
  }
633
650
  }
634
- break
651
+ return msgs
652
+ })
653
+ }}
654
+ onCyclePermission={() => {
655
+ setPermissionMode((prev) => {
656
+ const idx = PERMISSION_MODES.indexOf(prev)
657
+ const next = PERMISSION_MODES[(idx + 1) % PERMISSION_MODES.length]!
658
+ engine.getPermission().setMode(next)
659
+ return next
660
+ })
661
+ }}
662
+ onCancel={() => {
663
+ if (abortRef.current) {
664
+ abortRef.current.abort()
635
665
  }
636
- }
637
- return msgs
638
- })
639
- }}
640
- onCyclePermission={() => {
641
- setPermissionMode((prev) => {
642
- const idx = PERMISSION_MODES.indexOf(prev)
643
- const next = PERMISSION_MODES[(idx + 1) % PERMISSION_MODES.length]!
644
- engine.getPermission().setMode(next)
645
- return next
646
- })
647
- }}
648
- onCancel={() => {
649
- if (abortRef.current) {
650
- abortRef.current.abort()
651
- }
652
- }}
666
+ }}
667
+ onToggleAgentView={() => setAgentViewOpen((prev) => !prev)}
668
+ />
669
+ <Text dimColor>──────────────────────────────</Text>
670
+ </Box>
671
+ )}
672
+
673
+ {/* Agent status footer — shows running background agents */}
674
+ <AgentFooter
675
+ agents={Object.values(runningAgents)}
676
+ gitBranch={gitBranch}
677
+ tick={agentTick}
653
678
  />
654
- <Text dimColor>──────────────────────────────</Text>
655
- </Box>
656
- )}
657
679
 
658
- {/* Agent status footer — shows running background agents */}
659
- <AgentFooter agents={Object.values(runningAgents)} gitBranch={gitBranch} tick={agentTick} />
660
-
661
- {/* Status line — Claude Code style */}
662
- <Box marginTop={1} flexDirection="column">
663
- {goalText && (
664
- <Box>
665
- <Text color="green">🎯 Goal: {goalText}</Text>
680
+ {/* Status line — Claude Code style */}
681
+ <Box marginTop={1} flexDirection="column">
682
+ {goalText && (
683
+ <Box>
684
+ <Text color="green">🎯 Goal: {goalText}</Text>
685
+ </Box>
686
+ )}
687
+ <Box flexDirection="row">
688
+ <Text color={PERMISSION_COLORS[permissionMode]}>
689
+ ⏵⏵ {PERMISSION_LABELS[permissionMode]} ({t('ui.status.shift_tab_cycle')})
690
+ </Text>
691
+ <Text dimColor> · {t('ui.status.esc_to_interrupt')}</Text>
692
+ <Text dimColor> · {t('ui.status.left_for_agents')}</Text>
693
+ </Box>
666
694
  </Box>
667
- )}
668
- <Box flexDirection="row">
669
- <Text color={PERMISSION_COLORS[permissionMode]}>
670
- ⏵⏵ {PERMISSION_LABELS[permissionMode]} ({t('ui.status.shift_tab_cycle')})
671
- </Text>
672
- <Text dimColor> · {t('ui.status.esc_to_interrupt')}</Text>
673
- <Text dimColor> · {t('ui.status.left_for_agents')}</Text>
674
- </Box>
675
- </Box>
695
+ </>
696
+ )}
676
697
  </Box>
677
698
  )
678
699
  }
package/src/ui/input.tsx CHANGED
@@ -15,6 +15,8 @@ interface InputBarProps {
15
15
  onToggleFocus?: () => void
16
16
  /** Ctrl+O → expand last tool call */
17
17
  onToggleExpand?: () => void
18
+ /** Ctrl+G → toggle agent view dashboard */
19
+ onToggleAgentView?: () => void
18
20
  /** Shift+Tab → cycle permission mode */
19
21
  onCyclePermission?: () => void
20
22
  /** Escape → cancel loading (when input is empty) */
@@ -71,6 +73,7 @@ export function InputBar({
71
73
  onTogglePicker,
72
74
  onToggleFocus,
73
75
  onToggleExpand,
76
+ onToggleAgentView,
74
77
  onCyclePermission,
75
78
  onCancel,
76
79
  }: InputBarProps) {
@@ -185,6 +188,12 @@ export function InputBar({
185
188
  onToggleExpand?.()
186
189
  return
187
190
  }
191
+ // Ctrl+G → toggle agent view dashboard
192
+ if (key.ctrl && input === 'g') {
193
+ setValue(valueBeforeShortcut.current)
194
+ onToggleAgentView?.()
195
+ return
196
+ }
188
197
 
189
198
  if (vimEngine.current.mode !== 'normal') return
190
199
 
package/dist/mipham DELETED
Binary file