@miphamai/cli 0.2.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/README.md +76 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.jpg +0 -0
- package/bin/mipham +51 -0
- package/bin/mipham.ts +8 -0
- package/package.json +59 -0
- package/skills/mipham/om-model-optimize.mipham-skill.md +20 -0
- package/skills/mipham/om-security.mipham-skill.md +21 -0
- package/skills/standard/code-review.SKILL.md +116 -0
- package/skills/standard/compassionate-communication.SKILL.md +80 -0
- package/skills/standard/doc-generator.SKILL.md +21 -0
- package/skills/standard/github-ops.SKILL.md +22 -0
- package/skills/standard/memory.SKILL.md +22 -0
- package/skills/standard/mipham-code-setup.SKILL.md +113 -0
- package/skills/standard/security-review.SKILL.md +122 -0
- package/skills/standard/self-review.SKILL.md +20 -0
- package/skills/standard/superpower.SKILL.md +19 -0
- package/skills/standard/tdd.SKILL.md +20 -0
- package/skills/standard/web-search.SKILL.md +23 -0
- package/src/agent/sub-agent.ts +122 -0
- package/src/config/defaults.ts +10 -0
- package/src/config/loader.ts +30 -0
- package/src/core/context.ts +135 -0
- package/src/core/engine.ts +193 -0
- package/src/core/hooks.ts +116 -0
- package/src/core/instructions.ts +126 -0
- package/src/core/permission.ts +54 -0
- package/src/core/session-store.ts +136 -0
- package/src/index.tsx +93 -0
- package/src/mcp/client.ts +170 -0
- package/src/mcp/protocol.ts +104 -0
- package/src/mcp/transport.ts +169 -0
- package/src/mcp/types.ts +112 -0
- package/src/providers/anthropic.ts +286 -0
- package/src/providers/bootstrap.ts +28 -0
- package/src/providers/openai-compat.ts +158 -0
- package/src/providers/registry.ts +70 -0
- package/src/security/path.ts +90 -0
- package/src/security/url.ts +96 -0
- package/src/shared/constants.ts +368 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/types.ts +161 -0
- package/src/skills/loader.ts +109 -0
- package/src/skills/mipham/runtime.ts +63 -0
- package/src/skills/standard/runtime.ts +59 -0
- package/src/tools/agent/agent.ts +51 -0
- package/src/tools/agent/memory.ts +51 -0
- package/src/tools/agent/plan.ts +87 -0
- package/src/tools/agent/skill.ts +58 -0
- package/src/tools/exec/bash.ts +111 -0
- package/src/tools/exec/git.ts +63 -0
- package/src/tools/exec/task.ts +69 -0
- package/src/tools/file/edit.ts +57 -0
- package/src/tools/file/glob.ts +29 -0
- package/src/tools/file/grep.ts +52 -0
- package/src/tools/file/read.ts +38 -0
- package/src/tools/file/write.ts +27 -0
- package/src/tools/index.ts +126 -0
- package/src/tools/network/web-fetch.ts +61 -0
- package/src/tools/network/web-search.ts +32 -0
- package/src/tools/system/config.ts +68 -0
- package/src/tools/system/mcp.ts +75 -0
- package/src/ui/app.tsx +317 -0
- package/src/ui/chat.tsx +76 -0
- package/src/ui/commands.ts +2232 -0
- package/src/ui/input.tsx +83 -0
- package/src/ui/picker.tsx +209 -0
package/src/ui/input.tsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { Box, Text } from 'ink'
|
|
3
|
+
import TextInput from 'ink-text-input'
|
|
4
|
+
|
|
5
|
+
interface InputBarProps {
|
|
6
|
+
onSubmit: (input: string) => void
|
|
7
|
+
isLoading: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// ── Creative status messages (inspired by Claude Code's whimsical verbs) ──
|
|
11
|
+
|
|
12
|
+
const STATUS_VERBS = [
|
|
13
|
+
'思忖中', // Pondering
|
|
14
|
+
'推演中', // Deducting
|
|
15
|
+
'凝炼中', // Condensing
|
|
16
|
+
'熬煮中', // Simmering
|
|
17
|
+
'锻造中', // Forging
|
|
18
|
+
'研磨中', // Grinding
|
|
19
|
+
'解构中', // Deconstructing
|
|
20
|
+
'冥思中', // Contemplating
|
|
21
|
+
'编织中', // Weaving
|
|
22
|
+
'萃取中', // Extracting
|
|
23
|
+
'烹制中', // Cooking
|
|
24
|
+
'淬火中', // Quenching
|
|
25
|
+
'雕琢中', // Chiseling
|
|
26
|
+
'融汇中', // Fusing
|
|
27
|
+
'觉照中', // Illuminating
|
|
28
|
+
'运转中', // Operating
|
|
29
|
+
'参详中', // Studying
|
|
30
|
+
'化合中', // Synthesizing
|
|
31
|
+
'浸润中', // Infusing
|
|
32
|
+
'焙烤中', // Baking
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
const STATUS_EMOJI = ['🔮', '⚙️', '💎', '🔥', '🧪', '🌊', '⚡', '🌀', '🎯', '💡']
|
|
36
|
+
|
|
37
|
+
function pick<T>(arr: T[]): T {
|
|
38
|
+
return arr[Math.floor(Math.random() * arr.length)]!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function InputBar({ onSubmit, isLoading }: InputBarProps) {
|
|
42
|
+
const [value, setValue] = useState('')
|
|
43
|
+
const [verb, setVerb] = useState(() => pick(STATUS_VERBS))
|
|
44
|
+
const [emoji, setEmoji] = useState(() => pick(STATUS_EMOJI))
|
|
45
|
+
|
|
46
|
+
// Rotate status messages while loading
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isLoading) return
|
|
49
|
+
const interval = setInterval(() => {
|
|
50
|
+
setVerb(pick(STATUS_VERBS))
|
|
51
|
+
setEmoji(pick(STATUS_EMOJI))
|
|
52
|
+
}, 2000)
|
|
53
|
+
return () => clearInterval(interval)
|
|
54
|
+
}, [isLoading])
|
|
55
|
+
|
|
56
|
+
// Reset when loading starts
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (isLoading) {
|
|
59
|
+
setVerb(pick(STATUS_VERBS))
|
|
60
|
+
setEmoji(pick(STATUS_EMOJI))
|
|
61
|
+
}
|
|
62
|
+
}, [isLoading])
|
|
63
|
+
|
|
64
|
+
const handleSubmit = (val: string) => {
|
|
65
|
+
if (!val.trim() || isLoading) return
|
|
66
|
+
onSubmit(val)
|
|
67
|
+
setValue('')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Box marginTop={1}>
|
|
72
|
+
<Box marginRight={1}>
|
|
73
|
+
<Text color={isLoading ? 'yellow' : 'cyan'}>{isLoading ? `${emoji}` : '▸'}</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
<TextInput
|
|
76
|
+
value={value}
|
|
77
|
+
onChange={setValue}
|
|
78
|
+
onSubmit={handleSubmit}
|
|
79
|
+
placeholder={isLoading ? `${verb}...` : 'Type a message (Esc to exit)...'}
|
|
80
|
+
/>
|
|
81
|
+
</Box>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react'
|
|
2
|
+
import { Box, Text, useInput } from 'ink'
|
|
3
|
+
import type { MiphamConfig, ProviderConfig, ModelInfo } from '../shared/index.ts'
|
|
4
|
+
|
|
5
|
+
interface PickerProps {
|
|
6
|
+
config: MiphamConfig
|
|
7
|
+
currentProvider: string
|
|
8
|
+
currentModel: string
|
|
9
|
+
onSelect: (providerId: string, modelId: string) => void
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Panel = 'provider' | 'model'
|
|
14
|
+
|
|
15
|
+
export function ModelPicker({
|
|
16
|
+
config,
|
|
17
|
+
currentProvider,
|
|
18
|
+
currentModel,
|
|
19
|
+
onSelect,
|
|
20
|
+
onClose,
|
|
21
|
+
}: PickerProps) {
|
|
22
|
+
// Get active providers only
|
|
23
|
+
const providers = config.providers.filter((p) => p.status !== 'upcoming')
|
|
24
|
+
const activeModels = (p: ProviderConfig) => p.models.filter((m) => m.status === 'active')
|
|
25
|
+
|
|
26
|
+
// State
|
|
27
|
+
const [activePanel, setActivePanel] = useState<Panel>('provider')
|
|
28
|
+
const [providerIdx, setProviderIdx] = useState(() => {
|
|
29
|
+
const idx = providers.findIndex((p) => p.id === currentProvider)
|
|
30
|
+
return idx >= 0 ? idx : 0
|
|
31
|
+
})
|
|
32
|
+
const [modelIdx, setModelIdx] = useState(0)
|
|
33
|
+
|
|
34
|
+
const selectedProvider = providers[providerIdx]
|
|
35
|
+
const models = selectedProvider ? activeModels(selectedProvider) : []
|
|
36
|
+
|
|
37
|
+
// Reset model index when provider changes
|
|
38
|
+
const goToProvider = useCallback(
|
|
39
|
+
(idx: number) => {
|
|
40
|
+
const wrapped = ((idx % providers.length) + providers.length) % providers.length
|
|
41
|
+
setProviderIdx(wrapped)
|
|
42
|
+
setModelIdx(0)
|
|
43
|
+
setActivePanel('model') // auto-switch to model panel
|
|
44
|
+
},
|
|
45
|
+
[providers.length],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const selectModel = useCallback(
|
|
49
|
+
(idx: number) => {
|
|
50
|
+
if (!selectedProvider) return
|
|
51
|
+
const wrapped = ((idx % models.length) + models.length) % models.length
|
|
52
|
+
setModelIdx(wrapped)
|
|
53
|
+
},
|
|
54
|
+
[selectedProvider, models.length],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const confirmSelection = useCallback(() => {
|
|
58
|
+
if (!selectedProvider) return
|
|
59
|
+
const model = models[modelIdx]
|
|
60
|
+
if (model) {
|
|
61
|
+
onSelect(selectedProvider.id, model.id)
|
|
62
|
+
}
|
|
63
|
+
}, [selectedProvider, models, modelIdx, onSelect])
|
|
64
|
+
|
|
65
|
+
useInput((input, key) => {
|
|
66
|
+
// Global keys
|
|
67
|
+
if (key.escape) {
|
|
68
|
+
onClose()
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (key.return) {
|
|
73
|
+
if (activePanel === 'provider') {
|
|
74
|
+
goToProvider(providerIdx) // switches to model panel
|
|
75
|
+
} else {
|
|
76
|
+
confirmSelection()
|
|
77
|
+
}
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Tab or right arrow → switch to model panel
|
|
82
|
+
if (key.tab || (activePanel === 'provider' && input === 'l')) {
|
|
83
|
+
setActivePanel('model')
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Left arrow → switch to provider panel
|
|
88
|
+
if (key.leftArrow || (activePanel === 'model' && input === 'h')) {
|
|
89
|
+
setActivePanel('provider')
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Up/Down navigation
|
|
94
|
+
if (key.upArrow) {
|
|
95
|
+
if (activePanel === 'provider') {
|
|
96
|
+
setProviderIdx((providerIdx - 1 + providers.length) % providers.length)
|
|
97
|
+
} else {
|
|
98
|
+
selectModel(modelIdx - 1)
|
|
99
|
+
}
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (key.downArrow) {
|
|
104
|
+
if (activePanel === 'provider') {
|
|
105
|
+
setProviderIdx((providerIdx + 1) % providers.length)
|
|
106
|
+
} else {
|
|
107
|
+
selectModel(modelIdx + 1)
|
|
108
|
+
}
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Provider labels
|
|
114
|
+
const providerColor = (p: ProviderConfig) => (p.id === currentProvider ? 'green' : 'white')
|
|
115
|
+
|
|
116
|
+
const modelColor = (m: ModelInfo) =>
|
|
117
|
+
selectedProvider?.id === currentProvider && m.id === currentModel ? 'green' : 'white'
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Box flexDirection="column" borderStyle="round" borderColor="cyan" padding={1}>
|
|
121
|
+
{/* Title */}
|
|
122
|
+
<Box marginBottom={1}>
|
|
123
|
+
<Text bold color="cyan">
|
|
124
|
+
Select Model
|
|
125
|
+
</Text>
|
|
126
|
+
<Text dimColor> ←→ navigate · ↑↓ move · Enter select · Esc close</Text>
|
|
127
|
+
</Box>
|
|
128
|
+
|
|
129
|
+
{/* Two-column layout */}
|
|
130
|
+
<Box flexDirection="row" gap={4}>
|
|
131
|
+
{/* ── Provider Panel (一级) ── */}
|
|
132
|
+
<Box
|
|
133
|
+
flexDirection="column"
|
|
134
|
+
width={28}
|
|
135
|
+
borderStyle="single"
|
|
136
|
+
borderColor={activePanel === 'provider' ? 'cyan' : 'gray'}
|
|
137
|
+
padding={1}
|
|
138
|
+
>
|
|
139
|
+
<Text bold underline dimColor>
|
|
140
|
+
PROVIDER {activePanel === 'provider' ? '◀' : ''}
|
|
141
|
+
</Text>
|
|
142
|
+
{providers.map((p, i) => {
|
|
143
|
+
const isCurrent = p.id === currentProvider
|
|
144
|
+
const isSelected = i === providerIdx
|
|
145
|
+
const isUpcoming = p.status === 'upcoming'
|
|
146
|
+
return (
|
|
147
|
+
<Box key={p.id}>
|
|
148
|
+
<Text
|
|
149
|
+
color={isUpcoming ? 'gray' : isSelected ? 'cyan' : providerColor(p)}
|
|
150
|
+
bold={isSelected}
|
|
151
|
+
>
|
|
152
|
+
{isSelected ? '▶ ' : ' '}
|
|
153
|
+
{isCurrent ? '✓' : ' '}
|
|
154
|
+
{p.name.padEnd(16)}
|
|
155
|
+
</Text>
|
|
156
|
+
<Text dimColor>{isUpcoming ? '[coming]' : `${activeModels(p).length}m`}</Text>
|
|
157
|
+
</Box>
|
|
158
|
+
)
|
|
159
|
+
})}
|
|
160
|
+
</Box>
|
|
161
|
+
|
|
162
|
+
{/* ── Model Panel (二级) ── */}
|
|
163
|
+
<Box
|
|
164
|
+
flexDirection="column"
|
|
165
|
+
width={42}
|
|
166
|
+
borderStyle="single"
|
|
167
|
+
borderColor={activePanel === 'model' ? 'cyan' : 'gray'}
|
|
168
|
+
padding={1}
|
|
169
|
+
>
|
|
170
|
+
<Text bold underline dimColor>
|
|
171
|
+
MODELS {activePanel === 'model' ? '◀' : ''}
|
|
172
|
+
{selectedProvider ? ` — ${selectedProvider.name}` : ''}
|
|
173
|
+
</Text>
|
|
174
|
+
{models.length === 0 && <Text dimColor> (no active models)</Text>}
|
|
175
|
+
{models.map((m, i) => {
|
|
176
|
+
const isCurrentModel = selectedProvider?.id === currentProvider && m.id === currentModel
|
|
177
|
+
const isSelected = i === modelIdx
|
|
178
|
+
return (
|
|
179
|
+
<Box key={m.id} flexDirection="column">
|
|
180
|
+
<Text
|
|
181
|
+
color={isCurrentModel ? 'green' : isSelected ? 'cyan' : 'white'}
|
|
182
|
+
bold={isSelected}
|
|
183
|
+
>
|
|
184
|
+
{isSelected ? '▶ ' : ' '}
|
|
185
|
+
{isCurrentModel ? '✓' : ' '}
|
|
186
|
+
{m.name}
|
|
187
|
+
</Text>
|
|
188
|
+
<Text dimColor>
|
|
189
|
+
{' '}
|
|
190
|
+
{m.id} · {m.contextWindow.toLocaleString()} ctx
|
|
191
|
+
{m.vision ? ' · 🖼 vision' : ''}
|
|
192
|
+
</Text>
|
|
193
|
+
</Box>
|
|
194
|
+
)
|
|
195
|
+
})}
|
|
196
|
+
</Box>
|
|
197
|
+
</Box>
|
|
198
|
+
|
|
199
|
+
{/* Footer hint */}
|
|
200
|
+
<Box marginTop={1}>
|
|
201
|
+
<Text dimColor>
|
|
202
|
+
{activePanel === 'provider'
|
|
203
|
+
? 'Select a provider → Enter to see models'
|
|
204
|
+
: 'Select a model → Enter to switch'}
|
|
205
|
+
</Text>
|
|
206
|
+
</Box>
|
|
207
|
+
</Box>
|
|
208
|
+
)
|
|
209
|
+
}
|