@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
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import type { ProviderConfig } from './types'
|
|
2
|
+
|
|
3
|
+
// Providers: alphabetical by id. Models: Lite/Flash → Text → Vision → Code → Pro/Think
|
|
4
|
+
export const DEFAULT_PROVIDERS: ProviderConfig[] = [
|
|
5
|
+
{
|
|
6
|
+
id: 'anthropic',
|
|
7
|
+
name: 'Anthropic Claude',
|
|
8
|
+
protocol: 'anthropic',
|
|
9
|
+
apiKey: '${ANTHROPIC_API_KEY}',
|
|
10
|
+
models: [
|
|
11
|
+
{
|
|
12
|
+
id: 'claude-haiku-4-5-20251001',
|
|
13
|
+
name: 'Claude Haiku 4.5',
|
|
14
|
+
providerId: 'anthropic',
|
|
15
|
+
contextWindow: 200_000,
|
|
16
|
+
maxOutput: 32_000,
|
|
17
|
+
vision: true,
|
|
18
|
+
status: 'active',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'claude-sonnet-4-6',
|
|
22
|
+
name: 'Claude Sonnet 4.6',
|
|
23
|
+
providerId: 'anthropic',
|
|
24
|
+
contextWindow: 1_000_000,
|
|
25
|
+
maxOutput: 128_000,
|
|
26
|
+
vision: true,
|
|
27
|
+
status: 'active',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'claude-opus-4-8',
|
|
31
|
+
name: 'Claude Opus 4.8',
|
|
32
|
+
providerId: 'anthropic',
|
|
33
|
+
contextWindow: 1_000_000,
|
|
34
|
+
maxOutput: 128_000,
|
|
35
|
+
vision: true,
|
|
36
|
+
status: 'active',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'deepseek',
|
|
42
|
+
name: 'DeepSeek',
|
|
43
|
+
protocol: 'openai-compatible',
|
|
44
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
45
|
+
apiKey: '${DEEPSEEK_API_KEY}',
|
|
46
|
+
models: [
|
|
47
|
+
{
|
|
48
|
+
id: 'deepseek-v4-flash',
|
|
49
|
+
name: 'DeepSeek V4 Flash',
|
|
50
|
+
providerId: 'deepseek',
|
|
51
|
+
contextWindow: 1_000_000,
|
|
52
|
+
maxOutput: 384_000,
|
|
53
|
+
vision: false,
|
|
54
|
+
status: 'active',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'deepseek-v4-pro',
|
|
58
|
+
name: 'DeepSeek V4 Pro',
|
|
59
|
+
providerId: 'deepseek',
|
|
60
|
+
contextWindow: 1_000_000,
|
|
61
|
+
maxOutput: 384_000,
|
|
62
|
+
vision: false,
|
|
63
|
+
status: 'active',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'google',
|
|
69
|
+
name: 'Google Gemini',
|
|
70
|
+
protocol: 'openai-compatible',
|
|
71
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
72
|
+
apiKey: '${GEMINI_API_KEY}',
|
|
73
|
+
models: [
|
|
74
|
+
{
|
|
75
|
+
id: 'gemini-3.0-flash',
|
|
76
|
+
name: 'Gemini 3.0 Flash',
|
|
77
|
+
providerId: 'google',
|
|
78
|
+
contextWindow: 1_000_000,
|
|
79
|
+
maxOutput: 64_000,
|
|
80
|
+
vision: true,
|
|
81
|
+
status: 'active',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'gemini-3.0-pro',
|
|
85
|
+
name: 'Gemini 3.0 Pro',
|
|
86
|
+
providerId: 'google',
|
|
87
|
+
contextWindow: 1_000_000,
|
|
88
|
+
maxOutput: 128_000,
|
|
89
|
+
vision: true,
|
|
90
|
+
status: 'active',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'gemini-2.5-pro',
|
|
94
|
+
name: 'Gemini 2.5 Pro',
|
|
95
|
+
providerId: 'google',
|
|
96
|
+
contextWindow: 1_000_000,
|
|
97
|
+
maxOutput: 64_000,
|
|
98
|
+
vision: true,
|
|
99
|
+
status: 'active',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'doubao',
|
|
105
|
+
name: '豆包 (字节跳动)',
|
|
106
|
+
protocol: 'openai-compatible',
|
|
107
|
+
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
|
|
108
|
+
apiKey: '${DOUBAO_API_KEY}',
|
|
109
|
+
models: [
|
|
110
|
+
{
|
|
111
|
+
id: 'doubao-seed-1.6-flash',
|
|
112
|
+
name: '豆包 Seed 1.6 Flash',
|
|
113
|
+
providerId: 'doubao',
|
|
114
|
+
contextWindow: 256_000,
|
|
115
|
+
maxOutput: 128_000,
|
|
116
|
+
vision: true,
|
|
117
|
+
status: 'active',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'doubao-seed-2.0-mini',
|
|
121
|
+
name: '豆包 Seed 2.0 Mini',
|
|
122
|
+
providerId: 'doubao',
|
|
123
|
+
contextWindow: 256_000,
|
|
124
|
+
maxOutput: 128_000,
|
|
125
|
+
vision: true,
|
|
126
|
+
status: 'active',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'doubao-seed-2.0-lite',
|
|
130
|
+
name: '豆包 Seed 2.0 Lite',
|
|
131
|
+
providerId: 'doubao',
|
|
132
|
+
contextWindow: 256_000,
|
|
133
|
+
maxOutput: 128_000,
|
|
134
|
+
vision: true,
|
|
135
|
+
status: 'active',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'doubao-seed-1.6',
|
|
139
|
+
name: '豆包 Seed 1.6',
|
|
140
|
+
providerId: 'doubao',
|
|
141
|
+
contextWindow: 256_000,
|
|
142
|
+
maxOutput: 128_000,
|
|
143
|
+
vision: true,
|
|
144
|
+
status: 'active',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'doubao-seed-2.0-pro',
|
|
148
|
+
name: '豆包 Seed 2.0 Pro',
|
|
149
|
+
providerId: 'doubao',
|
|
150
|
+
contextWindow: 256_000,
|
|
151
|
+
maxOutput: 128_000,
|
|
152
|
+
vision: true,
|
|
153
|
+
status: 'active',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'doubao-seed-2.0-code',
|
|
157
|
+
name: '豆包 Seed 2.0 Code',
|
|
158
|
+
providerId: 'doubao',
|
|
159
|
+
contextWindow: 256_000,
|
|
160
|
+
maxOutput: 128_000,
|
|
161
|
+
vision: true,
|
|
162
|
+
status: 'active',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'hunyuan',
|
|
168
|
+
name: '腾讯混元',
|
|
169
|
+
protocol: 'openai-compatible',
|
|
170
|
+
baseUrl: 'https://api.hunyuan.cloud.tencent.com/v1',
|
|
171
|
+
apiKey: '${HUNYUAN_API_KEY}',
|
|
172
|
+
models: [
|
|
173
|
+
{
|
|
174
|
+
id: 'hunyuan-lite',
|
|
175
|
+
name: '混元 Lite (免费)',
|
|
176
|
+
providerId: 'hunyuan',
|
|
177
|
+
contextWindow: 256_000,
|
|
178
|
+
maxOutput: 6_000,
|
|
179
|
+
vision: false,
|
|
180
|
+
status: 'active',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'hunyuan-turbos-latest',
|
|
184
|
+
name: '混元 TurboS',
|
|
185
|
+
providerId: 'hunyuan',
|
|
186
|
+
contextWindow: 32_000,
|
|
187
|
+
maxOutput: 16_000,
|
|
188
|
+
vision: false,
|
|
189
|
+
status: 'active',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'hunyuan-2.0-instruct-20251111',
|
|
193
|
+
name: '混元 2.0 Instruct',
|
|
194
|
+
providerId: 'hunyuan',
|
|
195
|
+
contextWindow: 144_000,
|
|
196
|
+
maxOutput: 16_000,
|
|
197
|
+
vision: false,
|
|
198
|
+
status: 'active',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'hunyuan-t1-vision-20250916',
|
|
202
|
+
name: '混元 T1 Vision',
|
|
203
|
+
providerId: 'hunyuan',
|
|
204
|
+
contextWindow: 32_000,
|
|
205
|
+
maxOutput: 64_000,
|
|
206
|
+
vision: true,
|
|
207
|
+
status: 'active',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'hunyuan-a13b',
|
|
211
|
+
name: '混元 A13B',
|
|
212
|
+
providerId: 'hunyuan',
|
|
213
|
+
contextWindow: 224_000,
|
|
214
|
+
maxOutput: 32_000,
|
|
215
|
+
vision: false,
|
|
216
|
+
status: 'active',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'hunyuan-2.0-thinking-20251109',
|
|
220
|
+
name: '混元 2.0 Think',
|
|
221
|
+
providerId: 'hunyuan',
|
|
222
|
+
contextWindow: 192_000,
|
|
223
|
+
maxOutput: 64_000,
|
|
224
|
+
vision: false,
|
|
225
|
+
status: 'active',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'hunyuan-t1-latest',
|
|
229
|
+
name: '混元 T1 (推理)',
|
|
230
|
+
providerId: 'hunyuan',
|
|
231
|
+
contextWindow: 32_000,
|
|
232
|
+
maxOutput: 64_000,
|
|
233
|
+
vision: false,
|
|
234
|
+
status: 'active',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: 'hy3-preview',
|
|
238
|
+
name: '混元 Hy3 Preview',
|
|
239
|
+
providerId: 'hunyuan',
|
|
240
|
+
contextWindow: 256_000,
|
|
241
|
+
maxOutput: 128_000,
|
|
242
|
+
vision: false,
|
|
243
|
+
status: 'active',
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'mipham',
|
|
249
|
+
name: 'MiphamAI',
|
|
250
|
+
protocol: 'openai-compatible',
|
|
251
|
+
baseUrl: 'https://api.mipham.ai/v1',
|
|
252
|
+
apiKey: '${MIPHAM_API_KEY}',
|
|
253
|
+
models: [
|
|
254
|
+
{
|
|
255
|
+
id: 'om-V5-Flash',
|
|
256
|
+
name: 'OM V5 Flash',
|
|
257
|
+
providerId: 'mipham',
|
|
258
|
+
contextWindow: 1_000_000,
|
|
259
|
+
maxOutput: 128_000,
|
|
260
|
+
vision: false,
|
|
261
|
+
status: 'upcoming',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'om-V5-Pro',
|
|
265
|
+
name: 'OM V5 Pro',
|
|
266
|
+
providerId: 'mipham',
|
|
267
|
+
contextWindow: 1_000_000,
|
|
268
|
+
maxOutput: 128_000,
|
|
269
|
+
vision: false,
|
|
270
|
+
status: 'upcoming',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'om-V5-Visual',
|
|
274
|
+
name: 'OM V5 Visual',
|
|
275
|
+
providerId: 'mipham',
|
|
276
|
+
contextWindow: 200_000,
|
|
277
|
+
maxOutput: 32_000,
|
|
278
|
+
vision: true,
|
|
279
|
+
status: 'upcoming',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
status: 'upcoming',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'openai',
|
|
286
|
+
name: 'OpenAI',
|
|
287
|
+
protocol: 'openai-compatible',
|
|
288
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
289
|
+
apiKey: '${OPENAI_API_KEY}',
|
|
290
|
+
models: [
|
|
291
|
+
{
|
|
292
|
+
id: 'gpt-5.4-mini',
|
|
293
|
+
name: 'GPT-5.4 Mini',
|
|
294
|
+
providerId: 'openai',
|
|
295
|
+
contextWindow: 400_000,
|
|
296
|
+
maxOutput: 32_000,
|
|
297
|
+
vision: true,
|
|
298
|
+
status: 'active',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'gpt-5.4',
|
|
302
|
+
name: 'GPT-5.4',
|
|
303
|
+
providerId: 'openai',
|
|
304
|
+
contextWindow: 1_050_000,
|
|
305
|
+
maxOutput: 128_000,
|
|
306
|
+
vision: true,
|
|
307
|
+
status: 'active',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 'gpt-5.5',
|
|
311
|
+
name: 'GPT-5.5',
|
|
312
|
+
providerId: 'openai',
|
|
313
|
+
contextWindow: 1_050_000,
|
|
314
|
+
maxOutput: 128_000,
|
|
315
|
+
vision: true,
|
|
316
|
+
status: 'active',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'gpt-5.3-codex',
|
|
320
|
+
name: 'GPT-5.3 Codex',
|
|
321
|
+
providerId: 'openai',
|
|
322
|
+
contextWindow: 400_000,
|
|
323
|
+
maxOutput: 64_000,
|
|
324
|
+
vision: false,
|
|
325
|
+
status: 'active',
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: 'qwen',
|
|
331
|
+
name: '通义千问',
|
|
332
|
+
protocol: 'openai-compatible',
|
|
333
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
334
|
+
apiKey: '${QWEN_API_KEY}',
|
|
335
|
+
models: [
|
|
336
|
+
{
|
|
337
|
+
id: 'qwen-plus',
|
|
338
|
+
name: 'Qwen Plus',
|
|
339
|
+
providerId: 'qwen',
|
|
340
|
+
contextWindow: 128_000,
|
|
341
|
+
maxOutput: 32_000,
|
|
342
|
+
vision: true,
|
|
343
|
+
status: 'active',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: 'qwen-max',
|
|
347
|
+
name: 'Qwen Max',
|
|
348
|
+
providerId: 'qwen',
|
|
349
|
+
contextWindow: 128_000,
|
|
350
|
+
maxOutput: 32_000,
|
|
351
|
+
vision: true,
|
|
352
|
+
status: 'active',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
export const PROTOCOL_LABELS: Record<string, string> = {
|
|
359
|
+
'openai-compatible': 'OpenAI Compatible',
|
|
360
|
+
anthropic: 'Anthropic',
|
|
361
|
+
custom: 'Custom Protocol',
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export const TOOL_CATEGORIES = ['file', 'exec', 'agent', 'network', 'system'] as const
|
|
365
|
+
export const CONFIG_FILE_NAME = 'config.yml'
|
|
366
|
+
export const MIPHAM_DIR = '.mipham'
|
|
367
|
+
export const USER_CONFIG_DIR = '.mipham'
|
|
368
|
+
export const MEMORY_DIR = 'memory'
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// ── Provider Types ──
|
|
2
|
+
export type ProtocolType = 'openai-compatible' | 'anthropic' | 'custom'
|
|
3
|
+
|
|
4
|
+
export interface ModelInfo {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
providerId: string
|
|
8
|
+
contextWindow: number
|
|
9
|
+
maxOutput: number
|
|
10
|
+
vision: boolean
|
|
11
|
+
status: 'active' | 'upcoming' | 'deprecated'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ProviderConfig {
|
|
15
|
+
id: string
|
|
16
|
+
name: string
|
|
17
|
+
protocol: ProtocolType
|
|
18
|
+
baseUrl?: string
|
|
19
|
+
apiKey: string
|
|
20
|
+
models: ModelInfo[]
|
|
21
|
+
status?: 'active' | 'upcoming'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Message Types ──
|
|
25
|
+
export interface TextContent {
|
|
26
|
+
type: 'text'
|
|
27
|
+
text: string
|
|
28
|
+
}
|
|
29
|
+
export interface ImageContent {
|
|
30
|
+
type: 'image_url'
|
|
31
|
+
image_url: { url: string }
|
|
32
|
+
}
|
|
33
|
+
export interface ToolUseContent {
|
|
34
|
+
type: 'tool_use'
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
input: Record<string, unknown>
|
|
38
|
+
}
|
|
39
|
+
export interface ToolResultContent {
|
|
40
|
+
type: 'tool_result'
|
|
41
|
+
tool_use_id: string
|
|
42
|
+
content: string
|
|
43
|
+
}
|
|
44
|
+
export type ContentBlock = TextContent | ImageContent | ToolUseContent | ToolResultContent
|
|
45
|
+
|
|
46
|
+
export interface Message {
|
|
47
|
+
role: 'system' | 'user' | 'assistant'
|
|
48
|
+
content: string | ContentBlock[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Tool Types ──
|
|
52
|
+
export type ToolPermission = 'auto' | 'ask' | 'bypass'
|
|
53
|
+
export type ToolCategory = 'file' | 'exec' | 'agent' | 'network' | 'system'
|
|
54
|
+
|
|
55
|
+
export interface ToolContext {
|
|
56
|
+
cwd: string
|
|
57
|
+
sessionId: string
|
|
58
|
+
provider: string
|
|
59
|
+
model: string
|
|
60
|
+
skillsLoader?: import('../skills/loader').SkillsLoader
|
|
61
|
+
registry?: import('../providers/registry').ProviderRegistry
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolResult {
|
|
65
|
+
success: boolean
|
|
66
|
+
content: string
|
|
67
|
+
error?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ToolDefinition {
|
|
71
|
+
name: string
|
|
72
|
+
description: string
|
|
73
|
+
category: ToolCategory
|
|
74
|
+
permission: ToolPermission
|
|
75
|
+
parameters: Record<string, unknown>
|
|
76
|
+
execute: (params: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Stream Types ──
|
|
80
|
+
export interface StreamChunk {
|
|
81
|
+
type: 'text' | 'tool_use' | 'tool_result' | 'stop' | 'error'
|
|
82
|
+
content?: string
|
|
83
|
+
toolUse?: ToolUseContent
|
|
84
|
+
tool_use_id?: string
|
|
85
|
+
error?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Config Types ──
|
|
89
|
+
export interface MiphamConfig {
|
|
90
|
+
version: string
|
|
91
|
+
defaultProvider: string
|
|
92
|
+
defaultModel: string
|
|
93
|
+
permission: ToolPermission
|
|
94
|
+
providers: ProviderConfig[]
|
|
95
|
+
skills?: { paths: string[]; mcpServers: McpServerConfig[] }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface McpServerConfig {
|
|
99
|
+
name: string
|
|
100
|
+
command: string
|
|
101
|
+
args: string[]
|
|
102
|
+
env?: Record<string, string>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Skill Types ──
|
|
106
|
+
export interface SkillDefinition {
|
|
107
|
+
name: string
|
|
108
|
+
description: string
|
|
109
|
+
version: string
|
|
110
|
+
type: 'standard' | 'mipham'
|
|
111
|
+
tools?: ToolDefinition[]
|
|
112
|
+
hooks?: HookDefinition[]
|
|
113
|
+
prompts?: Record<string, string>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Hook Types ──
|
|
117
|
+
export type HookEvent =
|
|
118
|
+
| 'PreToolUse'
|
|
119
|
+
| 'PostToolUse'
|
|
120
|
+
| 'SessionStart'
|
|
121
|
+
| 'SessionEnd'
|
|
122
|
+
| 'Notification'
|
|
123
|
+
|
|
124
|
+
export interface HookDefinition {
|
|
125
|
+
event: HookEvent
|
|
126
|
+
toolName?: string
|
|
127
|
+
handler: (context: HookContext) => Promise<HookResult>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface HookContext {
|
|
131
|
+
event: HookEvent
|
|
132
|
+
toolName?: string
|
|
133
|
+
toolInput?: Record<string, unknown>
|
|
134
|
+
toolResult?: ToolResult
|
|
135
|
+
sessionId: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface HookResult {
|
|
139
|
+
allowed: boolean
|
|
140
|
+
reason?: string
|
|
141
|
+
modifiedInput?: Record<string, unknown>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Instruction Types ──
|
|
145
|
+
export interface InstructionFile {
|
|
146
|
+
path: string
|
|
147
|
+
level: 'group' | 'company' | 'project' | 'directory' | 'user'
|
|
148
|
+
privacy: 'public' | 'project' | 'private'
|
|
149
|
+
language: string
|
|
150
|
+
content: string
|
|
151
|
+
frontmatter: Record<string, unknown>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Permission Types ──
|
|
155
|
+
export type PermissionLevel = 'auto' | 'ask' | 'bypass'
|
|
156
|
+
|
|
157
|
+
export interface PermissionRule {
|
|
158
|
+
toolName: string
|
|
159
|
+
level: PermissionLevel
|
|
160
|
+
pattern?: string
|
|
161
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { join, extname } from 'node:path'
|
|
3
|
+
import { parse as parseYaml } from 'yaml'
|
|
4
|
+
import type { SkillDefinition } from '../shared/index.ts'
|
|
5
|
+
|
|
6
|
+
interface FrontmatterResult {
|
|
7
|
+
data: Record<string, unknown>
|
|
8
|
+
content: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseFrontmatter(raw: string): FrontmatterResult {
|
|
12
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
|
|
13
|
+
if (!match) {
|
|
14
|
+
return { data: {}, content: raw }
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
data: parseYaml(match[1] || '') as Record<string, unknown>,
|
|
18
|
+
content: match[2] || '',
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SkillsLoader {
|
|
23
|
+
private skills = new Map<string, SkillDefinition>()
|
|
24
|
+
|
|
25
|
+
loadBuiltin(basePath: string): void {
|
|
26
|
+
// Load standard skills (*.SKILL.md)
|
|
27
|
+
const standardDir = join(basePath, 'skills', 'standard')
|
|
28
|
+
if (existsSync(standardDir)) {
|
|
29
|
+
this.loadDirectory(standardDir, 'standard')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Load mipham skills (*.mipham-skill.md)
|
|
33
|
+
const miphamDir = join(basePath, 'skills', 'mipham')
|
|
34
|
+
if (existsSync(miphamDir)) {
|
|
35
|
+
this.loadDirectory(miphamDir, 'mipham')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
loadExternal(paths: string[]): void {
|
|
40
|
+
for (const p of paths) {
|
|
41
|
+
if (existsSync(p)) {
|
|
42
|
+
const stat = statSync(p)
|
|
43
|
+
if (stat.isDirectory()) {
|
|
44
|
+
this.loadDirectory(p, 'standard')
|
|
45
|
+
} else if (stat.isFile()) {
|
|
46
|
+
this.tryLoad(p, 'standard')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get(name: string): SkillDefinition | undefined {
|
|
53
|
+
return this.skills.get(name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
list(): SkillDefinition[] {
|
|
57
|
+
return Array.from(this.skills.values())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
listByType(type: 'standard' | 'mipham'): SkillDefinition[] {
|
|
61
|
+
return Array.from(this.skills.values()).filter((s) => s.type === type)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
has(name: string): boolean {
|
|
65
|
+
return this.skills.has(name)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private loadDirectory(dir: string, type: 'standard' | 'mipham'): void {
|
|
69
|
+
try {
|
|
70
|
+
const entries = readdirSync(dir)
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const fullPath = join(dir, entry)
|
|
73
|
+
const isSkillFile = entry.endsWith('.SKILL.md') || entry.endsWith('.mipham-skill.md')
|
|
74
|
+
|
|
75
|
+
if (isSkillFile) {
|
|
76
|
+
this.tryLoad(fullPath, type)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// skip unreadable
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private tryLoad(path: string, type: 'standard' | 'mipham'): void {
|
|
85
|
+
try {
|
|
86
|
+
const raw = readFileSync(path, 'utf-8')
|
|
87
|
+
const { data, content } = parseFrontmatter(raw)
|
|
88
|
+
|
|
89
|
+
const skill: SkillDefinition = {
|
|
90
|
+
name: (data.name as string) || this.nameFromPath(path),
|
|
91
|
+
description: (data.description as string) || '',
|
|
92
|
+
version: (data.version as string) || '0.1.0',
|
|
93
|
+
type,
|
|
94
|
+
tools: data.tools as SkillDefinition['tools'],
|
|
95
|
+
hooks: data.hooks as SkillDefinition['hooks'],
|
|
96
|
+
prompts: data.prompts as SkillDefinition['prompts'],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.skills.set(skill.name, skill)
|
|
100
|
+
} catch {
|
|
101
|
+
// skip unparseable
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private nameFromPath(path: string): string {
|
|
106
|
+
const base = path.split('/').pop() || ''
|
|
107
|
+
return base.replace(/\.(SKILL|mipham-skill)\.md$/i, '')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SkillDefinition } from '../../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export interface MiphamRuntimeContext {
|
|
4
|
+
skill: SkillDefinition
|
|
5
|
+
cwd: string
|
|
6
|
+
sessionId: string
|
|
7
|
+
modelId: string
|
|
8
|
+
providerId: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mipham exclusive skill runtime.
|
|
13
|
+
* Mipham skills use `.mipham-skill.md` extension and have access to
|
|
14
|
+
* Mipham-specific features: model optimization, security features, etc.
|
|
15
|
+
*/
|
|
16
|
+
export class MiphamRuntime {
|
|
17
|
+
private context: MiphamRuntimeContext
|
|
18
|
+
|
|
19
|
+
constructor(context: MiphamRuntimeContext) {
|
|
20
|
+
this.context = context
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getSkill(): SkillDefinition {
|
|
24
|
+
return this.context.skill
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getPrompts(): Record<string, string> {
|
|
28
|
+
return this.context.skill.prompts || {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getTools(): SkillDefinition['tools'] {
|
|
32
|
+
return this.context.skill.tools || []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getHooks(): SkillDefinition['hooks'] {
|
|
36
|
+
return this.context.skill.hooks || []
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute a named prompt with Mipham-specific context.
|
|
41
|
+
*/
|
|
42
|
+
async executePrompt(name: string, variables?: Record<string, string>): Promise<string> {
|
|
43
|
+
const prompt = this.context.skill.prompts?.[name]
|
|
44
|
+
if (!prompt) {
|
|
45
|
+
throw new Error(`Prompt "${name}" not found in skill "${this.context.skill.name}"`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let result = prompt
|
|
49
|
+
const allVars: Record<string, string> = {
|
|
50
|
+
provider: this.context.providerId,
|
|
51
|
+
model: this.context.modelId,
|
|
52
|
+
session: this.context.sessionId,
|
|
53
|
+
cwd: this.context.cwd,
|
|
54
|
+
...variables,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const [key, value] of Object.entries(allVars)) {
|
|
58
|
+
result = result.replaceAll(`\${${key}}`, value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
}
|