@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,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,2 @@
1
+ export * from './types'
2
+ export * from './constants'
@@ -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
+ }