@miphamai/cli 0.19.4 → 0.20.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/bin/mipham.ts +1 -1
- package/package.json +2 -1
- package/src/agent/sub-agent.ts +1 -1
- package/src/agent-view/dashboard.tsx +2 -2
- package/src/commands/environment.ts +2 -3
- package/src/commands/git.ts +0 -1
- package/src/commands/project.ts +1 -6
- package/src/config/loader.ts +1 -1
- package/src/core/credential-masker.ts +0 -1
- package/src/core/engine.ts +42 -18
- package/src/core/hooks.ts +0 -1
- package/src/core/rules-loader.ts +2 -2
- package/src/i18n-context.tsx +37 -0
- package/src/index.tsx +27 -11
- package/src/mcp/client.ts +13 -3
- package/src/mcp/oauth.ts +14 -4
- package/src/mcp/registry.ts +12 -2
- package/src/skills/registry.ts +1 -1
- package/src/tools/agent/skill.ts +1 -1
- package/src/tools/exec/enter-worktree.ts +1 -1
- package/src/tools/index.ts +18 -8
- package/src/ui/agent-footer.tsx +3 -1
- package/src/ui/app.tsx +22 -16
- package/src/ui/chat.tsx +19 -15
- package/src/ui/command-picker.tsx +13 -8
- package/src/ui/commands.ts +310 -434
- package/src/ui/input.tsx +55 -103
- package/src/ui/picker.tsx +10 -8
package/src/ui/agent-footer.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
+
import { useI18n } from '../i18n-context'
|
|
3
4
|
|
|
4
5
|
export interface AgentEntry {
|
|
5
6
|
id: string
|
|
@@ -33,6 +34,7 @@ function formatTokens(n: number): string {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function AgentFooter({ agents, gitBranch, tick: _tick }: AgentFooterProps) {
|
|
37
|
+
const { t } = useI18n()
|
|
36
38
|
const hasAgents = agents.length > 0
|
|
37
39
|
const hasBranch = gitBranch.length > 0
|
|
38
40
|
|
|
@@ -73,7 +75,7 @@ export function AgentFooter({ agents, gitBranch, tick: _tick }: AgentFooterProps
|
|
|
73
75
|
{/* Spacer pushes stats to the right — Ink handles this naturally */}
|
|
74
76
|
<Text dimColor>
|
|
75
77
|
{' '}
|
|
76
|
-
{isRunning ? formatElapsed(elapsed) : 'finished'}
|
|
78
|
+
{isRunning ? formatElapsed(elapsed) : t('ui.agent.finished')}
|
|
77
79
|
</Text>
|
|
78
80
|
{isRunning && agent.tokensUsed > 0 && (
|
|
79
81
|
<Text dimColor>
|
package/src/ui/app.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import type { QueryEngine } from '../core/engine'
|
|
|
5
5
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
6
6
|
import type { SkillsLoader } from '../skills/loader'
|
|
7
7
|
import type { PluginManager } from '../plugin/plugin-manager'
|
|
8
|
-
import {
|
|
8
|
+
import { setPreference } from '../config/preferences'
|
|
9
9
|
import { AgentRegistry } from '../agent/agent-registry'
|
|
10
10
|
import { getBackgroundAgentRegistry } from '../agent/background-registry'
|
|
11
11
|
import { ChatPanel } from './chat'
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
handleSwitch,
|
|
20
20
|
type CommandContext,
|
|
21
21
|
} from './commands'
|
|
22
|
+
import { useI18n } from '../i18n-context'
|
|
22
23
|
import type { PermissionMode } from '../shared/index.ts'
|
|
23
24
|
|
|
24
25
|
interface AppProps {
|
|
@@ -72,14 +73,6 @@ const PERMISSION_MODES: PermissionMode[] = [
|
|
|
72
73
|
// Labels aligned with Claude Code terminology: describe behavior, not capability.
|
|
73
74
|
// Claude Code modes: manual mode → accept edits on → bypass
|
|
74
75
|
// Mipham extends with 3 extra modes (plan, auto, dontAsk) for finer control.
|
|
75
|
-
const PERMISSION_LABELS: Record<PermissionMode, string> = {
|
|
76
|
-
default: 'manual mode',
|
|
77
|
-
acceptEdits: 'accept edits on',
|
|
78
|
-
plan: 'plan mode · read-only',
|
|
79
|
-
auto: 'auto mode',
|
|
80
|
-
dontAsk: "don't ask",
|
|
81
|
-
bypassPermissions: 'bypass',
|
|
82
|
-
}
|
|
83
76
|
const PERMISSION_COLORS: Record<PermissionMode, string> = {
|
|
84
77
|
default: 'white',
|
|
85
78
|
acceptEdits: 'blue',
|
|
@@ -128,14 +121,26 @@ export function App({
|
|
|
128
121
|
version,
|
|
129
122
|
sessionId,
|
|
130
123
|
}: AppProps) {
|
|
124
|
+
const { t } = useI18n()
|
|
125
|
+
const PERMISSION_LABELS = useMemo<Record<PermissionMode, string>>(
|
|
126
|
+
() => ({
|
|
127
|
+
default: t('ui.permission.manual'),
|
|
128
|
+
acceptEdits: t('ui.permission.accept_edits'),
|
|
129
|
+
plan: t('ui.permission.plan_mode'),
|
|
130
|
+
auto: t('ui.permission.auto'),
|
|
131
|
+
dontAsk: t('ui.permission.dont_ask'),
|
|
132
|
+
bypassPermissions: t('ui.permission.bypass'),
|
|
133
|
+
}),
|
|
134
|
+
[t],
|
|
135
|
+
)
|
|
131
136
|
const [messages, setMessages] = useState<ChatMessage[]>([])
|
|
132
137
|
const [isLoading, setIsLoading] = useState(false)
|
|
133
138
|
const [providerId, setProviderId] = useState(initialProvider || config.defaultProvider)
|
|
134
139
|
const [modelId, setModelId] = useState(initialModel || config.defaultModel)
|
|
135
140
|
const [pickerOpen, setPickerOpen] = useState(false)
|
|
136
|
-
const [
|
|
137
|
-
const [
|
|
138
|
-
const [
|
|
141
|
+
const [_sessionTitle, setSessionTitle] = useState('')
|
|
142
|
+
const [_fastMode, setFastMode] = useState(false)
|
|
143
|
+
const [_effort, setEffort] = useState('high')
|
|
139
144
|
const [focusMode, setFocusMode] = useState(false)
|
|
140
145
|
const [_ultracodeMode, setUltracodeMode] = useState(false)
|
|
141
146
|
const [goalText, setGoalText] = useState('')
|
|
@@ -244,8 +249,9 @@ export function App({
|
|
|
244
249
|
setUltracodeMode: (on: boolean) => setUltracodeMode(on),
|
|
245
250
|
skillsLoader,
|
|
246
251
|
pluginManager,
|
|
252
|
+
t,
|
|
247
253
|
}),
|
|
248
|
-
[engine, config, providerId, modelId, skillsLoader, pluginManager, sessionId],
|
|
254
|
+
[engine, config, providerId, modelId, skillsLoader, pluginManager, sessionId, t],
|
|
249
255
|
)
|
|
250
256
|
|
|
251
257
|
const handleSubmit = useCallback(
|
|
@@ -612,10 +618,10 @@ export function App({
|
|
|
612
618
|
)}
|
|
613
619
|
<Box flexDirection="row">
|
|
614
620
|
<Text color={PERMISSION_COLORS[permissionMode]}>
|
|
615
|
-
⏵⏵ {PERMISSION_LABELS[permissionMode]} (
|
|
621
|
+
⏵⏵ {PERMISSION_LABELS[permissionMode]} ({t('ui.status.shift_tab_cycle')})
|
|
616
622
|
</Text>
|
|
617
|
-
<Text dimColor> ·
|
|
618
|
-
<Text dimColor> ·
|
|
623
|
+
<Text dimColor> · {t('ui.status.esc_to_interrupt')}</Text>
|
|
624
|
+
<Text dimColor> · {t('ui.status.left_for_agents')}</Text>
|
|
619
625
|
</Box>
|
|
620
626
|
</Box>
|
|
621
627
|
</Box>
|
package/src/ui/chat.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
+
import { useI18n } from '../i18n-context'
|
|
3
4
|
import type { ChatMessage } from './app'
|
|
4
5
|
|
|
5
6
|
interface ChatPanelProps {
|
|
@@ -49,6 +50,7 @@ function toolColor(name: string): string {
|
|
|
49
50
|
*/
|
|
50
51
|
const MessageRow = React.memo(
|
|
51
52
|
function MessageRow({ msg }: { msg: ChatMessage; isLast: boolean }) {
|
|
53
|
+
const { t } = useI18n()
|
|
52
54
|
return (
|
|
53
55
|
<Box
|
|
54
56
|
flexDirection="column"
|
|
@@ -76,8 +78,8 @@ const MessageRow = React.memo(
|
|
|
76
78
|
{msg.role === 'user'
|
|
77
79
|
? `▸ ${displayCwd()}`
|
|
78
80
|
: msg.role === 'assistant'
|
|
79
|
-
? '
|
|
80
|
-
:
|
|
81
|
+
? t('ui.assistant.role_label')
|
|
82
|
+
: `⚠ ${t('ui.system.role_label')}`}
|
|
81
83
|
:
|
|
82
84
|
</Text>
|
|
83
85
|
<Text>{msg.content}</Text>
|
|
@@ -93,6 +95,7 @@ const MessageRow = React.memo(
|
|
|
93
95
|
)
|
|
94
96
|
|
|
95
97
|
export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
98
|
+
const { t } = useI18n()
|
|
96
99
|
// Group consecutive tool calls into compact summaries to reduce visual noise.
|
|
97
100
|
// In focus mode, use the more aggressive compactForFocus.
|
|
98
101
|
const displayMessages = focusMode ? compactForFocus(messages) : compactToolGroups(messages)
|
|
@@ -102,8 +105,7 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
102
105
|
{focusMode && messages.length > 0 && (
|
|
103
106
|
<Box marginBottom={1}>
|
|
104
107
|
<Text dimColor>
|
|
105
|
-
🔍
|
|
106
|
-
to expand
|
|
108
|
+
🔍 {t('ui.chat.focus_hint')} — {countHidden(messages, displayMessages, t)} · {t('ui.chat.toggle_focus_hint')} · {t('ui.chat.expand_hint')}
|
|
107
109
|
</Text>
|
|
108
110
|
</Box>
|
|
109
111
|
)}
|
|
@@ -111,22 +113,24 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
111
113
|
<Box flexDirection="column">
|
|
112
114
|
<Box marginBottom={1}>
|
|
113
115
|
<Text color="cyan" bold>
|
|
114
|
-
|
|
116
|
+
{t('ui.banner.title')}
|
|
115
117
|
</Text>
|
|
116
|
-
<Text dimColor> —
|
|
118
|
+
<Text dimColor> — {t('ui.banner.subtitle')}</Text>
|
|
117
119
|
</Box>
|
|
118
|
-
<Text dimColor>
|
|
120
|
+
<Text dimColor>{t('ui.banner.tagline')}</Text>
|
|
119
121
|
<Box marginTop={1}>
|
|
120
122
|
<Text dimColor>
|
|
121
|
-
|
|
122
|
-
<Text color="yellow">Ctrl+P</Text> pick model · <Text color="yellow">Esc</Text> to
|
|
123
|
-
exit
|
|
123
|
+
{t('ui.banner.start_message')} <Text color="yellow">/help</Text>
|
|
124
124
|
</Text>
|
|
125
125
|
</Box>
|
|
126
126
|
<Box marginTop={1}>
|
|
127
127
|
<Text dimColor>
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
{t('ui.banner.controls_hint')}
|
|
129
|
+
</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
<Box marginTop={1}>
|
|
132
|
+
<Text dimColor>
|
|
133
|
+
{t('ui.banner.tip_clear')}
|
|
130
134
|
</Text>
|
|
131
135
|
</Box>
|
|
132
136
|
</Box>
|
|
@@ -239,9 +243,9 @@ function summarizeToolGroup(tools: ChatMessage[]): ChatMessage {
|
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
/** Count how many messages are hidden in focus mode. */
|
|
242
|
-
function countHidden(all: ChatMessage[], compacted: ChatMessage[]): string {
|
|
246
|
+
function countHidden(all: ChatMessage[], compacted: ChatMessage[], t: (key: string, params?: Record<string, string>) => string): string {
|
|
243
247
|
const toolCount = all.filter((m) => m.toolMeta).length
|
|
244
248
|
const hidden = all.length - compacted.length
|
|
245
|
-
if (hidden <= 0) return
|
|
246
|
-
return
|
|
249
|
+
if (hidden <= 0) return t('ui.chat.showing_all', { count: String(all.length) })
|
|
250
|
+
return t('ui.chat.tool_outputs_folded', { hidden: String(hidden), toolCount: String(toolCount) })
|
|
247
251
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState, useMemo, useEffect } from 'react'
|
|
2
2
|
import { Box, Text, useInput } from 'ink'
|
|
3
3
|
import TextInput from 'ink-text-input'
|
|
4
|
-
import {
|
|
4
|
+
import { useI18n } from '../i18n-context'
|
|
5
|
+
import { getCommandList } from './commands.js'
|
|
5
6
|
|
|
6
7
|
interface CommandPickerProps {
|
|
7
8
|
/** Text already typed (e.g. "/", "/age") — used as initial filter */
|
|
@@ -29,6 +30,7 @@ export function CommandPicker({
|
|
|
29
30
|
onClose,
|
|
30
31
|
maxVisible = PAGE_SIZE,
|
|
31
32
|
}: CommandPickerProps) {
|
|
33
|
+
const { t } = useI18n()
|
|
32
34
|
const allCommands = useMemo(() => getCommandList(), [])
|
|
33
35
|
const [filter, setFilter] = useState(initialFilter)
|
|
34
36
|
const [cursorIdx, setCursorIdx] = useState(0)
|
|
@@ -53,7 +55,7 @@ export function CommandPicker({
|
|
|
53
55
|
Math.min(cursorIdx - Math.floor(maxVisible / 2), filtered.length - maxVisible),
|
|
54
56
|
)
|
|
55
57
|
const visible = filtered.slice(scrollStart, scrollStart + maxVisible)
|
|
56
|
-
const
|
|
58
|
+
const _adjustedCursor = cursorIdx - scrollStart
|
|
57
59
|
|
|
58
60
|
// Wrap cursor safely
|
|
59
61
|
const safeCursor = (i: number) =>
|
|
@@ -92,14 +94,14 @@ export function CommandPicker({
|
|
|
92
94
|
{/* Title */}
|
|
93
95
|
<Box marginBottom={1}>
|
|
94
96
|
<Text bold color="cyan">
|
|
95
|
-
|
|
97
|
+
{t('ui.command_picker.title')}
|
|
96
98
|
</Text>
|
|
97
|
-
<Text dimColor>
|
|
99
|
+
<Text dimColor> {t('ui.command_picker.nav_help')}</Text>
|
|
98
100
|
</Box>
|
|
99
101
|
|
|
100
102
|
{/* Command list */}
|
|
101
103
|
<Box flexDirection="column" marginBottom={1}>
|
|
102
|
-
{visible.length === 0 && <Text dimColor>
|
|
104
|
+
{visible.length === 0 && <Text dimColor> {t('ui.command_picker.no_matches', { filter })}</Text>}
|
|
103
105
|
{visible.map((cmd, i) => {
|
|
104
106
|
const globalIdx = i + scrollStart
|
|
105
107
|
const isCursor = globalIdx === cursorIdx
|
|
@@ -119,8 +121,11 @@ export function CommandPicker({
|
|
|
119
121
|
{filtered.length > maxVisible && (
|
|
120
122
|
<Box marginBottom={1}>
|
|
121
123
|
<Text dimColor>
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
+
{t('ui.command_picker.scroll_indicator', {
|
|
125
|
+
start: String(scrollStart + 1),
|
|
126
|
+
end: String(Math.min(scrollStart + maxVisible, filtered.length)),
|
|
127
|
+
total: String(filtered.length),
|
|
128
|
+
})}
|
|
124
129
|
</Text>
|
|
125
130
|
</Box>
|
|
126
131
|
)}
|
|
@@ -137,7 +142,7 @@ export function CommandPicker({
|
|
|
137
142
|
onSelect(selected.name)
|
|
138
143
|
}
|
|
139
144
|
}}
|
|
140
|
-
placeholder=
|
|
145
|
+
placeholder={t('ui.command_picker.placeholder')}
|
|
141
146
|
/>
|
|
142
147
|
</Box>
|
|
143
148
|
</Box>
|