@miphamai/cli 0.80.1 → 0.81.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miphamai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@mipham/shared": "workspace:*",
|
|
54
|
-
"@types/bun": "^1.
|
|
54
|
+
"@types/bun": "^1.4.2",
|
|
55
55
|
"@types/node": "^22.19.19",
|
|
56
56
|
"@types/react": "^19.3.0",
|
|
57
57
|
"ink-testing-library": "^4.0.0",
|
|
58
|
-
"vitest": "^
|
|
58
|
+
"vitest": "^5.0.0"
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -63,6 +63,56 @@ const WRITER_COMMANDS = new Set([
|
|
|
63
63
|
'truncate',
|
|
64
64
|
])
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Commands that wrap another command as their payload. A Read/Edit deny rule
|
|
68
|
+
* must still apply when the reader/writer is wrapped (`sudo cat X`, `env -C / X`,
|
|
69
|
+
* `timeout 5 X`) — otherwise the wrapper silently bypasses the rule.
|
|
70
|
+
*/
|
|
71
|
+
const PREFIX_COMMANDS = new Set([
|
|
72
|
+
'sudo',
|
|
73
|
+
'doas',
|
|
74
|
+
'nohup',
|
|
75
|
+
'command',
|
|
76
|
+
'exec',
|
|
77
|
+
'nice',
|
|
78
|
+
'timeout',
|
|
79
|
+
'env',
|
|
80
|
+
'xargs',
|
|
81
|
+
'eval',
|
|
82
|
+
'stdbuf',
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
/** Value-taking options of wrapper commands (consume the following token). */
|
|
86
|
+
const PREFIX_VALUE_OPTIONS = new Set([
|
|
87
|
+
'-u',
|
|
88
|
+
'--user',
|
|
89
|
+
'-g',
|
|
90
|
+
'--group',
|
|
91
|
+
'-h',
|
|
92
|
+
'--host',
|
|
93
|
+
'-p',
|
|
94
|
+
'--prompt', // sudo/doas
|
|
95
|
+
'-n',
|
|
96
|
+
'--adjustment', // nice (and xargs --max-args)
|
|
97
|
+
'-k',
|
|
98
|
+
'--kill-after',
|
|
99
|
+
'-s',
|
|
100
|
+
'--signal', // timeout
|
|
101
|
+
'-C',
|
|
102
|
+
'--chdir',
|
|
103
|
+
'--unset',
|
|
104
|
+
'-S',
|
|
105
|
+
'--split-string', // env
|
|
106
|
+
'-a',
|
|
107
|
+
'--arg-file',
|
|
108
|
+
'-E',
|
|
109
|
+
'--eof',
|
|
110
|
+
'-I',
|
|
111
|
+
'--replace',
|
|
112
|
+
'-P',
|
|
113
|
+
'--max-procs', // xargs
|
|
114
|
+
])
|
|
115
|
+
|
|
66
116
|
/**
|
|
67
117
|
* Split a (possibly compound) shell command into simple-command segments, so a
|
|
68
118
|
* Bash(pattern) rule matches any segment rather than only the whole string
|
|
@@ -156,6 +206,8 @@ function flattenCommand(command: string): string[] {
|
|
|
156
206
|
const out: string[] = []
|
|
157
207
|
for (const seg of splitShellSegments(command)) {
|
|
158
208
|
out.push(seg)
|
|
209
|
+
const stripped = stripPrefixCommand(seg)
|
|
210
|
+
if (stripped !== seg) out.push(stripped)
|
|
159
211
|
for (const inner of extractSubstitutions(seg)) {
|
|
160
212
|
out.push(...flattenCommand(inner))
|
|
161
213
|
}
|
|
@@ -163,6 +215,46 @@ function flattenCommand(command: string): string[] {
|
|
|
163
215
|
return out
|
|
164
216
|
}
|
|
165
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Resolve the effective command and its arguments after any wrapper prefix
|
|
220
|
+
* commands (`sudo`, `env`, `timeout`, …). Skips the wrapper and its own options
|
|
221
|
+
* (flags, the values of value-taking flags, `env`'s `VAR=value` assignments, and
|
|
222
|
+
* `timeout`'s positional duration) to reach the real command. Conservative, not a
|
|
223
|
+
* full parser: only recognized wrappers are stripped, so an unrecognized token is
|
|
224
|
+
* always treated as the command (never skipped) — which over-matches, the safe
|
|
225
|
+
* direction for a deny rule.
|
|
226
|
+
*/
|
|
227
|
+
function effectiveCommand(tokens: string[]): { base: string; args: string[] } {
|
|
228
|
+
let i = 0
|
|
229
|
+
while (i < tokens.length) {
|
|
230
|
+
const name = (tokens[i] || '').split('/').pop() || ''
|
|
231
|
+
if (!PREFIX_COMMANDS.has(name)) break
|
|
232
|
+
i++ // skip the wrapper
|
|
233
|
+
while (i < tokens.length && tokens[i]!.startsWith('-')) {
|
|
234
|
+
const opt = tokens[i]!
|
|
235
|
+
i++
|
|
236
|
+
if (PREFIX_VALUE_OPTIONS.has(opt) && i < tokens.length) i++ // skip the flag's value
|
|
237
|
+
}
|
|
238
|
+
if (name === 'env') {
|
|
239
|
+
while (i < tokens.length && tokens[i]!.includes('=')) i++ // `VAR=value` assignments
|
|
240
|
+
}
|
|
241
|
+
if (name === 'timeout') i++ // positional duration
|
|
242
|
+
}
|
|
243
|
+
if (i >= tokens.length) return { base: '', args: [] }
|
|
244
|
+
return { base: (tokens[i] || '').split('/').pop() || '', args: tokens.slice(i + 1) }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** A command with any wrapper prefix commands stripped, so `sudo rm -rf /`
|
|
248
|
+
* matches a `Bash(rm *)` rule. Returns the input unchanged when there is no
|
|
249
|
+
* wrapper (or nothing but wrappers). */
|
|
250
|
+
function stripPrefixCommand(command: string): string {
|
|
251
|
+
const tokens = command.split(/\s+/).filter(Boolean)
|
|
252
|
+
if (tokens.length === 0) return command
|
|
253
|
+
const { base, args } = effectiveCommand(tokens)
|
|
254
|
+
if (!base) return command
|
|
255
|
+
return [base, ...args].join(' ')
|
|
256
|
+
}
|
|
257
|
+
|
|
166
258
|
/**
|
|
167
259
|
* Detect reader/writer commands at the front of each shell segment and recurse
|
|
168
260
|
* into command substitutions, so `echo $(cat .git-credentials)` is caught.
|
|
@@ -171,8 +263,7 @@ function scanReaderWriterCommands(command: string, read: string[], write: string
|
|
|
171
263
|
for (const seg of splitShellSegments(command)) {
|
|
172
264
|
const tokens = seg.split(/\s+/).filter(Boolean)
|
|
173
265
|
if (tokens.length > 0) {
|
|
174
|
-
const base = (tokens
|
|
175
|
-
const args = tokens.slice(1)
|
|
266
|
+
const { base, args } = effectiveCommand(tokens)
|
|
176
267
|
if (READER_COMMANDS.has(base)) {
|
|
177
268
|
// `sed -i` / `perl -i` read AND write their file args.
|
|
178
269
|
const inPlace = args.some((a) => a === '-i' || a.startsWith('--in-place'))
|
|
@@ -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.81.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
|
@@ -23,7 +23,21 @@ function detectParallelism(): number {
|
|
|
23
23
|
return os.cpus().length || 1
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the concurrency cap for parallel() fan-out. Defaults to CPU-derived
|
|
28
|
+
* parallelism capped at 16. An explicit `MIPHAM_WORKFLOW_MAX_CONCURRENT_AGENTS`
|
|
29
|
+
* (1–256) overrides it, for inference-bound fan-outs whose bottleneck is LLM
|
|
30
|
+
* latency rather than CPU. Invalid / out-of-range values fall back to the default.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveMaxConcurrent(envValue: string | undefined): number {
|
|
33
|
+
if (envValue !== undefined && envValue !== '') {
|
|
34
|
+
const n = Number(envValue)
|
|
35
|
+
if (Number.isInteger(n) && n >= 1) return Math.min(n, 256)
|
|
36
|
+
}
|
|
37
|
+
return Math.max(1, Math.min(16, detectParallelism()))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MAX_CONCURRENT = resolveMaxConcurrent(process.env.MIPHAM_WORKFLOW_MAX_CONCURRENT_AGENTS)
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
43
|
* Simple async semaphore for concurrency limiting.
|