@miphamai/cli 0.81.1 → 0.81.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/package.json +5 -2
- package/src/config/wizard-config.ts +72 -0
- package/src/core/claude-md-audit.ts +32 -8
- package/src/core/engine.ts +4 -0
- package/src/i18n-core/locales/en-US.json +1 -0
- package/src/i18n-core/locales/zh-CN.json +1 -0
- package/src/index.tsx +5 -0
- package/src/shared/package-info.ts +1 -1
- package/src/ui/config-wizard.tsx +9 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miphamai/cli",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.3",
|
|
4
4
|
"description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"dev": "bun run bin/mipham.ts",
|
|
39
39
|
"build": "bun run scripts/generate-bundled-skills.ts && bun build --compile --minify ./bin/mipham.ts --outfile dist/mipham",
|
|
40
40
|
"typecheck": "tsc --noEmit",
|
|
41
|
-
"test": "vitest run"
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"knip": "knip --production --no-progress --no-exit-code",
|
|
43
|
+
"coverage": "vitest run --coverage --coverage.provider=v8 --coverage.include='src/**/*.{ts,tsx}' --coverage.reporter=text-summary --coverage.reporter=json-summary"
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
46
|
"@larksuiteoapi/node-sdk": "^1.73.0",
|
|
@@ -54,6 +56,7 @@
|
|
|
54
56
|
"@types/bun": "^1.4.2",
|
|
55
57
|
"@types/node": "^22.19.19",
|
|
56
58
|
"@types/react": "^19.3.0",
|
|
59
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
57
60
|
"ink-testing-library": "^4.0.0",
|
|
58
61
|
"vitest": "^5.0.0"
|
|
59
62
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data layer for the first-run Config Wizard — no Ink, no React, so it can be
|
|
3
|
+
* tested directly (the UI component imports it, tests import it too).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_PROVIDERS } from '../shared/constants'
|
|
7
|
+
import type { ModelInfo } from '../shared/types'
|
|
8
|
+
|
|
9
|
+
/** Providers offered in the wizard's cloud path (Ollama has its own step). */
|
|
10
|
+
export const CLOUD_PROVIDERS = DEFAULT_PROVIDERS.filter(
|
|
11
|
+
(p) => p.id !== 'ollama' && p.id !== 'mipham',
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
export function getActiveModels(providerId: string): ModelInfo[] {
|
|
15
|
+
const provider = DEFAULT_PROVIDERS.find((p) => p.id === providerId)
|
|
16
|
+
return provider?.models.filter((m) => m.status === 'active') || []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build the config.yml body written by the wizard.
|
|
21
|
+
*
|
|
22
|
+
* Built-in providers get **no** `models:` block on purpose: `mergeProviders`
|
|
23
|
+
* (config/loader.ts) treats a supplied `models:` list as a wholesale
|
|
24
|
+
* replacement of the built-in list, so writing bare `- id:` lines there would
|
|
25
|
+
* strip `status` (→ the model picker shows "no active models"),
|
|
26
|
+
* `contextWindow`, `maxOutput` and `vision` from every built-in model.
|
|
27
|
+
* `models:` is written only for providers whose built-in list is empty
|
|
28
|
+
* (Ollama), where it must carry every model field in full.
|
|
29
|
+
*/
|
|
30
|
+
export function buildConfigYaml(
|
|
31
|
+
providerId: string,
|
|
32
|
+
modelId: string,
|
|
33
|
+
storedKey: string,
|
|
34
|
+
ollamaModels: ModelInfo[] = [],
|
|
35
|
+
): string {
|
|
36
|
+
const provider = DEFAULT_PROVIDERS.find((p) => p.id === providerId)
|
|
37
|
+
const hasBuiltInModels = (provider?.models.length ?? 0) > 0
|
|
38
|
+
|
|
39
|
+
const lines = [
|
|
40
|
+
'# Mipham Code Configuration',
|
|
41
|
+
`# Generated by Config Wizard — ${new Date().toISOString()}`,
|
|
42
|
+
'',
|
|
43
|
+
'version: 1',
|
|
44
|
+
`defaultProvider: ${providerId}`,
|
|
45
|
+
`defaultModel: ${modelId}`,
|
|
46
|
+
'permission: default',
|
|
47
|
+
'',
|
|
48
|
+
'providers:',
|
|
49
|
+
` - id: ${providerId}`,
|
|
50
|
+
` name: ${provider?.name || providerId}`,
|
|
51
|
+
` protocol: ${provider?.protocol || 'openai-compatible'}`,
|
|
52
|
+
...(provider?.baseUrl ? [` baseUrl: ${provider.baseUrl}`] : []),
|
|
53
|
+
` apiKey: ${storedKey}`,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
if (!hasBuiltInModels) {
|
|
57
|
+
lines.push(' models:')
|
|
58
|
+
for (const m of ollamaModels) {
|
|
59
|
+
lines.push(
|
|
60
|
+
` - id: ${m.id}`,
|
|
61
|
+
` name: ${m.name}`,
|
|
62
|
+
` providerId: ${m.providerId}`,
|
|
63
|
+
` contextWindow: ${m.contextWindow}`,
|
|
64
|
+
` maxOutput: ${m.maxOutput}`,
|
|
65
|
+
` vision: ${m.vision}`,
|
|
66
|
+
` status: ${m.status}`,
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return lines.join('\n')
|
|
72
|
+
}
|
|
@@ -44,21 +44,45 @@ const PATTERNS: Array<{ pattern: RegExp; reason: DerivableReason }> = [
|
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* A section that already points at another doc has been disclosed — there is
|
|
48
|
+
* nothing left to exclude, so the audit stays silent about it.
|
|
49
|
+
*/
|
|
50
|
+
const DOC_POINTER = /\]\([^)\s]*\.md(?:[#?][^)\s]*)?\)/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Return `##`/`###` headings whose title matches a derivable-content pattern and
|
|
54
|
+
* whose body is not already disclosed behind a `.md` pointer, in document order.
|
|
55
|
+
* Headings without a match are ignored.
|
|
49
56
|
*/
|
|
50
57
|
export function findDerivableSections(content: string): DerivableSection[] {
|
|
51
58
|
const found: DerivableSection[] = []
|
|
59
|
+
let heading: string | null = null
|
|
60
|
+
let reason: DerivableReason | null = null
|
|
61
|
+
let body: string[] = []
|
|
62
|
+
|
|
63
|
+
const flush = () => {
|
|
64
|
+
if (heading !== null && reason !== null && !DOC_POINTER.test(body.join('\n'))) {
|
|
65
|
+
found.push({ heading, reason })
|
|
66
|
+
}
|
|
67
|
+
heading = null
|
|
68
|
+
reason = null
|
|
69
|
+
body = []
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
for (const line of content.split('\n')) {
|
|
53
73
|
const m = line.match(/^(#{2,3})\s+(.+?)\s*$/)
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
if (m) {
|
|
75
|
+
flush()
|
|
76
|
+
const title = m[2]!.trim()
|
|
77
|
+
const hit = PATTERNS.find(({ pattern }) => pattern.test(title))
|
|
78
|
+
if (hit) {
|
|
79
|
+
heading = title
|
|
80
|
+
reason = hit.reason
|
|
60
81
|
}
|
|
82
|
+
continue
|
|
61
83
|
}
|
|
84
|
+
if (heading !== null) body.push(line)
|
|
62
85
|
}
|
|
86
|
+
flush()
|
|
63
87
|
return found
|
|
64
88
|
}
|
package/src/core/engine.ts
CHANGED
|
@@ -1038,6 +1038,10 @@ export class QueryEngine {
|
|
|
1038
1038
|
})
|
|
1039
1039
|
this.context.addToolResult(toolUse.id, result)
|
|
1040
1040
|
}
|
|
1041
|
+
|
|
1042
|
+
// Path-scoped rules for files touched in this round: process() injects for
|
|
1043
|
+
// the first tool round only, so the multi-turn rounds need their own call.
|
|
1044
|
+
this.injectRules()
|
|
1041
1045
|
}
|
|
1042
1046
|
// Max turns reached — safety limit, stop gracefully
|
|
1043
1047
|
}
|
|
@@ -901,6 +901,7 @@
|
|
|
901
901
|
"mode_title": "Choose connection method:",
|
|
902
902
|
"mode_cloud": "🌐 Cloud AI models (recommended)",
|
|
903
903
|
"mode_local": "🖥️ Local models (Ollama)",
|
|
904
|
+
"mode_cloud_hint": " {count} cloud providers built in",
|
|
904
905
|
"mode_local_hint": " Requires Ollama installed with models downloaded",
|
|
905
906
|
"nav_hint": "↑↓ select · Enter confirm · Esc back",
|
|
906
907
|
"provider_title": "Choose AI model provider:",
|
|
@@ -901,6 +901,7 @@
|
|
|
901
901
|
"mode_title": "选择模型连接方式:",
|
|
902
902
|
"mode_cloud": "🌐 云端 AI 模型(推荐)",
|
|
903
903
|
"mode_local": "🖥️ 本地模型(Ollama)",
|
|
904
|
+
"mode_cloud_hint": " 内置 {count} 家云端供应商",
|
|
904
905
|
"mode_local_hint": " 需要提前安装 Ollama 并下载模型",
|
|
905
906
|
"nav_hint": "↑↓ 选择 · Enter 确认 · Esc 返回",
|
|
906
907
|
"provider_title": "选择 AI 模型提供商:",
|
package/src/index.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import { loadSessionMemories, getMemoryManager } from './core/memory/memory-load
|
|
|
26
26
|
import { ContextManager } from './core/context'
|
|
27
27
|
import { PrefixCacheTracker } from './core/context-token'
|
|
28
28
|
import { QueryEngine } from './core/engine'
|
|
29
|
+
import { RulesLoader } from './core/rules-loader'
|
|
29
30
|
import { generateSessionName } from './core/session-name'
|
|
30
31
|
import { ExperienceRuleEngine } from './core/rule-engine.js'
|
|
31
32
|
import { SessionLog } from './core/session-log'
|
|
@@ -535,6 +536,10 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
535
536
|
engine.setSkills(vajraContext.get(SKILLS_KEY)!)
|
|
536
537
|
engine.setLlm(vajraContext.get(LLM_KEY)!)
|
|
537
538
|
|
|
539
|
+
// Path-scoped rules (.mipham/rules/*.md) — injected when the AI touches a
|
|
540
|
+
// matching file. Loaded once at startup; setRulesLoader performs the load.
|
|
541
|
+
engine.setRulesLoader(new RulesLoader(process.cwd()))
|
|
542
|
+
|
|
538
543
|
// Wire inference hooks (DLP) configuration
|
|
539
544
|
const inferenceHookConfig = loadInferenceHookConfig()
|
|
540
545
|
engine.setInferenceHookConfig(inferenceHookConfig)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.81.
|
|
12
|
+
export const PACKAGE_VERSION = '0.81.3' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
package/src/ui/config-wizard.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import { join } from 'node:path'
|
|
|
20
20
|
import { homedir } from 'node:os'
|
|
21
21
|
import { execSync } from 'node:child_process'
|
|
22
22
|
import { getCredentialKey, encryptApiKey } from '../config/credential-crypto'
|
|
23
|
+
import { CLOUD_PROVIDERS, getActiveModels, buildConfigYaml } from '../config/wizard-config'
|
|
23
24
|
|
|
24
25
|
// ── Types ──
|
|
25
26
|
|
|
@@ -32,24 +33,15 @@ interface Props {
|
|
|
32
33
|
|
|
33
34
|
// ── Constants ──
|
|
34
35
|
|
|
35
|
-
const CLOUD_PROVIDERS = DEFAULT_PROVIDERS.filter((p) => p.id !== 'ollama' && p.id !== 'mipham')
|
|
36
|
-
|
|
37
36
|
const SELECTED_COLOR = 'cyan'
|
|
38
37
|
|
|
39
38
|
// ── Helpers ──
|
|
40
39
|
|
|
41
|
-
function getActiveModels(providerId: string): ModelInfo[] {
|
|
42
|
-
const provider = DEFAULT_PROVIDERS.find((p) => p.id === providerId)
|
|
43
|
-
return provider?.models.filter((m) => m.status === 'active') || []
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
function writeConfigFile(providerId: string, modelId: string, apiKey: string): void {
|
|
47
41
|
const configDir = join(homedir(), '.mipham')
|
|
48
42
|
mkdirSync(configDir, { recursive: true })
|
|
49
43
|
|
|
50
|
-
const
|
|
51
|
-
const models =
|
|
52
|
-
providerId === 'ollama' ? getOllamaModelListForConfig() : getActiveModels(providerId)
|
|
44
|
+
const models = providerId === 'ollama' ? getOllamaModelListForConfig() : []
|
|
53
45
|
|
|
54
46
|
function getOllamaModelListForConfig(): ModelInfo[] {
|
|
55
47
|
// 与 getOllamaModelList 逻辑一致,但用于配置写入
|
|
@@ -97,26 +89,10 @@ function writeConfigFile(providerId: string, modelId: string, apiKey: string): v
|
|
|
97
89
|
}
|
|
98
90
|
const storedKey = encryptApiKey(apiKey, getCredentialKey(configDir))
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
'version: 1',
|
|
105
|
-
`defaultProvider: ${providerId}`,
|
|
106
|
-
`defaultModel: ${modelId}`,
|
|
107
|
-
'permission: default',
|
|
108
|
-
'',
|
|
109
|
-
'providers:',
|
|
110
|
-
` - id: ${providerId}`,
|
|
111
|
-
` name: ${provider?.name || providerId}`,
|
|
112
|
-
` protocol: ${provider?.protocol || 'openai-compatible'}`,
|
|
113
|
-
...(provider?.baseUrl ? [` baseUrl: ${provider.baseUrl}`] : []),
|
|
114
|
-
` apiKey: ${storedKey}`,
|
|
115
|
-
` models:`,
|
|
116
|
-
...models.map((m) => ` - id: ${m.id}`),
|
|
117
|
-
]
|
|
118
|
-
|
|
119
|
-
atomicWriteFileSync(join(configDir, 'config.yml'), lines.join('\n'))
|
|
92
|
+
atomicWriteFileSync(
|
|
93
|
+
join(configDir, 'config.yml'),
|
|
94
|
+
buildConfigYaml(providerId, modelId, storedKey, models),
|
|
95
|
+
)
|
|
120
96
|
}
|
|
121
97
|
|
|
122
98
|
function checkOllama(): { installed: boolean; running: boolean; models: string[] } {
|
|
@@ -409,7 +385,9 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
409
385
|
{cursor === 0 ? '▶' : ' '} {t('ui.wizard.mode_cloud')}
|
|
410
386
|
</Text>
|
|
411
387
|
</Box>
|
|
412
|
-
<Text dimColor>
|
|
388
|
+
<Text dimColor>
|
|
389
|
+
{t('ui.wizard.mode_cloud_hint', { count: String(CLOUD_PROVIDERS.length) })}
|
|
390
|
+
</Text>
|
|
413
391
|
|
|
414
392
|
<Box marginTop={2} marginBottom={1}>
|
|
415
393
|
<Text color={cursor === 1 ? SELECTED_COLOR : undefined}>
|