@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.
- package/package.json +2 -3
- package/src/agent/message-bus.ts +7 -1
- package/src/agent/sub-agent.ts +7 -1
- package/src/commands/environment.ts +270 -0
- package/src/commands/git.ts +152 -0
- package/src/commands/project.ts +1011 -0
- package/src/core/engine.ts +8 -7
- package/src/core/permission.ts +5 -0
- package/src/core/session-store.ts +4 -1
- package/src/index.tsx +6 -5
- package/src/skills/community-registry.json +72 -0
- package/src/skills/registry.ts +2 -75
- package/src/tools/exec/bash.ts +3 -3
- package/src/tools/exec/enter-worktree.ts +23 -1
- package/src/tools/exec/exit-worktree.ts +13 -0
- package/src/ui/agent-footer.tsx +89 -0
- package/src/ui/app.tsx +217 -96
- package/src/ui/chat.tsx +80 -30
- package/src/ui/commands.ts +202 -1484
- package/src/ui/input.tsx +61 -2
- package/src/ui/strip-indent.ts +19 -0
- package/src/workflow/runtime.ts +11 -1
package/src/ui/commands.ts
CHANGED
|
@@ -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
|
-
//
|
|
2339
|
+
// /deep-research <topic> — multi-agent deep research pipeline
|
|
2504
2340
|
// ═══════════════════════════════════════════════════════════════
|
|
2505
2341
|
|
|
2506
|
-
const
|
|
2507
|
-
const
|
|
2508
|
-
|
|
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:
|
|
2359
|
+
content: stripIndent`
|
|
2360
|
+
── Deep Research ──
|
|
2361
|
+
|
|
2362
|
+
Topic: "${topic}"
|
|
2512
2363
|
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
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
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2525
|
-
|
|
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
|
-
|
|
2528
|
-
|
|
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
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
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
|
-
|
|
2625
|
-
|
|
2417
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2418
|
+
// Theme — display theme toggle
|
|
2419
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2626
2420
|
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2643
|
-
|
|
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
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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(
|
|
2658
|
-
|
|
2659
|
-
|
|
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
|
-
|
|
2669
|
-
|
|
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
|
-
//
|
|
2494
|
+
// Phase 4 — Login / API Key Management
|
|
2696
2495
|
// ═══════════════════════════════════════════════════════════════
|
|
2697
2496
|
|
|
2698
|
-
const
|
|
2699
|
-
const
|
|
2497
|
+
const loginCmd: CommandHandler = (ctx) => {
|
|
2498
|
+
const activeProviders = ctx.config.providers.filter((p) => p.status === 'active')
|
|
2700
2499
|
|
|
2701
|
-
//
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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
|
-
|
|
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 }
|