@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.
Files changed (67) hide show
  1. package/README.md +76 -0
  2. package/assets/icon.icns +0 -0
  3. package/assets/icon.jpg +0 -0
  4. package/bin/mipham +51 -0
  5. package/bin/mipham.ts +8 -0
  6. package/package.json +59 -0
  7. package/skills/mipham/om-model-optimize.mipham-skill.md +20 -0
  8. package/skills/mipham/om-security.mipham-skill.md +21 -0
  9. package/skills/standard/code-review.SKILL.md +116 -0
  10. package/skills/standard/compassionate-communication.SKILL.md +80 -0
  11. package/skills/standard/doc-generator.SKILL.md +21 -0
  12. package/skills/standard/github-ops.SKILL.md +22 -0
  13. package/skills/standard/memory.SKILL.md +22 -0
  14. package/skills/standard/mipham-code-setup.SKILL.md +113 -0
  15. package/skills/standard/security-review.SKILL.md +122 -0
  16. package/skills/standard/self-review.SKILL.md +20 -0
  17. package/skills/standard/superpower.SKILL.md +19 -0
  18. package/skills/standard/tdd.SKILL.md +20 -0
  19. package/skills/standard/web-search.SKILL.md +23 -0
  20. package/src/agent/sub-agent.ts +122 -0
  21. package/src/config/defaults.ts +10 -0
  22. package/src/config/loader.ts +30 -0
  23. package/src/core/context.ts +135 -0
  24. package/src/core/engine.ts +193 -0
  25. package/src/core/hooks.ts +116 -0
  26. package/src/core/instructions.ts +126 -0
  27. package/src/core/permission.ts +54 -0
  28. package/src/core/session-store.ts +136 -0
  29. package/src/index.tsx +93 -0
  30. package/src/mcp/client.ts +170 -0
  31. package/src/mcp/protocol.ts +104 -0
  32. package/src/mcp/transport.ts +169 -0
  33. package/src/mcp/types.ts +112 -0
  34. package/src/providers/anthropic.ts +286 -0
  35. package/src/providers/bootstrap.ts +28 -0
  36. package/src/providers/openai-compat.ts +158 -0
  37. package/src/providers/registry.ts +70 -0
  38. package/src/security/path.ts +90 -0
  39. package/src/security/url.ts +96 -0
  40. package/src/shared/constants.ts +368 -0
  41. package/src/shared/index.ts +2 -0
  42. package/src/shared/types.ts +161 -0
  43. package/src/skills/loader.ts +109 -0
  44. package/src/skills/mipham/runtime.ts +63 -0
  45. package/src/skills/standard/runtime.ts +59 -0
  46. package/src/tools/agent/agent.ts +51 -0
  47. package/src/tools/agent/memory.ts +51 -0
  48. package/src/tools/agent/plan.ts +87 -0
  49. package/src/tools/agent/skill.ts +58 -0
  50. package/src/tools/exec/bash.ts +111 -0
  51. package/src/tools/exec/git.ts +63 -0
  52. package/src/tools/exec/task.ts +69 -0
  53. package/src/tools/file/edit.ts +57 -0
  54. package/src/tools/file/glob.ts +29 -0
  55. package/src/tools/file/grep.ts +52 -0
  56. package/src/tools/file/read.ts +38 -0
  57. package/src/tools/file/write.ts +27 -0
  58. package/src/tools/index.ts +126 -0
  59. package/src/tools/network/web-fetch.ts +61 -0
  60. package/src/tools/network/web-search.ts +32 -0
  61. package/src/tools/system/config.ts +68 -0
  62. package/src/tools/system/mcp.ts +75 -0
  63. package/src/ui/app.tsx +317 -0
  64. package/src/ui/chat.tsx +76 -0
  65. package/src/ui/commands.ts +2232 -0
  66. package/src/ui/input.tsx +83 -0
  67. package/src/ui/picker.tsx +209 -0
@@ -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
+ }