@miphamai/cli 0.16.0 → 0.16.2

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.
@@ -11,6 +11,18 @@ import type { PluginManager } from '../plugin/plugin-manager'
11
11
  import { McpClient } from '../mcp/client'
12
12
  import { NPM_INSTALL_COMMAND, NPM_UPDATE_COMMAND, PACKAGE_VERSION } from '../shared/index.ts'
13
13
  import { getPreference } from '../config/preferences'
14
+ import { stripIndent } from './strip-indent.js'
15
+ import {
16
+ initCmd,
17
+ setupCmd,
18
+ recommendCmd,
19
+ permissionsCmd,
20
+ addDirCmd,
21
+ securityCmd,
22
+ promptAuditCmd,
23
+ } from '../commands/project.js'
24
+ import { themeCmd, releaseNotesCmd, ideCmd, terminalSetupCmd } from '../commands/environment.js'
25
+ import { commitCmd, pushCmd, prCmd, issueCmd } from '../commands/git.js'
14
26
 
15
27
  export interface CommandContext {
16
28
  engine: QueryEngine
@@ -25,6 +37,7 @@ export interface CommandContext {
25
37
  setEffort: (level: string) => void
26
38
  setFocusMode: (on: boolean) => void
27
39
  setGoal: (text: string) => void
40
+ setUltracodeMode: (on: boolean) => void
28
41
  skillsLoader?: SkillsLoader
29
42
  pluginManager?: PluginManager
30
43
  }
@@ -47,7 +60,7 @@ export interface CommandResult {
47
60
  forwardToAI?: string
48
61
  }
49
62
 
50
- type CommandHandler = (
63
+ export type CommandHandler = (
51
64
  ctx: CommandContext,
52
65
  args: string[],
53
66
  ) => CommandResult | Promise<CommandResult>
@@ -475,90 +488,6 @@ const todosCmd: CommandHandler = (_ctx, args) => {
475
488
  // Project
476
489
  // ═══════════════════════════════════════════════════════════════
477
490
 
478
- const initCmd: CommandHandler = async (ctx) => {
479
- const { existsSync, mkdirSync, writeFileSync } = await import('node:fs')
480
- const { join } = await import('node:path')
481
- const { homedir } = await import('node:os')
482
-
483
- const home = homedir()
484
- const userConfigPath = join(home, '.mipham', 'config.yml')
485
-
486
- // Generate user-friendly config if it doesn't exist yet
487
- if (!existsSync(userConfigPath)) {
488
- mkdirSync(join(home, '.mipham'), { recursive: true })
489
-
490
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
491
- const providerYaml = activeProviders
492
- .map((p) => {
493
- const tips: Record<string, string> = {
494
- anthropic: '# Get key: https://console.anthropic.com/',
495
- openai: '# Get key: https://platform.openai.com/api-keys',
496
- deepseek: '# Get key: https://platform.deepseek.com/api_keys',
497
- kimi: '# Get key: https://platform.moonshot.cn/',
498
- doubao: '# Get key: https://console.volcengine.com/ark',
499
- hunyuan: '# Get key: https://console.cloud.tencent.com/hunyuan',
500
- qwen: '# Get key: https://dashscope.console.aliyun.com/apiKey',
501
- google: '# Get key: https://aistudio.google.com/apikey',
502
- }
503
- const comment = tips[p.id] || ''
504
- const baseUrlLine = p.baseUrl ? `\n baseUrl: "${p.baseUrl}"` : ''
505
- return ` ${comment}
506
- - id: ${p.id}
507
- name: "${p.name}"${baseUrlLine}
508
- apiKey: "\${${p.id.toUpperCase()}_API_KEY}"`
509
- })
510
- .join('\n\n')
511
-
512
- const configContent = `# Mipham Code — User Configuration
513
- # Location: ~/.mipham/config.yml
514
- # Docs: https://mipham.ai/code/docs/config
515
- #
516
- # ═══ Quick Start ═══
517
- # 1. Replace the API key placeholders below with your real keys
518
- # 2. Save the file
519
- # 3. Run 'mipham' — it auto-detects configured providers
520
- #
521
- # ═══ Environment Variables (Alternative) ═══
522
- # Instead of editing this file, you can set env vars:
523
- # export ANTHROPIC_API_KEY="sk-ant-..."
524
- # export OPENAI_API_KEY="sk-..."
525
- # (The \${VAR} syntax below reads from environment variables)
526
-
527
- # ── Defaults ──
528
- defaultProvider: ${ctx.providerId}
529
- defaultModel: ${ctx.modelId}
530
- permission: ask
531
-
532
- # ── Providers (${activeProviders.length} pre-configured — just add your API keys) ──
533
- providers:
534
- ${providerYaml}
535
- `
536
-
537
- writeFileSync(userConfigPath, configContent, 'utf-8')
538
- return {
539
- content: `✅ Mipham Code initialized!
540
-
541
- Created: ~/.mipham/config.yml (${activeProviders.length} providers pre-configured)
542
-
543
- Next steps:
544
- 1. Edit ~/.mipham/config.yml — replace API key placeholders with your real keys
545
- 2. Run mipham to start
546
-
547
- Providers configured:
548
- ${activeProviders.map((p) => ` • ${p.name} — ${p.id.toUpperCase()}_API_KEY`).join('\n')}
549
-
550
- Tip: /setup for the full 6-step wizard.`,
551
- }
552
- }
553
-
554
- // Config already exists — show status
555
- return {
556
- content: `~/.mipham/config.yml already exists.
557
-
558
- Run /setup for the full wizard, or /config to view current settings.`,
559
- }
560
- }
561
-
562
491
  // ═══════════════════════════════════════════════════════════════
563
492
  // Phase 1 — New Session Commands
564
493
  // ═══════════════════════════════════════════════════════════════
@@ -1165,6 +1094,52 @@ const focusCmd: CommandHandler = (ctx) => {
1165
1094
  }
1166
1095
  }
1167
1096
 
1097
+ // ═══════════════════════════════════════════════════════════════
1098
+ // Ultracode Mode — toggle multi-agent workflow orchestration
1099
+ // ═══════════════════════════════════════════════════════════════
1100
+
1101
+ const ultracodeCmd: CommandHandler = (ctx, args) => {
1102
+ const arg = args[0]?.toLowerCase()
1103
+ if (arg === 'on') {
1104
+ ctx.setUltracodeMode(true)
1105
+ return {
1106
+ content:
1107
+ '✓ Ultracode mode ON — multi-agent workflow orchestration enabled.\n\nThe AI will use Workflow tool to fan out, verify, and synthesize for all substantive tasks.',
1108
+ forwardToAI: stripIndent`
1109
+ Ultracode mode is now ACTIVE. For every substantive task:
1110
+ - Use the Workflow tool to orchestrate multi-agent execution
1111
+ - Default to pipeline() for multi-stage work — stages flow independently
1112
+ - Fan out independent work across parallel agents
1113
+ - Apply adversarial verification (verify with skeptics) to findings before accepting them
1114
+ - Synthesize results with a top-tier model pass
1115
+ - Prefer pipeline() over parallel() barriers — don't block stages unnecessarily
1116
+ - Use loopUntilConvergence when discovering unknown-size sets (bugs, issues, edge cases)
1117
+ - EDGE LOGIC IS FREE: flatten, dedupe, filter in plain JavaScript — NOT agent calls
1118
+ Keep responses concise — the workflow graph handles the depth.`,
1119
+ }
1120
+ } else if (arg === 'off') {
1121
+ ctx.setUltracodeMode(false)
1122
+ return {
1123
+ content: '✓ Ultracode mode OFF.',
1124
+ forwardToAI:
1125
+ 'Ultracode mode is now OFF. Revert to standard single-agent execution. Do NOT use Workflow tool unless explicitly asked.',
1126
+ }
1127
+ }
1128
+ return {
1129
+ content: stripIndent`
1130
+ Usage: /ultracode [on|off]
1131
+
1132
+ Ultracode mode enables multi-agent workflow orchestration for every substantive task.
1133
+ When ON, the AI uses the Workflow tool to decompose tasks, fan out parallel agents,
1134
+ verify findings adversarially, and synthesize results with a top-tier model.
1135
+
1136
+ Examples:
1137
+ /ultracode on — enable multi-agent mode
1138
+ /ultracode off — return to standard single-agent mode
1139
+ `,
1140
+ }
1141
+ }
1142
+
1168
1143
  // ═══════════════════════════════════════════════════════════════
1169
1144
  // Phase 1 — Workflow Commands
1170
1145
  // ═══════════════════════════════════════════════════════════════
@@ -1541,145 +1516,6 @@ const doctorCmd: CommandHandler = async (ctx) => {
1541
1516
  // GitHub & Git Workflow Commands (Claude Code parity)
1542
1517
  // ═══════════════════════════════════════════════════════════════
1543
1518
 
1544
- const commitCmd: CommandHandler = async () => {
1545
- try {
1546
- const { execSync } = await import('node:child_process')
1547
- const diff = execSync('git diff --cached --stat', { encoding: 'utf-8', timeout: 5000 }).trim()
1548
- const unstaged = execSync('git diff --name-only', { encoding: 'utf-8', timeout: 5000 }).trim()
1549
-
1550
- if (!diff) {
1551
- let msg = '── Git Commit ──\n\nNo staged changes found.\n\n'
1552
- if (unstaged) {
1553
- msg +=
1554
- 'Unstaged changes exist:\n' +
1555
- unstaged
1556
- .split('\n')
1557
- .map((f) => ' • ' + f)
1558
- .join('\n') +
1559
- '\n\n'
1560
- }
1561
- msg +=
1562
- 'Run: git add <files> to stage changes first.\nOr type: "commit these changes" for AI assistance.'
1563
- return { content: msg }
1564
- }
1565
-
1566
- return {
1567
- content: [
1568
- '── Git Commit ──',
1569
- '',
1570
- 'Staged changes:',
1571
- diff,
1572
- '',
1573
- 'To generate a commit message, type: "write a commit message for these changes"',
1574
- 'To commit: git commit -m "your message"',
1575
- '',
1576
- 'Tip: Use Conventional Commits format (feat:, fix:, chore:, docs:)',
1577
- ].join('\n'),
1578
- }
1579
- } catch {
1580
- return { content: '── Git Commit ──\n\nCould not run git. Are you in a git repository?' }
1581
- }
1582
- }
1583
-
1584
- const pushCmd: CommandHandler = async () => {
1585
- try {
1586
- const { execSync } = await import('node:child_process')
1587
- const branch = execSync('git branch --show-current', {
1588
- encoding: 'utf-8',
1589
- timeout: 3000,
1590
- }).trim()
1591
- const ahead = execSync(`git rev-list --count origin/${branch}..HEAD 2>/dev/null || echo 0`, {
1592
- encoding: 'utf-8',
1593
- timeout: 3000,
1594
- }).trim()
1595
-
1596
- return {
1597
- content: [
1598
- '── Git Push ──',
1599
- '',
1600
- `Branch: ${branch || '(unknown)'}`,
1601
- `Commits ahead of remote: ${ahead}`,
1602
- '',
1603
- ahead !== '0'
1604
- ? `Ready to push ${ahead} commit(s). Run: git push origin ${branch}`
1605
- : 'Nothing to push (up to date).',
1606
- '',
1607
- 'Or type: "push my changes" for AI assistance.',
1608
- ].join('\n'),
1609
- }
1610
- } catch {
1611
- return { content: '── Git Push ──\n\nCould not determine git status.' }
1612
- }
1613
- }
1614
-
1615
- const prCmd: CommandHandler = async () => {
1616
- try {
1617
- const { execSync } = await import('node:child_process')
1618
- const branch = execSync('git branch --show-current', {
1619
- encoding: 'utf-8',
1620
- timeout: 3000,
1621
- }).trim()
1622
- const mainBranch =
1623
- execSync('git remote show origin 2>/dev/null | grep "HEAD branch" | cut -d: -f2', {
1624
- encoding: 'utf-8',
1625
- timeout: 3000,
1626
- }).trim() || 'main'
1627
-
1628
- const diff = execSync(
1629
- `git diff --stat origin/${mainBranch}...HEAD 2>/dev/null || git diff --stat ${mainBranch}...HEAD 2>/dev/null || echo ""`,
1630
- { encoding: 'utf-8', timeout: 5000 },
1631
- ).trim()
1632
-
1633
- const commits = execSync(
1634
- `git log --oneline origin/${mainBranch}..HEAD 2>/dev/null || git log --oneline ${mainBranch}..HEAD 2>/dev/null || echo ""`,
1635
- { encoding: 'utf-8', timeout: 5000 },
1636
- ).trim()
1637
-
1638
- return {
1639
- content: [
1640
- '── Create Pull Request ──',
1641
- '',
1642
- `Branch: ${branch} → ${mainBranch}`,
1643
- '',
1644
- commits
1645
- ? `Commits:\n${commits
1646
- .split('\n')
1647
- .map((c) => ' ' + c)
1648
- .join('\n')}`
1649
- : 'No commits ahead.',
1650
- '',
1651
- diff ? `Changes:\n${diff}` : '',
1652
- '',
1653
- 'To create PR: type "create a pull request" for AI assistance.',
1654
- 'Or use: gh pr create --base ' + mainBranch + ' --head ' + branch,
1655
- ].join('\n'),
1656
- }
1657
- } catch {
1658
- return { content: '── Create Pull Request ──\n\nCould not determine PR context.' }
1659
- }
1660
- }
1661
-
1662
- const issueCmd: CommandHandler = async () => {
1663
- return {
1664
- content: [
1665
- '── GitHub Issues ──',
1666
- '',
1667
- 'To file an issue, type: "create a GitHub issue for [description]"',
1668
- '',
1669
- 'Quick commands:',
1670
- ' gh issue create --title "..." --body "..."',
1671
- ' gh issue list',
1672
- '',
1673
- 'Use /github-ops skill for advanced GitHub automation.',
1674
- ].join('\n'),
1675
- }
1676
- }
1677
-
1678
- // ═══════════════════════════════════════════════════════════════
1679
- // Code Quality — bridge slash commands to skills (Claude Code parity)
1680
- // ═══════════════════════════════════════════════════════════════
1681
-
1682
- /** Factory for git-diff-based commands that bridge to AI skills via forwardToAI. */
1683
1519
  function gitDiffBridgeCmd(opts: {
1684
1520
  label: string
1685
1521
  noChangesHint: string
@@ -2500,1285 +2336,176 @@ const workflowAutoCmd: CommandHandler = async (_ctx, args) => {
2500
2336
  }
2501
2337
 
2502
2338
  // ═══════════════════════════════════════════════════════════════
2503
- // Permissions
2339
+ // /deep-research <topic> — multi-agent deep research pipeline
2504
2340
  // ═══════════════════════════════════════════════════════════════
2505
2341
 
2506
- const permissionsCmd: CommandHandler = (ctx) => {
2507
- const c = ctx.engine.getContext()
2508
- const msgs = c.getMessages()
2342
+ const deepResearchCmd: CommandHandler = (_ctx, args) => {
2343
+ const topic = args.join(' ').trim()
2344
+ if (!topic) {
2345
+ return {
2346
+ content: stripIndent`
2347
+ Usage: /deep-research <topic>
2509
2348
 
2349
+ Conducts multi-agent deep research — 5-angle parallel search, adversarial verification,
2350
+ and top-tier synthesis into a well-cited report.
2351
+
2352
+ Example:
2353
+ /deep-research impact of quantum computing on cryptography
2354
+ /deep-research latest advances in CRISPR gene editing
2355
+ `,
2356
+ }
2357
+ }
2510
2358
  return {
2511
- content: `─ Permission Settings ─
2359
+ content: stripIndent`
2360
+ ── Deep Research ──
2361
+
2362
+ Topic: "${topic}"
2512
2363
 
2513
- Mode: ${ctx.config.permission}
2514
- Messages: ${msgs.length} in context
2515
- Tools: ${ctx.engine.getTools().size} available
2364
+ Orchestrating multi-agent research pipeline:
2365
+ 1. Scope — 5 research angles
2366
+ 2. Research — parallel web searches
2367
+ 3. Reduce — deduplicate + merge
2368
+ 4. Verify — adversarial skeptic pass
2369
+ 5. Synthesize — cited report, top model
2516
2370
 
2517
- Permission levels:
2518
- auto — run tools automatically without asking
2519
- ask — prompt before each tool execution (default)
2520
- bypass — skip all permission checks (use with caution)
2371
+ This may take a few minutes...
2372
+ `,
2373
+ forwardToAI: stripIndent`
2374
+ Conduct deep research on: "${topic}"
2375
+
2376
+ Use the Workflow tool to orchestrate a multi-agent research pipeline:
2377
+
2378
+ **Phase 1 — Scope**: Define 5 research angles covering: overview, technical-details,
2379
+ competitors/alternatives, criticism/limitations, future-trends.
2380
+
2381
+ **Phase 2 — Research**: Fan out parallel web searches across all 5 angles. Each agent
2382
+ searches the web and returns sources with titles, URLs, and key points. Use schema
2383
+ validation to ensure structured output.
2521
2384
 
2522
- Change with /config permission <level>.
2385
+ **Phase 3 — Reduce**: Deduplicate sources by URL. Merge overlapping findings. EDGE LOGIC
2386
+ IS FREE — use plain JavaScript, not agent calls.
2523
2387
 
2524
- Current directory permissions:
2525
- CWD: ${process.cwd()}
2388
+ **Phase 4 — Verify**: Run adversarial verification (verify with mode: 'adversarial',
2389
+ skeptics: 2, threshold: 1) on each key claim. Discard claims that fail verification.
2526
2390
 
2527
- To add a directory: use /add-dir (coming soon).
2528
- Tool execution is sandboxed to the project directory by default.`,
2391
+ **Phase 5 — Synthesize**: Use the best available model to synthesize all verified findings
2392
+ into a comprehensive, well-cited research report. Include:
2393
+ - Executive summary
2394
+ - Key findings with source citations (title, URL)
2395
+ - Competing perspectives and disagreements
2396
+ - Gaps and unknowns
2397
+ - Recommendations for further reading
2398
+
2399
+ Output the final report as a structured markdown document with inline citations.
2400
+ `,
2529
2401
  }
2530
2402
  }
2531
2403
 
2404
+ // ═══════════════════════════════════════════════════════════════
2405
+ // Permissions
2406
+ // ═══════════════════════════════════════════════════════════════
2407
+
2532
2408
  // ═══════════════════════════════════════════════════════════════
2533
2409
  // Recommend — analyze project and recommend setup (like Claude Code
2534
2410
  // automation recommender)
2535
2411
  // ═══════════════════════════════════════════════════════════════
2536
2412
 
2537
- const recommendCmd: CommandHandler = async (ctx) => {
2538
- const { existsSync, readFileSync } = await import('node:fs')
2539
- const { join } = await import('node:path')
2540
- const { getAvailableSkills } = await import('../skills/registry')
2541
-
2542
- const cwd = process.cwd()
2543
- const lines: string[] = ['── Mipham Code: Setup Recommendations ──', '', `Project: ${cwd}`, '']
2544
-
2545
- // ── Detect project type ──
2546
- const hasPackageJson = existsSync(join(cwd, 'package.json'))
2547
- const hasTsConfig = existsSync(join(cwd, 'tsconfig.json'))
2548
- const hasPyProject = existsSync(join(cwd, 'pyproject.toml'))
2549
- const hasRequirements = existsSync(join(cwd, 'requirements.txt'))
2550
- const hasDockerfile = existsSync(join(cwd, 'Dockerfile'))
2551
- const hasGitHubActions = existsSync(join(cwd, '.github', 'workflows'))
2552
- const hasNextConfig =
2553
- existsSync(join(cwd, 'next.config.js')) || existsSync(join(cwd, 'next.config.ts'))
2554
- const hasVueConfig =
2555
- existsSync(join(cwd, 'vite.config.ts')) || existsSync(join(cwd, 'vite.config.js'))
2556
- const hasTailwind =
2557
- existsSync(join(cwd, 'tailwind.config.ts')) || existsSync(join(cwd, 'tailwind.config.js'))
2558
-
2559
- let pkgData: Record<string, unknown> = {}
2560
- if (hasPackageJson) {
2561
- try {
2562
- pkgData = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'))
2563
- } catch {
2564
- /* ignore */
2565
- }
2566
- }
2567
- const deps = {
2568
- ...((pkgData.dependencies as Record<string, string>) || {}),
2569
- ...((pkgData.devDependencies as Record<string, string>) || {}),
2570
- }
2571
- const isTypeScript = hasTsConfig || 'typescript' in deps
2572
- const isReact = 'react' in deps || 'next' in deps
2573
- const isVue = 'vue' in deps
2574
- const isNode = hasPackageJson
2575
- const isPython = hasPyProject || hasRequirements
2576
- const isFastAPI = 'fastapi' in deps
2577
- const isNextJS = 'next' in deps
2578
- const isExpress = 'express' in deps || 'fastify' in deps
2579
- const isDocker = hasDockerfile
2580
-
2581
- // ── Detection summary ──
2582
- const tags: string[] = []
2583
- if (isTypeScript) tags.push('TypeScript')
2584
- if (isReact) tags.push('React')
2585
- if (isVue) tags.push('Vue')
2586
- if (isNextJS) tags.push('Next.js')
2587
- if (isExpress) tags.push('Node.js API')
2588
- if (isFastAPI) tags.push('FastAPI')
2589
- if (isPython) tags.push('Python')
2590
- if (isDocker) tags.push('Docker')
2591
- if (hasTailwind) tags.push('Tailwind CSS')
2592
- if (hasGitHubActions) tags.push('CI/CD')
2593
-
2594
- lines.push('── Detected Stack ──')
2595
- lines.push('')
2596
- if (tags.length > 0) {
2597
- lines.push(` ${tags.join(' · ')}`)
2598
- } else {
2599
- lines.push(' (generic project — no specific framework detected)')
2600
- }
2601
- lines.push('')
2602
-
2603
- // ── Skill recommendations ──
2604
- const communitySkills = getAvailableSkills()
2605
- const recommendedSkills: string[] = []
2606
-
2607
- // Always useful
2608
- recommendedSkills.push('code-review')
2609
- recommendedSkills.push('systematic-debugging')
2610
-
2611
- if (isTypeScript || isNode) {
2612
- recommendedSkills.push('github-ops')
2613
- }
2614
- if (isReact || isVue || isNextJS) {
2615
- recommendedSkills.push('frontend-design')
2616
- }
2617
- if (hasGitHubActions) {
2618
- recommendedSkills.push('security-review')
2619
- }
2620
- if (isPython) {
2621
- recommendedSkills.push('doc-generator')
2622
- }
2413
+ // ═══════════════════════════════════════════════════════════════
2414
+ // Setup — guided project initialization (mirrors Claude Code /setup)
2415
+ // ═══════════════════════════════════════════════════════════════
2623
2416
 
2624
- // Filter to only those in the registry
2625
- const available = recommendedSkills.filter((name) => communitySkills.some((s) => s.name === name))
2417
+ // ═══════════════════════════════════════════════════════════════
2418
+ // Theme — display theme toggle
2419
+ // ═══════════════════════════════════════════════════════════════
2626
2420
 
2627
- lines.push('── Recommended Skills ──')
2628
- lines.push('')
2629
- if (available.length > 0) {
2630
- for (const name of available) {
2631
- const entry = communitySkills.find((s) => s.name === name)!
2632
- lines.push(` /install-skill ${name.padEnd(26)} ${entry.description}`)
2633
- }
2634
- }
2635
- lines.push(' /browse-skills Browse all community skills')
2636
- lines.push('')
2421
+ const mcpCmd: CommandHandler = (ctx) => {
2422
+ const configuredServers = ctx.config.skills?.mcpServers ?? []
2423
+ const liveConnections = McpClient.getInstance().listConnections()
2637
2424
 
2638
- // ── Provider recommendations ──
2639
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
2640
- const configured = activeProviders.filter((p) => p.apiKey && p.apiKey.trim() !== '')
2425
+ const lines: string[] = ['── MCP Servers ──', '']
2641
2426
 
2642
- lines.push('── Provider Status ──')
2643
- lines.push('')
2644
- if (configured.length === 0) {
2645
- lines.push(' ⚠ No providers have API keys configured.')
2646
- lines.push(' Run /setup 2 to configure providers.')
2427
+ if (configuredServers.length > 0) {
2428
+ lines.push(`Configured servers (${configuredServers.length}):`)
2647
2429
  lines.push('')
2648
- lines.push(' Recommended for this project:')
2649
- if (isTypeScript || isNode || isReact) {
2650
- lines.push(' • anthropic — Claude (code generation, review)')
2651
- lines.push(' • openai — GPT-5 (general purpose)')
2652
- }
2653
- if (isPython) {
2654
- lines.push(' • anthropic — Claude (data science, ML)')
2430
+ for (const s of configuredServers) {
2431
+ const live = liveConnections.find((c) => c.config.name === s.name)
2432
+ const statusIcon = live
2433
+ ? live.status === 'connected'
2434
+ ? '🟢'
2435
+ : live.status === 'connecting'
2436
+ ? '🟡'
2437
+ : live.status === 'error'
2438
+ ? '🔴'
2439
+ : '⚪'
2440
+ : '⚪'
2441
+ const statusLabel = live ? live.status : 'not started'
2442
+ const envKeys = s.env ? Object.keys(s.env).join(', ') : '(none)'
2443
+
2444
+ lines.push(` ${statusIcon} ${s.name} [${statusLabel}]`)
2445
+ lines.push(` Command: ${s.command} ${s.args.join(' ')}`)
2446
+ lines.push(` Env vars: ${envKeys}`)
2447
+ if (live?.tools && live.tools.length > 0) {
2448
+ lines.push(` Tools: ${live.tools.map((t) => t.name).join(', ')}`)
2449
+ }
2450
+ if (live?.error) {
2451
+ lines.push(` Error: ${live.error}`)
2452
+ }
2453
+ if (live?.serverInfo) {
2454
+ lines.push(` Server: ${live.serverInfo.name} v${live.serverInfo.version}`)
2455
+ }
2456
+ lines.push('')
2655
2457
  }
2656
2458
  } else {
2657
- lines.push(` ${configured.length}/${activeProviders.length} providers configured`)
2658
- for (const p of configured) {
2659
- lines.push(` ✅ ${p.id.padEnd(14)} ${p.name}`)
2660
- }
2459
+ lines.push('No MCP servers configured.')
2460
+ lines.push('')
2461
+ lines.push('── Configuration ──')
2462
+ lines.push('')
2463
+ lines.push('Add MCP servers to .mipham/config.yml:')
2464
+ lines.push('')
2465
+ lines.push(' skills:')
2466
+ lines.push(' mcpServers:')
2467
+ lines.push(' - name: filesystem')
2468
+ lines.push(' command: npx')
2469
+ lines.push(' args: ["-y", "@anthropic/mcp-filesystem", "/path"]')
2470
+ lines.push(' env:')
2471
+ lines.push(' HOME: $HOME')
2472
+ lines.push('')
2473
+ lines.push(' - name: github')
2474
+ lines.push(' command: npx')
2475
+ lines.push(' args: ["-y", "@anthropic/mcp-github"]')
2476
+ lines.push(' env:')
2477
+ lines.push(' GITHUB_TOKEN: $GITHUB_TOKEN')
2478
+ lines.push('')
2479
+ lines.push('After configuring, restart Mipham Code to connect.')
2480
+ lines.push('Use the MCP tool (Tool 16) to call server tools.')
2661
2481
  }
2662
- lines.push('')
2663
2482
 
2664
- // ── Config recommendations ──
2665
- lines.push('── Configuration Tips ──')
2666
2483
  lines.push('')
2667
-
2668
- const hasProjectMipham = existsSync(join(cwd, '.mipham'))
2669
- if (!hasProjectMipham) {
2670
- lines.push(' /setup 1 Initialize .mipham/ + MIPHAM.md + config.yml')
2671
- }
2672
-
2673
- const { listInstalledSkills } = await import('../skills/registry')
2674
- const installed = listInstalledSkills()
2675
- if (installed.length === 0 && available.length > 0) {
2676
- lines.push(' Tip: Install recommended skills above for better AI assistance')
2677
- }
2678
-
2679
- if (isDocker && !hasProjectMipham) {
2680
- lines.push(' Tip: Add .mipham/ to .dockerignore for smaller images')
2681
- }
2682
-
2683
- if (hasGitHubActions) {
2684
- lines.push(' /setup 5 Configure tool permissions for CI/CD safety')
2685
- }
2686
-
2687
- lines.push(' /setup Full setup wizard (6 steps)')
2688
- lines.push(' /help All commands')
2484
+ lines.push('── Protocol ──')
2485
+ lines.push('MCP stdio transport (JSON-RPC 2.0) — fully implemented.')
2486
+ lines.push('Servers auto-connect on startup when configured.')
2689
2487
  lines.push('')
2488
+ lines.push('Learn more: https://modelcontextprotocol.io')
2690
2489
 
2691
2490
  return { content: lines.join('\n') }
2692
2491
  }
2693
2492
 
2694
2493
  // ═══════════════════════════════════════════════════════════════
2695
- // Setup — guided project initialization (mirrors Claude Code /setup)
2494
+ // Phase 4 — Login / API Key Management
2696
2495
  // ═══════════════════════════════════════════════════════════════
2697
2496
 
2698
- const setupCmd: CommandHandler = async (ctx, args) => {
2699
- const step = args[0]
2497
+ const loginCmd: CommandHandler = (ctx) => {
2498
+ const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
2700
2499
 
2701
- // ── Step selection ──
2702
- if (step === '1' || step === 'init') {
2703
- return setupStep1(ctx)
2704
- }
2705
- if (step === '2' || step === 'providers') {
2706
- return setupStep2(ctx)
2707
- }
2708
- if (step === '3' || step === 'model') {
2709
- return setupStep3(ctx)
2710
- }
2711
- if (step === '4' || step === 'skills') {
2712
- return setupStep4(ctx)
2713
- }
2714
- if (step === '5' || step === 'permissions') {
2715
- return setupStep5(ctx)
2716
- }
2717
- if (step === '6' || step === 'shell') {
2718
- return setupStep6(ctx)
2719
- }
2720
-
2721
- // ── Check existing setup status ──
2722
- const { existsSync } = await import('node:fs')
2723
- const { join } = await import('node:path')
2724
- const cwd = process.cwd()
2725
- const home = process.env.HOME || '~'
2726
-
2727
- const hasProjectMipham = existsSync(join(cwd, 'MIPHAM.md'))
2728
- const hasProjectConfig = existsSync(join(cwd, '.mipham', 'config.yml'))
2729
- const hasUserConfig = existsSync(join(home, '.mipham', 'config.yml'))
2730
- const hasMiphamDir = existsSync(join(cwd, '.mipham'))
2731
-
2732
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active').length
2733
- const totalProviders = ctx.config.providers.length
2734
- const skills = ctx.skillsLoader?.list() || []
2735
- const standardSkills = skills.filter((s: { type: string }) => s.type === 'standard').length
2736
- const miphamSkills = skills.filter((s: { type: string }) => s.type === 'mipham').length
2737
-
2738
- const statusIcon = (ok: boolean) => (ok ? '✅' : '⬜')
2739
-
2740
- return {
2741
- content: `── Mipham Code Setup ──
2742
-
2743
-
2744
- Project Status
2745
- ${statusIcon(hasMiphamDir)} .mipham/ directory ${hasMiphamDir ? '(config + metadata)' : '(not created)'}
2746
- ${statusIcon(hasProjectMipham)} MIPHAM.md ${hasProjectMipham ? '(project personality)' : '(not created)'}
2747
- ${statusIcon(hasProjectConfig)} Project config ${hasProjectConfig ? '~/.mipham/config.yml' : '(not created)'}
2748
- ${statusIcon(hasUserConfig)} User config ${hasUserConfig ? '~/.mipham/config.yml' : '(not created)'}
2749
-
2750
- Providers
2751
- ${activeProviders}/${totalProviders} active · Current: ${ctx.providerId}/${ctx.modelId}
2752
-
2753
- Skills
2754
- ${skills.length} loaded (${standardSkills} standard + ${miphamSkills} mipham)
2755
-
2756
- Permissions
2757
- Mode: ${ctx.config.permission} · Tools: ${ctx.engine.getTools().size}
2758
-
2759
-
2760
- ── Setup Steps ──
2761
-
2762
- 1. Initialize Project /setup 1 Create .mipham/ + MIPHAM.md + config.yml
2763
- 2. Configure Providers /setup 2 Set API keys, enable/disable providers
2764
- 3. Set Default Model /setup 3 Choose your preferred provider & model
2765
- 4. Install Skills /setup 4 Browse and install community skills
2766
- 5. Permissions Setup /setup 5 Configure tool access and security
2767
- 6. Shell Integration /setup 6 Add \`mipham\` to PATH, IDE setup
2768
-
2769
- Run: /setup <number> or chat: "help me set up Mipham Code"`,
2770
- }
2771
- }
2772
-
2773
- async function setupStep1(ctx: CommandContext): Promise<CommandResult> {
2774
- const { existsSync, mkdirSync, writeFileSync } = await import('node:fs')
2775
- const { join } = await import('node:path')
2776
- const cwd = process.cwd()
2777
-
2778
- const miphamDir = join(cwd, '.mipham')
2779
- const miphamPath = join(cwd, 'MIPHAM.md')
2780
- const configPath = join(miphamDir, 'config.yml')
2781
-
2782
- const created: string[] = []
2783
- const skipped: string[] = []
2784
-
2785
- // Create .mipham/ directory
2786
- if (!existsSync(miphamDir)) {
2787
- mkdirSync(miphamDir, { recursive: true })
2788
- created.push('.mipham/')
2789
- } else {
2790
- skipped.push('.mipham/ (already exists)')
2791
- }
2792
-
2793
- // Create project config if missing
2794
- if (!existsSync(configPath)) {
2795
- // Generate a user-friendly config with all providers pre-populated.
2796
- // Users just need to replace the API key placeholders with their real keys.
2797
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
2798
- const providerYaml = activeProviders
2799
- .map((p) => {
2800
- const comment =
2801
- p.id === 'anthropic'
2802
- ? '# Get key: https://console.anthropic.com/'
2803
- : p.id === 'openai'
2804
- ? '# Get key: https://platform.openai.com/api-keys'
2805
- : p.id === 'deepseek'
2806
- ? '# Get key: https://platform.deepseek.com/api_keys'
2807
- : p.id === 'kimi'
2808
- ? '# Get key: https://platform.moonshot.cn/'
2809
- : p.id === 'doubao'
2810
- ? '# Get key: https://console.volcengine.com/ark'
2811
- : p.id === 'hunyuan'
2812
- ? '# Get key: https://console.cloud.tencent.com/hunyuan'
2813
- : p.id === 'qwen'
2814
- ? '# Get key: https://dashscope.console.aliyun.com/apiKey'
2815
- : p.id === 'google'
2816
- ? '# Get key: https://aistudio.google.com/apikey'
2817
- : ''
2818
- const baseUrlLine = p.baseUrl ? `\n baseUrl: "${p.baseUrl}"` : ''
2819
- return ` ${comment}
2820
- - id: ${p.id}
2821
- name: "${p.name}"${baseUrlLine}
2822
- apiKey: "\${${p.id.toUpperCase()}_API_KEY}"`
2823
- })
2824
- .join('\n\n')
2825
-
2826
- const defaultConfig = `# Mipham Code — User Configuration
2827
- # Location: ~/.mipham/config.yml
2828
- # Docs: https://mipham.ai/code/docs/config
2829
- #
2830
- # ═══ Quick Start ═══
2831
- # 1. Set your API keys below (replace the placeholder values)
2832
- # 2. Save the file
2833
- # 3. Run 'mipham' — it auto-detects configured providers
2834
- #
2835
- # ═══ Environment Variables ═══
2836
- # Instead of editing this file, you can set env vars:
2837
- # export ANTHROPIC_API_KEY="sk-ant-..."
2838
- # export OPENAI_API_KEY="sk-..."
2839
- # (The \${VAR} syntax below reads from environment variables)
2840
-
2841
- # ── Defaults ──
2842
- defaultProvider: ${ctx.providerId}
2843
- defaultModel: ${ctx.modelId}
2844
- permission: ask
2845
-
2846
- # ── Providers (8 configured, just add your API keys) ──
2847
- providers:
2848
- ${providerYaml}
2849
- `
2850
- writeFileSync(configPath, defaultConfig, 'utf-8')
2851
- created.push('.mipham/config.yml')
2852
- } else {
2853
- skipped.push('.mipham/config.yml (already exists)')
2854
- }
2855
-
2856
- // Create MIPHAM.md if missing
2857
- if (!existsSync(miphamPath)) {
2858
- const projectName = cwd.split('/').pop() || 'my-project'
2859
- const defaultMipham = `---
2860
- model: mipham-code
2861
- version: 1.0.0
2862
- privacy: project
2863
- language: zh-CN
2864
- ---
2865
-
2866
- # MIPHAM.md — ${projectName}
2867
-
2868
- > 本文件定义 ${projectName} 项目中 AI 助手的交互人格和项目规范。
2869
- > 继承自 One Mipham Corporation 集团 MIPHAM.md。
2870
-
2871
- ---
2872
-
2873
- ## 项目概述
2874
-
2875
- [简要描述项目目的和定位]
2876
-
2877
- ## 技术栈
2878
-
2879
- [列出主要技术栈]
2880
-
2881
- ## 项目规范
2882
-
2883
- - [添加项目特有的编码规则]
2884
- - [添加团队约定]
2885
-
2886
- ## AI 交互偏好
2887
-
2888
- - 回复语言:[中文/英文]
2889
- - 代码风格:[偏好]
2890
- - 注释语言:[中文/英文]
2891
- `
2892
- writeFileSync(miphamPath, defaultMipham, 'utf-8')
2893
- created.push('MIPHAM.md')
2894
- } else {
2895
- skipped.push('MIPHAM.md (already exists)')
2896
- }
2897
-
2898
- const lines: string[] = ['── Step 1: Initialize Project ──', '']
2899
- if (created.length > 0) {
2900
- lines.push('Created:')
2901
- for (const c of created) lines.push(` ✅ ${c}`)
2902
- }
2903
- if (skipped.length > 0) {
2904
- lines.push('')
2905
- lines.push('Skipped (already configured):')
2906
- for (const s of skipped) lines.push(` ⏭ ${s}`)
2907
- }
2908
- lines.push('')
2909
- lines.push('Next: /setup 2 to configure providers')
2910
-
2911
- return { content: lines.join('\n') }
2912
- }
2913
-
2914
- async function setupStep2(ctx: CommandContext): Promise<CommandResult> {
2915
- const active = ctx.config.providers.filter((p) => p.status === 'active')
2916
- const upcoming = ctx.config.providers.filter((p) => p.status === 'upcoming')
2917
-
2918
- const lines: string[] = [
2919
- '── Step 2: Configure Providers ──',
2920
- '',
2921
- `Active providers (${active.length}):`,
2922
- ...active.map((p) => ` ✅ ${p.id.padEnd(14)} ${p.name.padEnd(20)} ${p.protocol}`),
2923
- '',
2924
- `Upcoming (${upcoming.length}):`,
2925
- ...upcoming.map((p) => ` 🔶 ${p.id.padEnd(14)} ${p.name.padEnd(20)} ${p.protocol}`),
2926
- '',
2927
- '── API Key Setup ──',
2928
- '',
2929
- 'Set API keys via environment variables or .mipham/config.yml:',
2930
- '',
2931
- ' export ANTHROPIC_API_KEY="sk-ant-..."',
2932
- ' export OPENAI_API_KEY="sk-..."',
2933
- ' export DEEPSEEK_API_KEY="sk-..."',
2934
- ' export QWEN_API_KEY="sk-..."',
2935
- ' export DOUBAO_API_KEY="..."',
2936
- ' export HUNYUAN_API_KEY="..."',
2937
- '',
2938
- 'Or add to ~/.mipham/config.yml:',
2939
- ' providers:',
2940
- ' - id: anthropic',
2941
- ' apiKey: $ANTHROPIC_API_KEY',
2942
- '',
2943
- 'Current: ' + ctx.providerId + ' / ' + ctx.modelId,
2944
- '',
2945
- 'Next: /setup 3 to choose default model',
2946
- ]
2947
-
2948
- return { content: lines.join('\n') }
2949
- }
2950
-
2951
- async function setupStep3(ctx: CommandContext): Promise<CommandResult> {
2952
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
2953
-
2954
- const lines: string[] = [
2955
- '── Step 3: Set Default Model ──',
2956
- '',
2957
- `Current: ${ctx.providerId} / ${ctx.modelId}`,
2958
- '',
2959
- 'Available providers & models:',
2960
- '',
2961
- ]
2962
-
2963
- for (const p of activeProviders) {
2964
- lines.push(` ${p.id}${p.id === ctx.providerId ? ' ← current' : ''}`)
2965
- for (const m of p.models.filter((m) => m.status === 'active')) {
2966
- const marker = m.id === ctx.modelId ? ' ★' : ' '
2967
- lines.push(
2968
- `${marker} ${m.id.padEnd(30)} ${m.contextWindow.toLocaleString()} ctx ${m.vision ? '🖼' : '📝'}`,
2969
- )
2970
- }
2971
- lines.push('')
2972
- }
2973
-
2974
- lines.push('To switch: /switch <provider> <model>')
2975
- lines.push('To make permanent: edit .mipham/config.yml → defaultProvider / defaultModel')
2976
- lines.push('')
2977
- lines.push('Next: /setup 4 to install skills')
2978
-
2979
- return { content: lines.join('\n') }
2980
- }
2981
-
2982
- async function setupStep4(ctx: CommandContext): Promise<CommandResult> {
2983
- const counts = ctx.skillsLoader?.countByType() ?? { standard: 0, mipham: 0, total: 0 }
2984
- const standardNames = ctx.skillsLoader?.getNamesByType('standard') ?? []
2985
- const miphamNames = ctx.skillsLoader?.getNamesByType('mipham') ?? []
2986
-
2987
- return {
2988
- content: `── Step 4: Install Skills ──
2989
-
2990
- Skills extend Mipham Code with specialized capabilities.
2991
-
2992
- Built-in skills (${counts.total} total):
2993
- Standard (${counts.standard}): ${standardNames.join(', ')}
2994
- Mipham (${counts.mipham}): ${miphamNames.join(', ')}
2995
-
2996
- Community skills:
2997
- Coming soon — the Mipham Code skills marketplace will let you
2998
- browse and install community-contributed skills.
2999
-
3000
- For now, add custom skills manually:
3001
- 1. Create a .SKILL.md file in .mipham/skills/
3002
- 2. Use /reload-skills to load it
3003
-
3004
- Skill file template:
3005
- ---
3006
- name: my-skill
3007
- description: What this skill does
3008
- version: 1.0.0
3009
- type: standard
3010
- ---
3011
- # My Skill
3012
- [instructions for the AI]
3013
-
3014
- Next: /setup 5 to configure permissions`,
3015
- }
3016
- }
3017
-
3018
- async function setupStep5(ctx: CommandContext): Promise<CommandResult> {
3019
- return {
3020
- content: `── Step 5: Permissions Setup ──
3021
-
3022
- Current mode: ${ctx.config.permission}
3023
-
3024
- Permission levels:
3025
- auto — Run tools automatically (suitable for sandboxed envs)
3026
- ask — Prompt before each tool execution (default, recommended)
3027
- bypass — Skip all checks (⚠ only for trusted codebases)
3028
-
3029
- Change with: /config permission <level>
3030
-
3031
- Available tools (${ctx.engine.getTools().size}):
3032
- File: read, write, edit, glob, grep
3033
- Exec: bash, git, task
3034
- Agent: agent, memory, plan, skill
3035
- Net: web-fetch, web-search
3036
- Sys: config, mcp
3037
-
3038
- Each tool category can be configured independently in .mipham/config.yml:
3039
- permissions:
3040
- file: ask
3041
- exec: ask
3042
- network: auto
3043
-
3044
- Next: /setup 6 for shell integration`,
3045
- }
3046
- }
3047
-
3048
- async function setupStep6(_ctx: CommandContext): Promise<CommandResult> {
3049
- return {
3050
- content: `── Step 6: Shell Integration ──
3051
-
3052
- Add Mipham Code to your shell:
3053
-
3054
- # Add to ~/.zshrc or ~/.bashrc
3055
- alias mipham='cd ~/your-project && bun run ~/path/to/mipham-code/apps/cli/bin/mipham.ts'
3056
-
3057
- # Or if installed globally:
3058
- alias mipham='mipham'
3059
-
3060
- IDE Integration:
3061
- VS Code — coming soon (extension marketplace)
3062
- JetBrains — coming soon (plugin)
3063
- Terminal — run \`mipham\` in any terminal
3064
-
3065
- Quick launch:
3066
- Ctrl+P Open model picker
3067
- /help Show all commands
3068
- Esc Exit
3069
-
3070
- ── Setup Complete! ──
3071
-
3072
- You're all set. Start a conversation:
3073
- "help me build a REST API"
3074
- "review my code"
3075
- "explain this project"
3076
-
3077
- For help at any time: /help`,
3078
- }
3079
- }
3080
-
3081
- // ═══════════════════════════════════════════════════════════════
3082
- // Theme — display theme toggle
3083
- // ═══════════════════════════════════════════════════════════════
3084
-
3085
- const themeCmd: CommandHandler = (_ctx, args) => {
3086
- const theme = args[0]?.toLowerCase()
3087
- const validThemes = ['dark', 'light', 'auto'] as const
3088
-
3089
- if (!theme || !validThemes.includes(theme as (typeof validThemes)[number])) {
3090
- return {
3091
- content: `── Theme ──
3092
-
3093
- Terminal color themes (set in .mipham/config.yml):
3094
-
3095
- theme: dark — dark background (default, recommended for terminals)
3096
- theme: light — light background
3097
- theme: auto — follow system preference
3098
-
3099
- Usage: /theme dark | light | auto
3100
-
3101
- Current: auto (follows terminal)`,
3102
- }
3103
- }
3104
-
3105
- return {
3106
- content: `✓ Theme set to "${theme}".
3107
-
3108
- Add to ~/.mipham/config.yml to persist:
3109
- theme: ${theme}
3110
-
3111
- Terminal themes affect syntax highlighting and UI accents.
3112
- Full theme customization is available in the Web UI at https://mipham.ai/code/dashboard`,
3113
- }
3114
- }
3115
-
3116
- // ═══════════════════════════════════════════════════════════════
3117
- // Add-Dir — add a directory to workspace permissions
3118
- // ═══════════════════════════════════════════════════════════════
3119
-
3120
- const addDirCmd: CommandHandler = async (_ctx, args) => {
3121
- const dir = args[0]
3122
- if (!dir) {
3123
- return {
3124
- content: `Usage: /add-dir <path>
3125
-
3126
- Add a directory to Mipham Code's allowed workspace paths.
3127
- This grants the AI permission to read/write files in that directory.
3128
-
3129
- Examples:
3130
- /add-dir ~/projects/my-api
3131
- /add-dir /usr/local/share/data
3132
-
3133
- Current allowed directories:
3134
- • ${process.cwd()} (project root, always allowed)
3135
-
3136
- Note: For security, tools like bash already respect .mipham/config.yml
3137
- permission boundaries. Adding directories here extends read/write access.`,
3138
- }
3139
- }
3140
-
3141
- const { existsSync } = await import('node:fs')
3142
- const { resolve } = await import('node:path')
3143
- const resolved = resolve(dir.replace(/^~/, process.env.HOME || '~'))
3144
-
3145
- if (!existsSync(resolved)) {
3146
- return { content: `✗ Directory not found: ${resolved}\n\nCheck the path and try again.` }
3147
- }
3148
-
3149
- return {
3150
- content: `✓ Directory registered: ${resolved}
3151
-
3152
- To persist across sessions, add to .mipham/config.yml:
3153
- workspace:
3154
- extraDirs:
3155
- - ${resolved}
3156
-
3157
- The AI can now access files in this directory.
3158
- Permission level is controlled by /config permission <level>.`,
3159
- }
3160
- }
3161
-
3162
- // ═══════════════════════════════════════════════════════════════
3163
- // Security / Audit — security review checklist
3164
- // ═══════════════════════════════════════════════════════════════
3165
-
3166
- const promptAuditCmd: CommandHandler = async () => {
3167
- const { existsSync, readFileSync, readdirSync, statSync } = await import('node:fs')
3168
- const { join, extname, basename } = await import('node:path')
3169
- const cwd = process.cwd()
3170
-
3171
- // Patterns that suggest prompts written for older/less-capable models
3172
- const AUDIT_RULES: Array<{
3173
- id: string
3174
- pattern: RegExp
3175
- severity: 'low' | 'medium' | 'high'
3176
- message: string
3177
- suggestion: string
3178
- }> = [
3179
- {
3180
- id: 'over-explaining',
3181
- pattern:
3182
- /you are (a|an) (helpful |friendly |knowledgeable )?(AI |language model |assistant)/i,
3183
- severity: 'low',
3184
- message: 'Self-identification preamble ("You are a...")',
3185
- suggestion: "Modern models don't need role preamble. Remove or shorten to 1 line.",
3186
- },
3187
- {
3188
- id: 'step-by-step',
3189
- pattern: /(think|reason|work) (step[ -]?by[ -]?step|through this|carefully about this)/i,
3190
- severity: 'low',
3191
- message: 'Step-by-step reasoning prompt',
3192
- suggestion: 'Reasoning models handle this natively. Remove for token savings.',
3193
- },
3194
- {
3195
- id: 'do-not-list',
3196
- pattern:
3197
- /(do not|never|you must not|don't|under no circumstances).*\n.*(do not|never|you must not|don't)/i,
3198
- severity: 'medium',
3199
- message: 'Multiple "do not" constraints',
3200
- suggestion: 'Modern models need fewer prohibitions. Consolidate to 1-2 key constraints.',
3201
- },
3202
- {
3203
- id: 'token-waste',
3204
- pattern:
3205
- /(remember|keep in mind|it is important to note|please note that|i want to emphasize)/i,
3206
- severity: 'low',
3207
- message: 'Filler emphasis phrases ("remember", "please note")',
3208
- suggestion: 'Remove filler. State the instruction directly.',
3209
- },
3210
- {
3211
- id: 'format-over-spec',
3212
- pattern: /(you must (always |only )?respond (in|with|using) (JSON|XML|YAML|markdown|HTML))/i,
3213
- severity: 'low',
3214
- message: 'Over-specified output format',
3215
- suggestion: 'Modern models follow format instructions with fewer words. Shorten.',
3216
- },
3217
- {
3218
- id: 'example-overload',
3219
- pattern: /((?:example|for instance|e\.g\.,).*\n){3,}/i,
3220
- severity: 'low',
3221
- message: 'Three or more examples in sequence',
3222
- suggestion: '1-2 examples suffice for modern models. Remove redundant ones.',
3223
- },
3224
- {
3225
- id: 'old-model-ref',
3226
- pattern: /(GPT-3|GPT-3\.5|Claude\s*[12][^3]|text-davinci|command-r|llama\s*2)/i,
3227
- severity: 'high',
3228
- message: 'Reference to older model',
3229
- suggestion: 'Update model references to current generation (Sonnet 5, Opus 5, GPT-5, etc.).',
3230
- },
3231
- {
3232
- id: 'long-preamble',
3233
- pattern: /^.{200,}$/m,
3234
- severity: 'medium',
3235
- message: 'Very long single-line instructions (200+ chars)',
3236
- suggestion: 'Break into bullet points. Modern models parse structured prompts better.',
3237
- },
3238
- {
3239
- id: 'verbose-constraint',
3240
- pattern:
3241
- /you (must|should|shall) (always |only |never )?(ensure|guarantee|verify|validate|confirm|cross-check|double-check)/i,
3242
- severity: 'medium',
3243
- message: 'Overly verbose constraint language',
3244
- suggestion: 'Replace "you must ensure that" with imperative: "Ensure".',
3245
- },
3246
- ]
3247
-
3248
- // Files to scan
3249
- const scanDirs = [
3250
- join(cwd, '.mipham', 'skills'),
3251
- join(cwd, '.mipham', 'rules'),
3252
- join(cwd, '.claude'),
3253
- ]
3254
- const skillFiles: string[] = []
3255
- // Also check standard project files
3256
- const projectFiles = [join(cwd, 'CLAUDE.md'), join(cwd, 'MIPHAM.md'), join(cwd, 'PRODUCT.md')]
3257
-
3258
- // Collect skill/rules files
3259
- for (const dir of scanDirs) {
3260
- try {
3261
- if (!existsSync(dir)) continue
3262
- const walk = (d: string) => {
3263
- const entries = readdirSync(d)
3264
- for (const e of entries) {
3265
- const fp = join(d, e)
3266
- const st = statSync(fp)
3267
- if (st.isDirectory()) {
3268
- walk(fp)
3269
- continue
3270
- }
3271
- if (['.md', '.SKILL.md', '.mipham-skill.md', '.txt'].some((ext) => fp.endsWith(ext))) {
3272
- skillFiles.push(fp)
3273
- }
3274
- }
3275
- }
3276
- walk(dir)
3277
- } catch {
3278
- /* skip */
3279
- }
3280
- }
3281
-
3282
- const allFiles = [...new Set([...projectFiles.filter((f) => existsSync(f)), ...skillFiles])]
3283
-
3284
- if (allFiles.length === 0) {
3285
- return {
3286
- content: [
3287
- '🔍 Prompt Audit',
3288
- '',
3289
- 'No prompt files found to audit.',
3290
- 'Place skills in .mipham/skills/ and rules in .mipham/rules/.',
3291
- ].join('\n'),
3292
- }
3293
- }
3294
-
3295
- // Scan each file
3296
- const results: Array<{
3297
- file: string
3298
- line: number
3299
- ruleId: string
3300
- severity: string
3301
- message: string
3302
- suggestion: string
3303
- }> = []
3304
-
3305
- for (const fp of allFiles) {
3306
- try {
3307
- const content = readFileSync(fp, 'utf-8')
3308
- const lines = content.split('\n')
3309
- for (const rule of AUDIT_RULES) {
3310
- // Multi-line pattern: check full content
3311
- if (rule.id === 'do-not-list' || rule.id === 'example-overload') {
3312
- if (rule.pattern.test(content)) {
3313
- // Find approx line
3314
- const match = rule.pattern.exec(content)
3315
- const idx = match ? content.slice(0, match.index).split('\n').length : 1
3316
- results.push({
3317
- file: fp.replace(cwd + '/', ''),
3318
- line: idx,
3319
- ruleId: rule.id,
3320
- severity: rule.severity,
3321
- message: rule.message,
3322
- suggestion: rule.suggestion,
3323
- })
3324
- }
3325
- continue
3326
- }
3327
-
3328
- // Line-by-line patterns
3329
- for (let i = 0; i < lines.length; i++) {
3330
- const line = lines[i]!
3331
- if (rule.pattern.test(line)) {
3332
- results.push({
3333
- file: fp.replace(cwd + '/', ''),
3334
- line: i + 1,
3335
- ruleId: rule.id,
3336
- severity: rule.severity,
3337
- message: rule.message,
3338
- suggestion: rule.suggestion,
3339
- })
3340
- }
3341
- }
3342
- }
3343
- } catch {
3344
- /* skip unreadable files */
3345
- }
3346
- }
3347
-
3348
- // Format output
3349
- const severityIcon: Record<string, string> = { high: '🔴', medium: '🟡', low: '🟢' }
3350
- const grouped: Record<string, typeof results> = {}
3351
- for (const r of results) {
3352
- const key = r.file
3353
- if (!grouped[key]) grouped[key] = []
3354
- grouped[key]!.push(r)
3355
- }
3356
-
3357
- const output: string[] = [
3358
- `🔍 Prompt Audit — ${allFiles.length} file(s) scanned, ${results.length} finding(s)`,
3359
- '',
3360
- ]
3361
-
3362
- if (results.length === 0) {
3363
- output.push('✅ All prompts look optimized for modern models. No issues found.')
3364
- } else {
3365
- for (const [file, items] of Object.entries(grouped)) {
3366
- output.push(`📄 ${file} (${items!.length} finding${items!.length > 1 ? 's' : ''})`)
3367
- for (const item of items!) {
3368
- output.push(
3369
- ` ${severityIcon[item.severity] || '⚪'} L${item.line}: [${item.ruleId}] ${item.message}`,
3370
- )
3371
- output.push(` → ${item.suggestion}`)
3372
- }
3373
- output.push('')
3374
- }
3375
- }
3376
-
3377
- output.push('─'.repeat(60))
3378
- output.push('Scanned: ' + allFiles.map((f) => basename(f)).join(', '))
3379
-
3380
- return { content: output.join('\n') }
3381
- }
3382
-
3383
- const securityCmd: CommandHandler = async () => {
3384
- const findings: string[] = []
3385
- const ok: string[] = []
3386
-
3387
- // Check for common security issues
3388
- const { existsSync, readFileSync } = await import('node:fs')
3389
- const { join } = await import('node:path')
3390
- const cwd = process.cwd()
3391
-
3392
- // 1. Check .gitignore for sensitive patterns
3393
- if (existsSync(join(cwd, '.gitignore'))) {
3394
- const gi = readFileSync(join(cwd, '.gitignore'), 'utf-8')
3395
- const hasEnv = gi.includes('.env')
3396
- const hasKeys = gi.includes('*.key') || gi.includes('*.pem')
3397
- if (hasEnv && hasKeys) {
3398
- ok.push('.gitignore covers .env + key files')
3399
- } else {
3400
- findings.push('Add .env, *.key, *.pem to .gitignore')
3401
- }
3402
- } else {
3403
- findings.push('No .gitignore found — create one with .env, node_modules, dist')
3404
- }
3405
-
3406
- // 2. Check for hardcoded secrets (quick grep for common patterns)
3407
- // ⚠ Security: results are redacted — only file:line locations are shown, never values
3408
- try {
3409
- const { execSync } = await import('node:child_process')
3410
- const secretPatterns = execSync(
3411
- `grep -rIn --include="*.ts" --include="*.js" --include="*.yml" --include="*.yaml" --include="*.json" -E "(API_KEY|SECRET|PASSWORD|TOKEN)\\s*=\\s*['\\\"][^$]" ${cwd} 2>/dev/null | grep -v node_modules | grep -v '.git/' | head -5 || echo ""`,
3412
- { encoding: 'utf-8', timeout: 5000 },
3413
- ).trim()
3414
- if (secretPatterns) {
3415
- // Redact the actual values — only show file:line locations
3416
- const redacted = secretPatterns
3417
- .split('\n')
3418
- .map((l) => {
3419
- const colonIdx = l.indexOf(':')
3420
- const secondColon = l.indexOf(':', colonIdx + 1)
3421
- if (secondColon > 0) {
3422
- return ' ' + l.slice(0, secondColon) + ' [VALUE REDACTED]'
3423
- }
3424
- return ' ' + l + ' [REDACTED]'
3425
- })
3426
- .join('\n')
3427
- findings.push(
3428
- `⚠ Hardcoded secrets detected (values redacted for security):\n${redacted}\n\n Replace with env vars: \${VAR_NAME} syntax`,
3429
- )
3430
- } else {
3431
- ok.push('No hardcoded secrets detected')
3432
- }
3433
- } catch {
3434
- // grep may fail if no matches — that's good
3435
- ok.push('No hardcoded secrets detected (quick scan)')
3436
- }
3437
-
3438
- // 3. Check for TLS in dependencies
3439
- if (existsSync(join(cwd, 'package.json'))) {
3440
- ok.push('package.json present — dependencies manageable')
3441
- }
3442
-
3443
- // 4. Check for license
3444
- if (existsSync(join(cwd, 'LICENSE'))) {
3445
- ok.push('LICENSE file present')
3446
- } else {
3447
- findings.push('No LICENSE file — add Apache 2.0 or appropriate license')
3448
- }
3449
-
3450
- // 5. CI/CD
3451
- if (existsSync(join(cwd, '.github', 'workflows'))) {
3452
- ok.push('CI/CD workflows configured')
3453
- } else {
3454
- findings.push('No CI/CD workflows found — add .github/workflows/')
3455
- }
3456
-
3457
- const lines: string[] = ['── Security Review ──', '', `Scanning: ${cwd}`, '']
3458
-
3459
- if (ok.length > 0) {
3460
- lines.push(`✅ Passed (${ok.length}):`)
3461
- for (const o of ok) lines.push(` • ${o}`)
3462
- }
3463
- if (findings.length > 0) {
3464
- lines.push('')
3465
- lines.push(`⚠ Findings (${findings.length}):`)
3466
- for (const f of findings) lines.push(` • ${f}`)
3467
- }
3468
- lines.push('')
3469
- lines.push('For a full audit: type "audit my project for security issues" in chat.')
3470
-
3471
- return { content: lines.join('\n') }
3472
- }
3473
-
3474
- // ═══════════════════════════════════════════════════════════════
3475
- // Release Notes — version changelog
3476
- // ═══════════════════════════════════════════════════════════════
3477
-
3478
- const releaseNotesCmd: CommandHandler = () => ({
3479
- content: `── Release Notes ──
3480
-
3481
- v0.1.2 (2026-06-09) — Current
3482
- • 48 slash commands (up from 20)
3483
- • /setup guided project initialization wizard
3484
- • Checkpoint/rewind mechanism
3485
- • Focus mode (last exchange view)
3486
- • 3-level MIPHAM.md architecture
3487
- • /doctor system diagnostics
3488
- • /export conversation to file
3489
- • /review and /pr-comments code review workflows
3490
- • /memory management
3491
- • /upgrade instructions
3492
- • Clipboard support (macOS/Windows)
3493
-
3494
- v0.1.1 (2026-06-02)
3495
- • Inline shared module for standalone builds
3496
- • Bin path fix for npm compatibility
3497
-
3498
- v0.1.0 (2026-06-01)
3499
- • Initial release
3500
- • Multi-model support (7 providers)
3501
- • 16 built-in tools
3502
- • 11 skills (9 standard + 2 mipham)
3503
- • Ctrl+P interactive model picker
3504
- • SSE streaming support
3505
-
3506
- Full changelog: https://mipham.ai/code/releases`,
3507
- })
3508
-
3509
- // ═══════════════════════════════════════════════════════════════
3510
- // IDE — IDE integration guide
3511
- // ═══════════════════════════════════════════════════════════════
3512
-
3513
- const ideCmd: CommandHandler = async (_ctx) => {
3514
- const { mkdirSync, writeFileSync, existsSync } = await import('node:fs')
3515
- const { join } = await import('node:path')
3516
-
3517
- const cwd = process.cwd()
3518
- const vscodeDir = join(cwd, '.vscode')
3519
- mkdirSync(vscodeDir, { recursive: true })
3520
-
3521
- const files: string[] = []
3522
-
3523
- // ── Detect bun path ──
3524
- let bunPath = '/opt/homebrew/bin/bun'
3525
- try {
3526
- const { execSync } = await import('node:child_process')
3527
- const detected = execSync('which bun 2>/dev/null || echo /opt/homebrew/bin/bun', {
3528
- encoding: 'utf-8',
3529
- }).trim()
3530
- if (detected) bunPath = detected
3531
- } catch {
3532
- // Use default
3533
- }
3534
-
3535
- // ── settings.json: terminal profile ──
3536
- const settingsPath = join(vscodeDir, 'settings.json')
3537
- const settings = {
3538
- 'terminal.integrated.profiles.osx': {
3539
- mipham: {
3540
- path: bunPath,
3541
- args: ['run', 'mipham'],
3542
- cwd: '${workspaceFolder}',
3543
- },
3544
- },
3545
- }
3546
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
3547
- files.push('.vscode/settings.json')
3548
-
3549
- // ── keybindings.json: Cmd+Esc launch ──
3550
- const keybindingsPath = join(vscodeDir, 'keybindings.json')
3551
- const keybindings = [
3552
- {
3553
- key: 'cmd+escape',
3554
- command: 'workbench.action.terminal.focus',
3555
- when: 'terminalProcessSupported',
3556
- },
3557
- {
3558
- key: 'cmd+shift+m',
3559
- command: 'workbench.action.terminal.new',
3560
- },
3561
- ]
3562
- writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2) + '\n', 'utf-8')
3563
- files.push('.vscode/keybindings.json')
3564
-
3565
- // ── extensions.json: recommended ──
3566
- const extensionsPath = join(vscodeDir, 'extensions.json')
3567
- const extensions = {
3568
- recommendations: ['miphamai.mipham-code'],
3569
- }
3570
- writeFileSync(extensionsPath, JSON.stringify(extensions, null, 2) + '\n', 'utf-8')
3571
- files.push('.vscode/extensions.json')
3572
-
3573
- return {
3574
- content: [
3575
- '── VS Code Integration ──',
3576
- '',
3577
- `Generated in ${vscodeDir}:`,
3578
- ...files.map((f) => ` ✅ ${f}`),
3579
- '',
3580
- 'What was configured:',
3581
- ' • Terminal profile "mipham" — opens Mipham Code in integrated terminal',
3582
- ' • Keyboard shortcut Cmd+Esc — focus terminal',
3583
- ' • Cmd+Shift+M — new terminal',
3584
- '',
3585
- 'To use:',
3586
- ' 1. Restart VS Code (or reload window: Cmd+Shift+P → Reload Window)',
3587
- ' 2. Open terminal: Ctrl+` or Cmd+Esc',
3588
- ' 3. Select "mipham" profile from the terminal dropdown',
3589
- '',
3590
- 'Install the VS Code extension for full integration:',
3591
- ` code --install-extension miphamai.mipham-code`,
3592
- '',
3593
- 'JetBrains: Settings → Tools → Terminal → Shell path → bun run mipham',
3594
- ].join('\n'),
3595
- }
3596
- }
3597
-
3598
- // ═══════════════════════════════════════════════════════════════
3599
- // Terminal Setup — shell integration guide
3600
- // ═══════════════════════════════════════════════════════════════
3601
-
3602
- const terminalSetupCmd: CommandHandler = async () => {
3603
- const { writeFileSync, appendFileSync, existsSync, mkdirSync, readFileSync } =
3604
- await import('node:fs')
3605
- const { join } = await import('node:path')
3606
- const { homedir } = await import('node:os')
3607
-
3608
- const home = homedir()
3609
- const lines: string[] = ['── Terminal Setup ──', '']
3610
-
3611
- // ── 1. Generate standalone shell setup script ──
3612
- const miphamDir = join(home, '.mipham')
3613
- mkdirSync(miphamDir, { recursive: true })
3614
-
3615
- const shellScript = join(miphamDir, 'shell-setup.sh')
3616
- const shellContent = [
3617
- '#!/bin/bash',
3618
- '# Mipham Code — Shell Integration',
3619
- '# Source this file in your shell profile:',
3620
- '# source ~/.mipham/shell-setup.sh',
3621
- '',
3622
- `export MIPHAM_HOME="${home}/.mipham"`,
3623
- '',
3624
- '# Alias: launch Mipham Code in current directory',
3625
- `alias mipham='cd $(pwd) && ${NPM_INSTALL_COMMAND} > /dev/null 2>&1; mipham'`,
3626
- '',
3627
- '# Or use the local development version:',
3628
- '# alias mipham="bun run /path/to/mipham-code/apps/cli/bin/mipham"',
3629
- '',
3630
- '# Auto-detect provider from config',
3631
- 'if [ -f ~/.mipham/config.yml ]; then',
3632
- ' export MIPHAM_PROVIDER=$(grep "defaultProvider:" ~/.mipham/config.yml | awk "{print \$2}")',
3633
- 'fi',
3634
- ].join('\n')
3635
- writeFileSync(shellScript, shellContent + '\n', 'utf-8')
3636
- lines.push(` ✅ Generated: ${shellScript}`)
3637
-
3638
- // ── 2. Append to shell profile ──
3639
- const shell = process.env.SHELL || '/bin/zsh'
3640
- const profileName = shell.includes('zsh') ? '.zshrc' : '.bashrc'
3641
- const profilePath = join(home, profileName)
3642
- const sourceLine = `\n# Mipham Code shell integration\n[ -f ~/.mipham/shell-setup.sh ] && source ~/.mipham/shell-setup.sh\n`
3643
-
3644
- try {
3645
- const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf-8') : ''
3646
- if (existing.includes('shell-setup.sh')) {
3647
- lines.push(` ⏭ ${profileName} already has Mipham Code integration`)
3648
- } else {
3649
- appendFileSync(profilePath, sourceLine, 'utf-8')
3650
- lines.push(` ✅ Added to ~/${profileName}`)
3651
- }
3652
- } catch {
3653
- lines.push(` ⚠️ Could not update ~/${profileName}. Add manually:`)
3654
- lines.push(` echo '${sourceLine.trim()}' >> ~/${profileName}`)
3655
- }
3656
-
3657
- // ── 3. Global install check ──
3658
- lines.push('')
3659
- lines.push('── Installation ──')
3660
- try {
3661
- const { execSync } = await import('node:child_process')
3662
- const miphamPath = execSync('which mipham 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim()
3663
- if (miphamPath) {
3664
- lines.push(` ✅ mipham found at: ${miphamPath}`)
3665
- const version = execSync('mipham --version 2>/dev/null || echo "unknown"', {
3666
- encoding: 'utf-8',
3667
- }).trim()
3668
- lines.push(` 📦 Version: ${version}`)
3669
- } else {
3670
- lines.push(` ⚠️ mipham not in PATH. Install globally:`)
3671
- lines.push(` ${NPM_INSTALL_COMMAND}`)
3672
- lines.push(` or: curl -fsSL https://mipham.ai/install.sh | bash`)
3673
- }
3674
- } catch {
3675
- lines.push(` 💡 Install: ${NPM_INSTALL_COMMAND}`)
3676
- }
3677
-
3678
- // ── 4. Verify ──
3679
- lines.push('')
3680
- lines.push('── Next Steps ──')
3681
- lines.push(' 1. Restart your terminal or run: source ~/.mipham/shell-setup.sh')
3682
- lines.push(' 2. Run: mipham --version')
3683
- lines.push(' 3. Start coding: cd your-project && mipham')
3684
- lines.push('')
3685
- lines.push(`Works with: Zsh, Bash. Shell: ${shell}`)
3686
-
3687
- return { content: lines.join('\n') }
3688
- }
3689
-
3690
- // ═══════════════════════════════════════════════════════════════
3691
- // Phase 4 — MCP Server Management
3692
- // ═══════════════════════════════════════════════════════════════
3693
-
3694
- const mcpCmd: CommandHandler = (ctx) => {
3695
- const configuredServers = ctx.config.skills?.mcpServers ?? []
3696
- const liveConnections = McpClient.getInstance().listConnections()
3697
-
3698
- const lines: string[] = ['── MCP Servers ──', '']
3699
-
3700
- if (configuredServers.length > 0) {
3701
- lines.push(`Configured servers (${configuredServers.length}):`)
3702
- lines.push('')
3703
- for (const s of configuredServers) {
3704
- const live = liveConnections.find((c) => c.config.name === s.name)
3705
- const statusIcon = live
3706
- ? live.status === 'connected'
3707
- ? '🟢'
3708
- : live.status === 'connecting'
3709
- ? '🟡'
3710
- : live.status === 'error'
3711
- ? '🔴'
3712
- : '⚪'
3713
- : '⚪'
3714
- const statusLabel = live ? live.status : 'not started'
3715
- const envKeys = s.env ? Object.keys(s.env).join(', ') : '(none)'
3716
-
3717
- lines.push(` ${statusIcon} ${s.name} [${statusLabel}]`)
3718
- lines.push(` Command: ${s.command} ${s.args.join(' ')}`)
3719
- lines.push(` Env vars: ${envKeys}`)
3720
- if (live?.tools && live.tools.length > 0) {
3721
- lines.push(` Tools: ${live.tools.map((t) => t.name).join(', ')}`)
3722
- }
3723
- if (live?.error) {
3724
- lines.push(` Error: ${live.error}`)
3725
- }
3726
- if (live?.serverInfo) {
3727
- lines.push(` Server: ${live.serverInfo.name} v${live.serverInfo.version}`)
3728
- }
3729
- lines.push('')
3730
- }
3731
- } else {
3732
- lines.push('No MCP servers configured.')
3733
- lines.push('')
3734
- lines.push('── Configuration ──')
3735
- lines.push('')
3736
- lines.push('Add MCP servers to .mipham/config.yml:')
3737
- lines.push('')
3738
- lines.push(' skills:')
3739
- lines.push(' mcpServers:')
3740
- lines.push(' - name: filesystem')
3741
- lines.push(' command: npx')
3742
- lines.push(' args: ["-y", "@anthropic/mcp-filesystem", "/path"]')
3743
- lines.push(' env:')
3744
- lines.push(' HOME: $HOME')
3745
- lines.push('')
3746
- lines.push(' - name: github')
3747
- lines.push(' command: npx')
3748
- lines.push(' args: ["-y", "@anthropic/mcp-github"]')
3749
- lines.push(' env:')
3750
- lines.push(' GITHUB_TOKEN: $GITHUB_TOKEN')
3751
- lines.push('')
3752
- lines.push('After configuring, restart Mipham Code to connect.')
3753
- lines.push('Use the MCP tool (Tool 16) to call server tools.')
3754
- }
3755
-
3756
- lines.push('')
3757
- lines.push('── Protocol ──')
3758
- lines.push('MCP stdio transport (JSON-RPC 2.0) — fully implemented.')
3759
- lines.push('Servers auto-connect on startup when configured.')
3760
- lines.push('')
3761
- lines.push('Learn more: https://modelcontextprotocol.io')
3762
-
3763
- return { content: lines.join('\n') }
3764
- }
3765
-
3766
- // ═══════════════════════════════════════════════════════════════
3767
- // Phase 4 — Login / API Key Management
3768
- // ═══════════════════════════════════════════════════════════════
3769
-
3770
- const loginCmd: CommandHandler = (ctx) => {
3771
- const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
3772
-
3773
- // Map provider IDs to their expected env var names
3774
- const providerEnvMap: Record<string, string> = {
3775
- anthropic: 'ANTHROPIC_API_KEY',
3776
- openai: 'OPENAI_API_KEY',
3777
- deepseek: 'DEEPSEEK_API_KEY',
3778
- google: 'GEMINI_API_KEY',
3779
- qwen: 'QWEN_API_KEY',
3780
- doubao: 'DOUBAO_API_KEY',
3781
- hunyuan: 'HUNYUAN_API_KEY',
2500
+ // Map provider IDs to their expected env var names
2501
+ const providerEnvMap: Record<string, string> = {
2502
+ anthropic: 'ANTHROPIC_API_KEY',
2503
+ openai: 'OPENAI_API_KEY',
2504
+ deepseek: 'DEEPSEEK_API_KEY',
2505
+ google: 'GEMINI_API_KEY',
2506
+ qwen: 'QWEN_API_KEY',
2507
+ doubao: 'DOUBAO_API_KEY',
2508
+ hunyuan: 'HUNYUAN_API_KEY',
3782
2509
  }
3783
2510
 
3784
2511
  const lines: string[] = [
@@ -4271,6 +2998,7 @@ const commandsListCmd: CommandHandler = () => {
4271
2998
  '/undo': 'History',
4272
2999
  '/copy': 'History',
4273
3000
  '/focus': 'History',
3001
+ '/ultracode': 'Workflow',
4274
3002
  '/pick': 'Model & Provider',
4275
3003
  '/model': 'Model & Provider',
4276
3004
  '/models': 'Model & Provider',
@@ -4305,6 +3033,7 @@ const commandsListCmd: CommandHandler = () => {
4305
3033
  '/pr-comments': 'Workflow',
4306
3034
  '/diff': 'Workflow',
4307
3035
  '/workflows': 'Workflow',
3036
+ '/deep-research': 'Workflow',
4308
3037
  '/loop': 'Workflow',
4309
3038
  '/init': 'Project',
4310
3039
  '/setup': 'Project',
@@ -4401,6 +3130,7 @@ registry.set('/rewind', rewindCmd)
4401
3130
  registry.set('/undo', undoCmd)
4402
3131
  registry.set('/copy', copyCmd)
4403
3132
  registry.set('/focus', focusCmd)
3133
+ registry.set('/ultracode', ultracodeCmd)
4404
3134
 
4405
3135
  // Model & Provider
4406
3136
  registry.set('/model', modelCmd)
@@ -4437,6 +3167,7 @@ registry.set('/loop', loopCmd)
4437
3167
  registry.set('/no-plan', noPlanCmd)
4438
3168
  registry.set('/workflow', workflowAutoCmd)
4439
3169
  registry.set('/workflows', workflowsCmd)
3170
+ registry.set('/deep-research', deepResearchCmd)
4440
3171
  registry.set('/review', reviewCmd)
4441
3172
  registry.set('/pr-comments', prCommentsCmd)
4442
3173
 
@@ -4540,6 +3271,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
4540
3271
  '/undo': 'Same as /rewind',
4541
3272
  '/copy': 'Copy last response to clipboard',
4542
3273
  '/focus': 'Toggle focus view',
3274
+ '/ultracode': 'Toggle multi-agent workflow orchestration mode',
4543
3275
  '/pick': 'Open model picker',
4544
3276
  '/model': 'Show current model',
4545
3277
  '/models': 'List all available models',
@@ -4572,6 +3304,8 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
4572
3304
  '/pr-comments': 'PR review summary',
4573
3305
  '/diff': 'Show git diff',
4574
3306
  '/workflows': 'List workflow scripts',
3307
+ '/deep-research':
3308
+ 'Deep research with multi-agent parallel search, verification, and cited synthesis',
4575
3309
  '/loop': 'Run prompt on interval',
4576
3310
  '/init': 'Initialize .mipham config',
4577
3311
  '/setup': 'Guided project setup wizard',
@@ -4629,22 +3363,6 @@ export function parseSlashCommand(input: string): { command: string; args: strin
4629
3363
  return { command, args }
4630
3364
  }
4631
3365
 
4632
- // ── Utility ──
4633
-
4634
- function stripIndent(strings: TemplateStringsArray, ...values: unknown[]): string {
4635
- let result = strings.reduce((acc, s, i) => acc + s + (values[i] ?? ''), '')
4636
- // Remove leading newline
4637
- result = result.replace(/^\n/, '')
4638
- // Find minimum indent
4639
- const match = result.match(/^( +)/m)
4640
- if (match?.[1]) {
4641
- const indent = match[1].length
4642
- result = result
4643
- .split('\n')
4644
- .map((line) => line.slice(indent))
4645
- .join('\n')
4646
- }
4647
- return result.trim()
4648
- }
3366
+ export { stripIndent } from './strip-indent.js'
4649
3367
 
4650
3368
  export { switchCmd as handleSwitch }