@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.
@@ -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 { getPreference, setPreference } from '../config/preferences'
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 [sessionTitle, setSessionTitle] = useState('')
137
- const [fastMode, setFastMode] = useState(false)
138
- const [effort, setEffort] = useState('high')
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]} (shift+tab to cycle)
621
+ ⏵⏵ {PERMISSION_LABELS[permissionMode]} ({t('ui.status.shift_tab_cycle')})
616
622
  </Text>
617
- <Text dimColor> · esc to interrupt</Text>
618
- <Text dimColor> · ← for agents</Text>
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
- ? 'Mipham Code'
80
- : '⚠ System'}
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
- 🔍 Focus — {countHidden(messages, displayMessages)} hidden · /focus to toggle · Ctrl+O
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
- Mipham Code
116
+ {t('ui.banner.title')}
115
117
  </Text>
116
- <Text dimColor> — AI-Powered Programming Assistant</Text>
118
+ <Text dimColor> — {t('ui.banner.subtitle')}</Text>
117
119
  </Box>
118
- <Text dimColor>Multi-model · Multi-provider · Skills & Tools · Open-core</Text>
120
+ <Text dimColor>{t('ui.banner.tagline')}</Text>
119
121
  <Box marginTop={1}>
120
122
  <Text dimColor>
121
- Type a message to start. <Text color="yellow">/help</Text> for commands ·{' '}
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
- Tip: Use <Text color="yellow">/clear</Text> to start fresh when switching topics and
129
- free up context
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 `showing all ${all.length} messages`
246
- return `${hidden} tool outputs folded (${toolCount} total tool calls)`
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 { getCommandList, type CommandEntry } from './commands.js'
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 adjustedCursor = cursorIdx - scrollStart
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
- Commands
97
+ {t('ui.command_picker.title')}
96
98
  </Text>
97
- <Text dimColor> ↑↓ navigate · Enter select · Esc close</Text>
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> No commands match "{filter}"</Text>}
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
- {scrollStart + 1}–{Math.min(scrollStart + maxVisible, filtered.length)} of{' '}
123
- {filtered.length} commands
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="Type to filter commands..."
145
+ placeholder={t('ui.command_picker.placeholder')}
141
146
  />
142
147
  </Box>
143
148
  </Box>