@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.
@@ -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
+ }
@@ -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
+ }
@@ -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
- content: `── IDE Integration ──
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
- Or manually: add to .vscode/settings.json
3195
- {
3196
- "terminal.integrated.profiles.osx": {
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
- JetBrains (IntelliJ / WebStorm / PyCharm):
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
- Terminal (any):
3206
- alias mipham='cd your-project && bun run path/to/mipham'
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
- Or install globally: ${NPM_INSTALL_COMMAND}
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
- Coming soon: dedicated VS Code & JetBrains plugin extensions.`,
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
- content: `── Terminal Setup ──
3219
-
3220
- Install globally:
3221
- ${NPM_INSTALL_COMMAND}
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
- Add to shell profile (~/.zshrc or ~/.bashrc):
3228
- alias mipham='bun run ~/path/to/mipham-code/apps/cli/bin/mipham'
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
- # Or with a specific provider/model:
3231
- alias mipham='mipham --provider anthropic --model claude-opus-4-8'
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
- Upgrade:
3234
- curl -fsSL https://mipham.ai/install.sh | bash
3235
- # or: ${NPM_UPDATE_COMMAND}
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 installation:
3238
- mipham --version
3239
- mipham --help
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
- Works with: Bash, Zsh, Fish, PowerShell, Windows Terminal`,
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