@miphamai/cli 0.10.0 → 0.12.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 +1 -1
- package/src/agent/agent-context.ts +60 -1
- package/src/agent/agent-registry.ts +1 -0
- package/src/agent/types.ts +2 -0
- package/src/artifacts/server.ts +39 -0
- package/src/config/defaults.ts +10 -1
- package/src/config/loader.ts +44 -2
- package/src/core/engine.ts +94 -1
- package/src/core/hooks.ts +23 -0
- package/src/core/inference-hook.ts +224 -0
- package/src/core/metrics.ts +364 -0
- package/src/core/output-styles.ts +95 -0
- package/src/core/rules-loader.ts +175 -0
- package/src/index.tsx +9 -1
- package/src/mcp/client.ts +9 -0
- package/src/shared/types.ts +57 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/system/tool-search.ts +143 -0
- package/src/ui/commands.ts +161 -43
- package/dist/mipham +0 -0
package/src/shared/types.ts
CHANGED
|
@@ -196,6 +196,7 @@ export type HookEvent =
|
|
|
196
196
|
| 'ConfigChange'
|
|
197
197
|
| 'SubagentStart'
|
|
198
198
|
| 'SubagentStop'
|
|
199
|
+
| 'PreInference'
|
|
199
200
|
|
|
200
201
|
export type HookType = 'command' | 'http' | 'code' | 'mcp_tool'
|
|
201
202
|
|
|
@@ -226,6 +227,18 @@ export interface HookContext {
|
|
|
226
227
|
userPrompt?: string
|
|
227
228
|
configKey?: string
|
|
228
229
|
configValue?: unknown
|
|
230
|
+
/** PreInference: full conversation messages for DLP inspection. */
|
|
231
|
+
messages?: Array<{ role: string; content: string }>
|
|
232
|
+
/** PreInference: recent tool calls and their results. */
|
|
233
|
+
toolCalls?: Array<{
|
|
234
|
+
name: string
|
|
235
|
+
input: Record<string, unknown>
|
|
236
|
+
resultPreview: string
|
|
237
|
+
}>
|
|
238
|
+
/** PreInference: current provider ID. */
|
|
239
|
+
provider?: string
|
|
240
|
+
/** PreInference: current model ID. */
|
|
241
|
+
model?: string
|
|
229
242
|
}
|
|
230
243
|
|
|
231
244
|
export interface HookResult {
|
|
@@ -278,3 +291,47 @@ export interface PermissionRule {
|
|
|
278
291
|
level: PermissionLevel
|
|
279
292
|
pattern?: string
|
|
280
293
|
}
|
|
294
|
+
|
|
295
|
+
// ── Inference Hook (DLP) Types ──
|
|
296
|
+
|
|
297
|
+
/** Configuration for the PreInference DLP hook, loaded from config.yml. */
|
|
298
|
+
export interface InferenceHookConfig {
|
|
299
|
+
/** DLP server endpoint (HTTPS). Empty = feature disabled. */
|
|
300
|
+
endpoint: string
|
|
301
|
+
/** HMAC signing secret (format: mis_<random>). */
|
|
302
|
+
signing_secret: string
|
|
303
|
+
/** Request timeout in milliseconds. Default 5000. */
|
|
304
|
+
timeout: number
|
|
305
|
+
/** Failure posture: 'fail-closed' blocks on error, 'fail-open' allows. */
|
|
306
|
+
on_failure: 'fail-closed' | 'fail-open'
|
|
307
|
+
/** Organization identifier (optional, sent in payload). */
|
|
308
|
+
organization_id: string
|
|
309
|
+
/** Additional custom headers to send with each request. */
|
|
310
|
+
headers: Record<string, string>
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Outgoing request to the DLP server. */
|
|
314
|
+
export interface InferenceCheckRequest {
|
|
315
|
+
type: 'inference_check'
|
|
316
|
+
id: string
|
|
317
|
+
created_at: string
|
|
318
|
+
data: {
|
|
319
|
+
type: 'pre_inference'
|
|
320
|
+
session_id: string
|
|
321
|
+
organization_id?: string
|
|
322
|
+
provider: string
|
|
323
|
+
model: string
|
|
324
|
+
messages: Array<{ role: string; content: string }>
|
|
325
|
+
tool_calls: Array<{
|
|
326
|
+
name: string
|
|
327
|
+
input: Record<string, unknown>
|
|
328
|
+
result_preview: string
|
|
329
|
+
}>
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Response from the DLP server. */
|
|
334
|
+
export interface InferenceCheckResponse {
|
|
335
|
+
verdict: 'allow' | 'deny'
|
|
336
|
+
reason?: string
|
|
337
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { webFetchTool } from './network/web-fetch'
|
|
|
20
20
|
import { webSearchTool } from './network/web-search'
|
|
21
21
|
import { configTool } from './system/config'
|
|
22
22
|
import { mcpTool } from './system/mcp'
|
|
23
|
+
import { toolSearchTool } from './system/tool-search'
|
|
23
24
|
import { artifactTool } from './artifact/artifact'
|
|
24
25
|
import { reportFindingsTool } from './agent/report-findings'
|
|
25
26
|
import { sendMessageTool } from './agent/send-message'
|
|
@@ -134,6 +135,7 @@ export function createToolRegistry(): Map<string, ToolDefinition> {
|
|
|
134
135
|
// System tools
|
|
135
136
|
withValidation(configTool),
|
|
136
137
|
withValidation(mcpTool),
|
|
138
|
+
withValidation(toolSearchTool),
|
|
137
139
|
// Artifact tools
|
|
138
140
|
withValidation(artifactTool),
|
|
139
141
|
// Computer Use tools
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolSearch — search MCP server tools by name or description.
|
|
3
|
+
*
|
|
4
|
+
* Instead of loading all MCP tool definitions into the context upfront,
|
|
5
|
+
* the AI can call ToolSearch to discover relevant tools on demand.
|
|
6
|
+
* This saves ~85% of context tokens for large MCP servers (50-100 tools).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ToolDefinition } from '../../shared/index.ts'
|
|
10
|
+
import { McpClient } from '../../mcp/client'
|
|
11
|
+
|
|
12
|
+
export const toolSearchTool: ToolDefinition = {
|
|
13
|
+
name: 'ToolSearch',
|
|
14
|
+
description:
|
|
15
|
+
'Search available MCP server tools by name or description. ' +
|
|
16
|
+
'Use this to discover tools on demand instead of loading all tool definitions into context. ' +
|
|
17
|
+
'Returns matching tool names with their server and description.',
|
|
18
|
+
category: 'system',
|
|
19
|
+
permission: 'auto',
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
query: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description:
|
|
26
|
+
'Search query — matches against tool names and descriptions. Leave empty to list all available MCP tools.',
|
|
27
|
+
},
|
|
28
|
+
server: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Optional: limit search to a specific MCP server name.',
|
|
31
|
+
},
|
|
32
|
+
limit: {
|
|
33
|
+
type: 'integer',
|
|
34
|
+
description: 'Max results to return (default: 20).',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: [],
|
|
38
|
+
},
|
|
39
|
+
async execute(params, _ctx) {
|
|
40
|
+
const query = ((params.query as string) || '').toLowerCase()
|
|
41
|
+
const serverFilter = (params.server as string) || ''
|
|
42
|
+
const limit = (params.limit as number) || 20
|
|
43
|
+
|
|
44
|
+
const client = McpClient.getInstance()
|
|
45
|
+
const connectedServers = client.getConnectedServers()
|
|
46
|
+
|
|
47
|
+
if (connectedServers.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
content:
|
|
51
|
+
'── MCP Tool Search ──\n\n' +
|
|
52
|
+
'No MCP servers connected.\n\n' +
|
|
53
|
+
'Connect MCP servers in .mcp.json or via /mcp add <name> <url>.',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Filter by server if specified
|
|
58
|
+
const servers = serverFilter
|
|
59
|
+
? connectedServers.filter((s) => s.toLowerCase().includes(serverFilter))
|
|
60
|
+
: connectedServers
|
|
61
|
+
|
|
62
|
+
if (serverFilter && servers.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
content:
|
|
66
|
+
`── MCP Tool Search ──\n\n` +
|
|
67
|
+
`No connected server matching "${serverFilter}".\n\n` +
|
|
68
|
+
`Connected servers: ${connectedServers.join(', ')}`,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Collect matching tools
|
|
73
|
+
interface Match {
|
|
74
|
+
server: string
|
|
75
|
+
tool: string
|
|
76
|
+
description: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const matches: Match[] = []
|
|
80
|
+
|
|
81
|
+
for (const serverName of servers) {
|
|
82
|
+
try {
|
|
83
|
+
const tools = client.getTools(serverName)
|
|
84
|
+
for (const tool of tools) {
|
|
85
|
+
const nameMatch = !query || tool.name.toLowerCase().includes(query)
|
|
86
|
+
const descMatch = !query || (tool.description || '').toLowerCase().includes(query)
|
|
87
|
+
if (nameMatch || descMatch) {
|
|
88
|
+
matches.push({
|
|
89
|
+
server: serverName,
|
|
90
|
+
tool: tool.name,
|
|
91
|
+
description: (tool.description || '').slice(0, 120),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Server disconnected mid-search — skip
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (matches.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
content:
|
|
104
|
+
`── MCP Tool Search ──\n\n` +
|
|
105
|
+
`No tools matching "${query}" across ${servers.length} server(s).\n\n` +
|
|
106
|
+
`Try a different query, or omit the query to list all tools.`,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Truncate to limit
|
|
111
|
+
const limited = matches.slice(0, limit)
|
|
112
|
+
|
|
113
|
+
const lines: string[] = [
|
|
114
|
+
`── MCP Tool Search ──`,
|
|
115
|
+
'',
|
|
116
|
+
`${limited.length} of ${matches.length} matching tool(s) across ${servers.length} server(s):`,
|
|
117
|
+
'',
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
// Group by server
|
|
121
|
+
const byServer = new Map<string, Match[]>()
|
|
122
|
+
for (const m of limited) {
|
|
123
|
+
const list = byServer.get(m.server) || []
|
|
124
|
+
list.push(m)
|
|
125
|
+
byServer.set(m.server, list)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const [server, tools] of byServer) {
|
|
129
|
+
lines.push(` ${server} (${tools.length}):`)
|
|
130
|
+
for (const t of tools) {
|
|
131
|
+
const desc = t.description ? ` — ${t.description}` : ''
|
|
132
|
+
lines.push(` mcp__${server.replace(/[^a-z0-9-]/g, '_')}__${t.tool}${desc}`)
|
|
133
|
+
}
|
|
134
|
+
lines.push('')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (matches.length > limit) {
|
|
138
|
+
lines.push(`... and ${matches.length - limit} more. Use "query" to narrow results.`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { success: true, content: lines.join('\n') }
|
|
142
|
+
},
|
|
143
|
+
}
|
package/src/ui/commands.ts
CHANGED
|
@@ -3182,64 +3182,182 @@ Full changelog: https://mipham.ai/code/releases`,
|
|
|
3182
3182
|
// IDE — IDE integration guide
|
|
3183
3183
|
// ═══════════════════════════════════════════════════════════════
|
|
3184
3184
|
|
|
3185
|
-
const ideCmd: CommandHandler = () =>
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
VS Code:
|
|
3189
|
-
Install the Mipham Code extension from the VS Code marketplace.
|
|
3190
|
-
• Open Command Palette (Cmd+Shift+P)
|
|
3191
|
-
• Search "Mipham Code: Start"
|
|
3192
|
-
• The terminal panel opens with Mipham Code loaded
|
|
3185
|
+
const ideCmd: CommandHandler = async (_ctx) => {
|
|
3186
|
+
const { mkdirSync, writeFileSync, existsSync } = await import('node:fs')
|
|
3187
|
+
const { join } = await import('node:path')
|
|
3193
3188
|
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
"mipham": { "path": "bun", "args": ["run", "mipham"] }
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3189
|
+
const cwd = process.cwd()
|
|
3190
|
+
const vscodeDir = join(cwd, '.vscode')
|
|
3191
|
+
mkdirSync(vscodeDir, { recursive: true })
|
|
3200
3192
|
|
|
3201
|
-
|
|
3202
|
-
• Settings → Tools → Terminal → Shell path
|
|
3203
|
-
• Set to: bun run ~/path/to/mipham-code/apps/cli/bin/mipham
|
|
3193
|
+
const files: string[] = []
|
|
3204
3194
|
|
|
3205
|
-
|
|
3206
|
-
|
|
3195
|
+
// ── Detect bun path ──
|
|
3196
|
+
let bunPath = '/opt/homebrew/bin/bun'
|
|
3197
|
+
try {
|
|
3198
|
+
const { execSync } = await import('node:child_process')
|
|
3199
|
+
const detected = execSync('which bun 2>/dev/null || echo /opt/homebrew/bin/bun', {
|
|
3200
|
+
encoding: 'utf-8',
|
|
3201
|
+
}).trim()
|
|
3202
|
+
if (detected) bunPath = detected
|
|
3203
|
+
} catch {
|
|
3204
|
+
// Use default
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// ── settings.json: terminal profile ──
|
|
3208
|
+
const settingsPath = join(vscodeDir, 'settings.json')
|
|
3209
|
+
const settings = {
|
|
3210
|
+
'terminal.integrated.profiles.osx': {
|
|
3211
|
+
mipham: {
|
|
3212
|
+
path: bunPath,
|
|
3213
|
+
args: ['run', 'mipham'],
|
|
3214
|
+
cwd: '${workspaceFolder}',
|
|
3215
|
+
},
|
|
3216
|
+
},
|
|
3217
|
+
}
|
|
3218
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
3219
|
+
files.push('.vscode/settings.json')
|
|
3220
|
+
|
|
3221
|
+
// ── keybindings.json: Cmd+Esc launch ──
|
|
3222
|
+
const keybindingsPath = join(vscodeDir, 'keybindings.json')
|
|
3223
|
+
const keybindings = [
|
|
3224
|
+
{
|
|
3225
|
+
key: 'cmd+escape',
|
|
3226
|
+
command: 'workbench.action.terminal.focus',
|
|
3227
|
+
when: 'terminalProcessSupported',
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
key: 'cmd+shift+m',
|
|
3231
|
+
command: 'workbench.action.terminal.new',
|
|
3232
|
+
},
|
|
3233
|
+
]
|
|
3234
|
+
writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2) + '\n', 'utf-8')
|
|
3235
|
+
files.push('.vscode/keybindings.json')
|
|
3207
3236
|
|
|
3208
|
-
|
|
3237
|
+
// ── extensions.json: recommended ──
|
|
3238
|
+
const extensionsPath = join(vscodeDir, 'extensions.json')
|
|
3239
|
+
const extensions = {
|
|
3240
|
+
recommendations: ['miphamai.mipham-code'],
|
|
3241
|
+
}
|
|
3242
|
+
writeFileSync(extensionsPath, JSON.stringify(extensions, null, 2) + '\n', 'utf-8')
|
|
3243
|
+
files.push('.vscode/extensions.json')
|
|
3209
3244
|
|
|
3210
|
-
|
|
3211
|
-
|
|
3245
|
+
return {
|
|
3246
|
+
content: [
|
|
3247
|
+
'── VS Code Integration ──',
|
|
3248
|
+
'',
|
|
3249
|
+
`Generated in ${vscodeDir}:`,
|
|
3250
|
+
...files.map((f) => ` ✅ ${f}`),
|
|
3251
|
+
'',
|
|
3252
|
+
'What was configured:',
|
|
3253
|
+
' • Terminal profile "mipham" — opens Mipham Code in integrated terminal',
|
|
3254
|
+
' • Keyboard shortcut Cmd+Esc — focus terminal',
|
|
3255
|
+
' • Cmd+Shift+M — new terminal',
|
|
3256
|
+
'',
|
|
3257
|
+
'To use:',
|
|
3258
|
+
' 1. Restart VS Code (or reload window: Cmd+Shift+P → Reload Window)',
|
|
3259
|
+
' 2. Open terminal: Ctrl+` or Cmd+Esc',
|
|
3260
|
+
' 3. Select "mipham" profile from the terminal dropdown',
|
|
3261
|
+
'',
|
|
3262
|
+
'Install the VS Code extension for full integration:',
|
|
3263
|
+
` code --install-extension miphamai.mipham-code`,
|
|
3264
|
+
'',
|
|
3265
|
+
'JetBrains: Settings → Tools → Terminal → Shell path → bun run mipham',
|
|
3266
|
+
].join('\n'),
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3212
3269
|
|
|
3213
3270
|
// ═══════════════════════════════════════════════════════════════
|
|
3214
3271
|
// Terminal Setup — shell integration guide
|
|
3215
3272
|
// ═══════════════════════════════════════════════════════════════
|
|
3216
3273
|
|
|
3217
|
-
const terminalSetupCmd: CommandHandler = () =>
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
mipham
|
|
3223
|
-
|
|
3224
|
-
One-liner install:
|
|
3225
|
-
curl -fsSL https://mipham.ai/install.sh | bash
|
|
3274
|
+
const terminalSetupCmd: CommandHandler = async () => {
|
|
3275
|
+
const { writeFileSync, appendFileSync, existsSync, mkdirSync, readFileSync } =
|
|
3276
|
+
await import('node:fs')
|
|
3277
|
+
const { join } = await import('node:path')
|
|
3278
|
+
const { homedir } = await import('node:os')
|
|
3226
3279
|
|
|
3227
|
-
|
|
3228
|
-
|
|
3280
|
+
const home = homedir()
|
|
3281
|
+
const lines: string[] = ['── Terminal Setup ──', '']
|
|
3282
|
+
|
|
3283
|
+
// ── 1. Generate standalone shell setup script ──
|
|
3284
|
+
const miphamDir = join(home, '.mipham')
|
|
3285
|
+
mkdirSync(miphamDir, { recursive: true })
|
|
3286
|
+
|
|
3287
|
+
const shellScript = join(miphamDir, 'shell-setup.sh')
|
|
3288
|
+
const shellContent = [
|
|
3289
|
+
'#!/bin/bash',
|
|
3290
|
+
'# Mipham Code — Shell Integration',
|
|
3291
|
+
'# Source this file in your shell profile:',
|
|
3292
|
+
'# source ~/.mipham/shell-setup.sh',
|
|
3293
|
+
'',
|
|
3294
|
+
`export MIPHAM_HOME="${home}/.mipham"`,
|
|
3295
|
+
'',
|
|
3296
|
+
'# Alias: launch Mipham Code in current directory',
|
|
3297
|
+
`alias mipham='cd $(pwd) && ${NPM_INSTALL_COMMAND} > /dev/null 2>&1; mipham'`,
|
|
3298
|
+
'',
|
|
3299
|
+
'# Or use the local development version:',
|
|
3300
|
+
'# alias mipham="bun run /path/to/mipham-code/apps/cli/bin/mipham"',
|
|
3301
|
+
'',
|
|
3302
|
+
'# Auto-detect provider from config',
|
|
3303
|
+
'if [ -f ~/.mipham/config.yml ]; then',
|
|
3304
|
+
' export MIPHAM_PROVIDER=$(grep "defaultProvider:" ~/.mipham/config.yml | awk "{print \$2}")',
|
|
3305
|
+
'fi',
|
|
3306
|
+
].join('\n')
|
|
3307
|
+
writeFileSync(shellScript, shellContent + '\n', 'utf-8')
|
|
3308
|
+
lines.push(` ✅ Generated: ${shellScript}`)
|
|
3309
|
+
|
|
3310
|
+
// ── 2. Append to shell profile ──
|
|
3311
|
+
const shell = process.env.SHELL || '/bin/zsh'
|
|
3312
|
+
const profileName = shell.includes('zsh') ? '.zshrc' : '.bashrc'
|
|
3313
|
+
const profilePath = join(home, profileName)
|
|
3314
|
+
const sourceLine = `\n# Mipham Code shell integration\n[ -f ~/.mipham/shell-setup.sh ] && source ~/.mipham/shell-setup.sh\n`
|
|
3229
3315
|
|
|
3230
|
-
|
|
3231
|
-
|
|
3316
|
+
try {
|
|
3317
|
+
const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf-8') : ''
|
|
3318
|
+
if (existing.includes('shell-setup.sh')) {
|
|
3319
|
+
lines.push(` ⏭ ${profileName} already has Mipham Code integration`)
|
|
3320
|
+
} else {
|
|
3321
|
+
appendFileSync(profilePath, sourceLine, 'utf-8')
|
|
3322
|
+
lines.push(` ✅ Added to ~/${profileName}`)
|
|
3323
|
+
}
|
|
3324
|
+
} catch {
|
|
3325
|
+
lines.push(` ⚠️ Could not update ~/${profileName}. Add manually:`)
|
|
3326
|
+
lines.push(` echo '${sourceLine.trim()}' >> ~/${profileName}`)
|
|
3327
|
+
}
|
|
3232
3328
|
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3329
|
+
// ── 3. Global install check ──
|
|
3330
|
+
lines.push('')
|
|
3331
|
+
lines.push('── Installation ──')
|
|
3332
|
+
try {
|
|
3333
|
+
const { execSync } = await import('node:child_process')
|
|
3334
|
+
const miphamPath = execSync('which mipham 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim()
|
|
3335
|
+
if (miphamPath) {
|
|
3336
|
+
lines.push(` ✅ mipham found at: ${miphamPath}`)
|
|
3337
|
+
const version = execSync('mipham --version 2>/dev/null || echo "unknown"', {
|
|
3338
|
+
encoding: 'utf-8',
|
|
3339
|
+
}).trim()
|
|
3340
|
+
lines.push(` 📦 Version: ${version}`)
|
|
3341
|
+
} else {
|
|
3342
|
+
lines.push(` ⚠️ mipham not in PATH. Install globally:`)
|
|
3343
|
+
lines.push(` ${NPM_INSTALL_COMMAND}`)
|
|
3344
|
+
lines.push(` or: curl -fsSL https://mipham.ai/install.sh | bash`)
|
|
3345
|
+
}
|
|
3346
|
+
} catch {
|
|
3347
|
+
lines.push(` 💡 Install: ${NPM_INSTALL_COMMAND}`)
|
|
3348
|
+
}
|
|
3236
3349
|
|
|
3237
|
-
Verify
|
|
3238
|
-
|
|
3239
|
-
|
|
3350
|
+
// ── 4. Verify ──
|
|
3351
|
+
lines.push('')
|
|
3352
|
+
lines.push('── Next Steps ──')
|
|
3353
|
+
lines.push(' 1. Restart your terminal or run: source ~/.mipham/shell-setup.sh')
|
|
3354
|
+
lines.push(' 2. Run: mipham --version')
|
|
3355
|
+
lines.push(' 3. Start coding: cd your-project && mipham')
|
|
3356
|
+
lines.push('')
|
|
3357
|
+
lines.push(`Works with: Zsh, Bash. Shell: ${shell}`)
|
|
3240
3358
|
|
|
3241
|
-
|
|
3242
|
-
}
|
|
3359
|
+
return { content: lines.join('\n') }
|
|
3360
|
+
}
|
|
3243
3361
|
|
|
3244
3362
|
// ═══════════════════════════════════════════════════════════════
|
|
3245
3363
|
// Phase 4 — MCP Server Management
|
package/dist/mipham
DELETED
|
Binary file
|