@miphamai/cli 0.40.0 → 0.41.0
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/bin/mipham.ts +14 -43
- package/package.json +1 -1
- package/src/shared/arg-validation.ts +78 -0
- package/src/shared/package-info.ts +1 -1
package/bin/mipham.ts
CHANGED
|
@@ -1204,49 +1204,20 @@ npm: https://www.npmjs.com/package/@miphamai/cli`)
|
|
|
1204
1204
|
const handled = await runWorkflowCLI()
|
|
1205
1205
|
if (handled) return
|
|
1206
1206
|
|
|
1207
|
-
// ── Unknown command detection
|
|
1208
|
-
// After all known subcommands are checked, any remaining
|
|
1209
|
-
//
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
'
|
|
1214
|
-
'
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
'token',
|
|
1222
|
-
'help',
|
|
1223
|
-
]
|
|
1224
|
-
const firstArg = process.argv.slice(2).find((a) => !a.startsWith('-'))
|
|
1225
|
-
if (firstArg) {
|
|
1226
|
-
// Levenshtein distance to find closest match
|
|
1227
|
-
const distance = (a: string, b: string): number => {
|
|
1228
|
-
const m = a.length
|
|
1229
|
-
const n = b.length
|
|
1230
|
-
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0))
|
|
1231
|
-
for (let i = 0; i <= m; i++) dp[i]![0] = i
|
|
1232
|
-
for (let j = 0; j <= n; j++) dp[0]![j] = j
|
|
1233
|
-
for (let i = 1; i <= m; i++)
|
|
1234
|
-
for (let j = 1; j <= n; j++)
|
|
1235
|
-
dp[i]![j] =
|
|
1236
|
-
a[i - 1] === b[j - 1]
|
|
1237
|
-
? dp[i - 1]![j - 1]!
|
|
1238
|
-
: 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!)
|
|
1239
|
-
return dp[m]![n]!
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
const best = KNOWN_COMMANDS.map((cmd) => ({ cmd, dist: distance(firstArg, cmd) }))
|
|
1243
|
-
.filter((c) => c.dist <= 3)
|
|
1244
|
-
.sort((a, b) => a.dist - b.dist)
|
|
1245
|
-
const suggestions = best.map((c) => `'mipham ${c.cmd}'`).join(', ')
|
|
1246
|
-
|
|
1247
|
-
console.error(`Unknown command: mipham ${firstArg}`)
|
|
1248
|
-
if (suggestions) {
|
|
1249
|
-
console.error(`Did you mean: ${suggestions}?`)
|
|
1207
|
+
// ── Unknown command / option detection ─────────────────────────────────────
|
|
1208
|
+
// After all known subcommands are checked, any remaining argument is probably
|
|
1209
|
+
// a typo. Show error + suggestions instead of silently launching the CLI.
|
|
1210
|
+
const { detectUnknownArgument } = await import('../src/shared/arg-validation')
|
|
1211
|
+
const unknown = detectUnknownArgument(process.argv.slice(2))
|
|
1212
|
+
if (unknown) {
|
|
1213
|
+
const isOption = unknown.kind === 'option'
|
|
1214
|
+
const prefix = isOption ? '' : 'mipham '
|
|
1215
|
+
console.error(`Unknown ${unknown.kind}: ${prefix}${unknown.arg}`)
|
|
1216
|
+
if (unknown.suggestions.length > 0) {
|
|
1217
|
+
const rendered = unknown.suggestions
|
|
1218
|
+
.map((s) => (isOption ? `'${s}'` : `'mipham ${s}'`))
|
|
1219
|
+
.join(', ')
|
|
1220
|
+
console.error(`Did you mean: ${rendered}?`)
|
|
1250
1221
|
}
|
|
1251
1222
|
console.error(`Run 'mipham --help' for usage.`)
|
|
1252
1223
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument validation for the `mipham` entry point.
|
|
3
|
+
*
|
|
4
|
+
* After every known subcommand (`update`, `daemon`, `attach`, ...) has been
|
|
5
|
+
* dispatched, any remaining argument is a likely typo. This module classifies
|
|
6
|
+
* a leftover argument as an unknown command (positional) or unknown option
|
|
7
|
+
* (leading `-`/`--`) and suggests the closest known alternative via
|
|
8
|
+
* Levenshtein distance, instead of silently launching the interactive CLI.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface UnknownArgument {
|
|
12
|
+
kind: 'command' | 'option'
|
|
13
|
+
arg: string
|
|
14
|
+
suggestions: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const KNOWN_COMMANDS = [
|
|
18
|
+
'update',
|
|
19
|
+
'upgrade',
|
|
20
|
+
'plugin',
|
|
21
|
+
'workflow',
|
|
22
|
+
'daemon',
|
|
23
|
+
'attach',
|
|
24
|
+
'agents',
|
|
25
|
+
'agent',
|
|
26
|
+
'goal',
|
|
27
|
+
'schedule',
|
|
28
|
+
'token',
|
|
29
|
+
'help',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const KNOWN_FLAGS = ['--version', '-v', '-V', '--help', '-h', '--dump-config', '--safe-mode']
|
|
33
|
+
|
|
34
|
+
/** Levenshtein edit distance between two strings. */
|
|
35
|
+
function levenshtein(a: string, b: string): number {
|
|
36
|
+
const m = a.length
|
|
37
|
+
const n = b.length
|
|
38
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0))
|
|
39
|
+
for (let i = 0; i <= m; i++) dp[i]![0] = i
|
|
40
|
+
for (let j = 0; j <= n; j++) dp[0]![j] = j
|
|
41
|
+
for (let i = 1; i <= m; i++)
|
|
42
|
+
for (let j = 1; j <= n; j++)
|
|
43
|
+
dp[i]![j] =
|
|
44
|
+
a[i - 1] === b[j - 1]
|
|
45
|
+
? dp[i - 1]![j - 1]!
|
|
46
|
+
: 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!)
|
|
47
|
+
return dp[m]![n]!
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Closest candidates within `maxDist` edits, nearest first. */
|
|
51
|
+
function closest(target: string, candidates: string[], maxDist = 3): string[] {
|
|
52
|
+
return candidates
|
|
53
|
+
.map((c) => ({ c, dist: levenshtein(target, c) }))
|
|
54
|
+
.filter((x) => x.dist <= maxDist)
|
|
55
|
+
.sort((a, b) => a.dist - b.dist)
|
|
56
|
+
.map((x) => x.c)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return the first unknown argument (command or option), or null if all
|
|
61
|
+
* arguments are recognized. Positional commands are checked first so an
|
|
62
|
+
* unknown command keeps its existing message; unknown options (e.g.
|
|
63
|
+
* `--cersion`, a typo of `--version`) are reported when no positional
|
|
64
|
+
* command is present, instead of falling through to the interactive CLI.
|
|
65
|
+
*/
|
|
66
|
+
export function detectUnknownArgument(args: string[]): UnknownArgument | null {
|
|
67
|
+
const firstArg = args.find((a) => !a.startsWith('-'))
|
|
68
|
+
if (firstArg && !KNOWN_COMMANDS.includes(firstArg)) {
|
|
69
|
+
return { kind: 'command', arg: firstArg, suggestions: closest(firstArg, KNOWN_COMMANDS) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const unknownFlag = args.find((a) => a.startsWith('-') && !KNOWN_FLAGS.includes(a))
|
|
73
|
+
if (unknownFlag) {
|
|
74
|
+
return { kind: 'option', arg: unknownFlag, suggestions: closest(unknownFlag, KNOWN_FLAGS) }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.
|
|
12
|
+
export const PACKAGE_VERSION = '0.41.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|