@mandujs/cli 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/commands/brain.ts +157 -0
- package/src/commands/doctor.ts +124 -0
- package/src/commands/watch.ts +80 -0
- package/src/main.ts +61 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mandujs/core": "0.
|
|
35
|
+
"@mandujs/core": "0.9.0"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"bun": ">=1.0.0"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu CLI - Brain Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for managing Brain (sLLM) configuration.
|
|
5
|
+
* - brain setup: Configure sLLM settings
|
|
6
|
+
* - brain status: Check current Brain status
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
initializeBrain,
|
|
11
|
+
getBrain,
|
|
12
|
+
detectEnvironment,
|
|
13
|
+
createOllamaAdapter,
|
|
14
|
+
DEFAULT_OLLAMA_CONFIG,
|
|
15
|
+
} from "../../../core/src/index";
|
|
16
|
+
|
|
17
|
+
export interface BrainSetupOptions {
|
|
18
|
+
/** Model name (default: llama3.2) */
|
|
19
|
+
model?: string;
|
|
20
|
+
/** Ollama server URL */
|
|
21
|
+
url?: string;
|
|
22
|
+
/** Skip model check */
|
|
23
|
+
skipCheck?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BrainStatusOptions {
|
|
27
|
+
/** Show verbose status */
|
|
28
|
+
verbose?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Setup Brain (sLLM configuration)
|
|
33
|
+
*/
|
|
34
|
+
export async function brainSetup(options: BrainSetupOptions = {}): Promise<boolean> {
|
|
35
|
+
const { model = DEFAULT_OLLAMA_CONFIG.model, url = DEFAULT_OLLAMA_CONFIG.baseUrl, skipCheck } = options;
|
|
36
|
+
|
|
37
|
+
console.log("🧠 Mandu Brain Setup");
|
|
38
|
+
console.log("─".repeat(40));
|
|
39
|
+
console.log();
|
|
40
|
+
|
|
41
|
+
// Create adapter with provided settings
|
|
42
|
+
const adapter = createOllamaAdapter({
|
|
43
|
+
model,
|
|
44
|
+
baseUrl: url,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(`📦 Model: ${model}`);
|
|
48
|
+
console.log(`🔗 URL: ${url}`);
|
|
49
|
+
console.log();
|
|
50
|
+
|
|
51
|
+
if (!skipCheck) {
|
|
52
|
+
console.log("🔍 Checking Ollama connection...");
|
|
53
|
+
|
|
54
|
+
const status = await adapter.checkStatus();
|
|
55
|
+
|
|
56
|
+
if (status.available) {
|
|
57
|
+
console.log(`✅ Ollama is running`);
|
|
58
|
+
console.log(`✅ Model '${status.model}' is available`);
|
|
59
|
+
|
|
60
|
+
if (status.error) {
|
|
61
|
+
console.log(`⚠️ ${status.error}`);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
console.log(`❌ ${status.error || "Ollama is not available"}`);
|
|
65
|
+
console.log();
|
|
66
|
+
console.log("💡 To fix this:");
|
|
67
|
+
console.log(" 1. Install Ollama: https://ollama.com");
|
|
68
|
+
console.log(" 2. Start Ollama: ollama serve");
|
|
69
|
+
console.log(` 3. Pull the model: ollama pull ${model}`);
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(" Or run with --skip-check to skip this verification.");
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log();
|
|
77
|
+
console.log("✅ Brain setup complete!");
|
|
78
|
+
console.log();
|
|
79
|
+
console.log("💡 Brain is now ready to assist with:");
|
|
80
|
+
console.log(" • mandu doctor - Guard failure analysis + patch suggestions");
|
|
81
|
+
console.log(" • mandu watch - Real-time file monitoring with warnings");
|
|
82
|
+
console.log();
|
|
83
|
+
console.log("ℹ️ Brain works without LLM too - LLM only improves suggestion quality.");
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check Brain status
|
|
90
|
+
*/
|
|
91
|
+
export async function brainStatus(options: BrainStatusOptions = {}): Promise<boolean> {
|
|
92
|
+
const { verbose } = options;
|
|
93
|
+
|
|
94
|
+
console.log("🧠 Mandu Brain Status");
|
|
95
|
+
console.log("─".repeat(40));
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
// Initialize Brain
|
|
99
|
+
await initializeBrain();
|
|
100
|
+
const brain = getBrain();
|
|
101
|
+
const status = await brain.getStatus();
|
|
102
|
+
|
|
103
|
+
// Environment info
|
|
104
|
+
console.log("📊 Environment");
|
|
105
|
+
console.log(` CI: ${status.environment.isCI ? `Yes (${status.environment.ciProvider})` : "No"}`);
|
|
106
|
+
console.log(` Development: ${status.environment.isDevelopment ? "Yes" : "No"}`);
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
// Brain status
|
|
110
|
+
const brainIcon = status.enabled ? "🟢" : "🔴";
|
|
111
|
+
console.log(`${brainIcon} Brain: ${status.enabled ? "Enabled" : "Disabled"}`);
|
|
112
|
+
|
|
113
|
+
// Adapter status
|
|
114
|
+
const adapterIcon = status.adapter.available ? "🟢" : "🔴";
|
|
115
|
+
console.log(
|
|
116
|
+
`${adapterIcon} LLM: ${status.adapter.available ? `Available (${status.adapter.model})` : "Not available"}`
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (status.adapter.error) {
|
|
120
|
+
console.log(` ⚠️ ${status.adapter.error}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Memory status
|
|
124
|
+
if (verbose) {
|
|
125
|
+
console.log();
|
|
126
|
+
console.log("📦 Memory");
|
|
127
|
+
console.log(` Has data: ${status.memory.hasData ? "Yes" : "No"}`);
|
|
128
|
+
console.log(` Session duration: ${status.memory.sessionDuration}s`);
|
|
129
|
+
console.log(` Idle time: ${status.memory.idleTime}s`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log();
|
|
133
|
+
|
|
134
|
+
// Recommendations
|
|
135
|
+
if (!status.enabled) {
|
|
136
|
+
console.log("💡 Brain is disabled because:");
|
|
137
|
+
|
|
138
|
+
if (status.environment.isCI) {
|
|
139
|
+
console.log(" • Running in CI environment (by design)");
|
|
140
|
+
} else if (!status.adapter.available) {
|
|
141
|
+
console.log(" • No LLM adapter available");
|
|
142
|
+
console.log();
|
|
143
|
+
console.log(" To enable Brain:");
|
|
144
|
+
console.log(" 1. Install Ollama: https://ollama.com");
|
|
145
|
+
console.log(" 2. Start Ollama: ollama serve");
|
|
146
|
+
console.log(" 3. Run: bunx mandu brain setup");
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
console.log("✅ Brain is ready!");
|
|
150
|
+
console.log();
|
|
151
|
+
console.log("💡 Available commands:");
|
|
152
|
+
console.log(" • bunx mandu doctor - Analyze Guard failures");
|
|
153
|
+
console.log(" • bunx mandu watch - Real-time file monitoring");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu CLI - Doctor Command
|
|
3
|
+
*
|
|
4
|
+
* Analyzes Guard failures and suggests patches.
|
|
5
|
+
* Works with or without LLM - template-based analysis is always available.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
loadManifest,
|
|
10
|
+
runGuardCheck,
|
|
11
|
+
analyzeViolations,
|
|
12
|
+
printDoctorReport,
|
|
13
|
+
generateMarkdownReport,
|
|
14
|
+
initializeBrain,
|
|
15
|
+
getBrain,
|
|
16
|
+
} from "../../../core/src/index";
|
|
17
|
+
import { resolveFromCwd, getRootDir } from "../util/fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs from "fs/promises";
|
|
20
|
+
|
|
21
|
+
export interface DoctorOptions {
|
|
22
|
+
/** Output format: console, json, or markdown */
|
|
23
|
+
format?: "console" | "json" | "markdown";
|
|
24
|
+
/** Whether to use LLM for enhanced analysis */
|
|
25
|
+
useLLM?: boolean;
|
|
26
|
+
/** Output file path (for json/markdown formats) */
|
|
27
|
+
output?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function doctor(options: DoctorOptions = {}): Promise<boolean> {
|
|
31
|
+
const { format = "console", useLLM = true, output } = options;
|
|
32
|
+
|
|
33
|
+
const specPath = resolveFromCwd("spec/routes.manifest.json");
|
|
34
|
+
const rootDir = getRootDir();
|
|
35
|
+
|
|
36
|
+
console.log(`🩺 Mandu Doctor`);
|
|
37
|
+
console.log(`📄 Spec 파일: ${specPath}`);
|
|
38
|
+
|
|
39
|
+
// Initialize Brain
|
|
40
|
+
const brainEnabled = await initializeBrain();
|
|
41
|
+
const brain = getBrain();
|
|
42
|
+
const llmAvailable = await brain.isLLMAvailable();
|
|
43
|
+
|
|
44
|
+
if (brainEnabled) {
|
|
45
|
+
console.log(`🧠 Brain: ${llmAvailable ? "LLM 활성화" : "템플릿 모드"}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`🧠 Brain: 비활성화`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
|
|
51
|
+
// Load manifest
|
|
52
|
+
const result = await loadManifest(specPath);
|
|
53
|
+
|
|
54
|
+
if (!result.success || !result.data) {
|
|
55
|
+
console.error("❌ Spec 로드 실패:");
|
|
56
|
+
result.errors?.forEach((e) => console.error(` - ${e}`));
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`✅ Spec 로드 완료`);
|
|
61
|
+
console.log(`🔍 Guard 검사 중...\n`);
|
|
62
|
+
|
|
63
|
+
// Run guard check
|
|
64
|
+
const checkResult = await runGuardCheck(result.data, rootDir);
|
|
65
|
+
|
|
66
|
+
if (checkResult.passed) {
|
|
67
|
+
console.log(`✅ Guard 통과 - 위반 사항이 없습니다.`);
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(`💡 다음 단계: bunx mandu dev`);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(
|
|
74
|
+
`⚠️ ${checkResult.violations.length}개 위반 발견 - 분석 중...\n`
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Analyze violations
|
|
78
|
+
const analysis = await analyzeViolations(checkResult.violations, {
|
|
79
|
+
useLLM: useLLM && brainEnabled && llmAvailable,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Output based on format
|
|
83
|
+
switch (format) {
|
|
84
|
+
case "console":
|
|
85
|
+
printDoctorReport(analysis);
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case "json": {
|
|
89
|
+
const json = JSON.stringify(
|
|
90
|
+
{
|
|
91
|
+
summary: analysis.summary,
|
|
92
|
+
violations: analysis.violations,
|
|
93
|
+
patches: analysis.patches,
|
|
94
|
+
nextCommand: analysis.nextCommand,
|
|
95
|
+
llmAssisted: analysis.llmAssisted,
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (output) {
|
|
102
|
+
await fs.writeFile(output, json, "utf-8");
|
|
103
|
+
console.log(`📄 Report saved to: ${output}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(json);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "markdown": {
|
|
111
|
+
const md = generateMarkdownReport(analysis);
|
|
112
|
+
|
|
113
|
+
if (output) {
|
|
114
|
+
await fs.writeFile(output, md, "utf-8");
|
|
115
|
+
console.log(`📄 Report saved to: ${output}`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(md);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu CLI - Watch Command
|
|
3
|
+
*
|
|
4
|
+
* Real-time file watching with architecture rule warnings.
|
|
5
|
+
* Warnings only - never blocks operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
startWatcher,
|
|
10
|
+
stopWatcher,
|
|
11
|
+
getWatcher,
|
|
12
|
+
printWatchStart,
|
|
13
|
+
printWatchStop,
|
|
14
|
+
printStatus,
|
|
15
|
+
createConsoleHandler,
|
|
16
|
+
} from "../../../core/src/index";
|
|
17
|
+
import { getRootDir } from "../util/fs";
|
|
18
|
+
|
|
19
|
+
export interface WatchOptions {
|
|
20
|
+
/** Extra commands to run on violations */
|
|
21
|
+
extraCommands?: string[];
|
|
22
|
+
/** Debounce delay in ms */
|
|
23
|
+
debounce?: number;
|
|
24
|
+
/** Show status only (don't start watching) */
|
|
25
|
+
status?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function watch(options: WatchOptions = {}): Promise<boolean> {
|
|
29
|
+
const { extraCommands, debounce, status } = options;
|
|
30
|
+
|
|
31
|
+
const rootDir = getRootDir();
|
|
32
|
+
|
|
33
|
+
// Status only mode
|
|
34
|
+
if (status) {
|
|
35
|
+
const watcher = getWatcher();
|
|
36
|
+
if (watcher) {
|
|
37
|
+
printStatus(watcher.getStatus());
|
|
38
|
+
} else {
|
|
39
|
+
console.log("👁️ Watch is not running");
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Start watching
|
|
45
|
+
printWatchStart(rootDir);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const watcher = await startWatcher({
|
|
49
|
+
rootDir,
|
|
50
|
+
extraCommands,
|
|
51
|
+
debounceMs: debounce,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Add console handler
|
|
55
|
+
watcher.onWarning(createConsoleHandler());
|
|
56
|
+
|
|
57
|
+
// Handle shutdown signals
|
|
58
|
+
const shutdown = () => {
|
|
59
|
+
printWatchStop();
|
|
60
|
+
stopWatcher();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
process.on("SIGINT", shutdown);
|
|
65
|
+
process.on("SIGTERM", shutdown);
|
|
66
|
+
|
|
67
|
+
// Keep the process running
|
|
68
|
+
await new Promise(() => {
|
|
69
|
+
// Never resolves - runs until interrupted
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return true;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(
|
|
75
|
+
"❌ Watch failed:",
|
|
76
|
+
error instanceof Error ? error.message : error
|
|
77
|
+
);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
changeList,
|
|
17
17
|
changePrune,
|
|
18
18
|
} from "./commands/change";
|
|
19
|
+
import { doctor } from "./commands/doctor";
|
|
20
|
+
import { watch } from "./commands/watch";
|
|
21
|
+
import { brainSetup, brainStatus } from "./commands/brain";
|
|
19
22
|
|
|
20
23
|
const HELP_TEXT = `
|
|
21
24
|
🥟 Mandu CLI - Agent-Native Fullstack Framework
|
|
@@ -30,6 +33,12 @@ Commands:
|
|
|
30
33
|
build 클라이언트 번들 빌드 (Hydration)
|
|
31
34
|
dev 개발 서버 실행
|
|
32
35
|
|
|
36
|
+
doctor Guard 실패 분석 + 패치 제안 (Brain)
|
|
37
|
+
watch 실시간 파일 감시 - 경고만 (Brain)
|
|
38
|
+
|
|
39
|
+
brain setup sLLM 설정 (선택)
|
|
40
|
+
brain status Brain 상태 확인
|
|
41
|
+
|
|
33
42
|
contract create <routeId> 라우트에 대한 Contract 생성
|
|
34
43
|
contract validate Contract-Slot 일관성 검증
|
|
35
44
|
|
|
@@ -54,8 +63,12 @@ Options:
|
|
|
54
63
|
--message <msg> change begin 시 설명 메시지
|
|
55
64
|
--id <id> change rollback 시 특정 변경 ID
|
|
56
65
|
--keep <n> change prune 시 유지할 스냅샷 수 (기본: 5)
|
|
57
|
-
--output <path> openapi
|
|
58
|
-
--
|
|
66
|
+
--output <path> openapi/doctor 출력 경로
|
|
67
|
+
--format <fmt> doctor 출력 형식: console, json, markdown (기본: console)
|
|
68
|
+
--no-llm doctor에서 LLM 사용 안 함 (템플릿 모드)
|
|
69
|
+
--model <name> brain setup 시 모델 이름 (기본: llama3.2)
|
|
70
|
+
--url <url> brain setup 시 Ollama URL
|
|
71
|
+
--verbose 상세 출력
|
|
59
72
|
--help, -h 도움말 표시
|
|
60
73
|
|
|
61
74
|
Examples:
|
|
@@ -66,6 +79,11 @@ Examples:
|
|
|
66
79
|
bunx mandu build --minify
|
|
67
80
|
bunx mandu build --watch
|
|
68
81
|
bunx mandu dev --port 3000
|
|
82
|
+
bunx mandu doctor
|
|
83
|
+
bunx mandu doctor --format markdown --output report.md
|
|
84
|
+
bunx mandu watch
|
|
85
|
+
bunx mandu brain setup --model codellama
|
|
86
|
+
bunx mandu brain status
|
|
69
87
|
bunx mandu contract create users
|
|
70
88
|
bunx mandu contract validate --verbose
|
|
71
89
|
bunx mandu openapi generate --output docs/api.json
|
|
@@ -79,6 +97,9 @@ Workflow:
|
|
|
79
97
|
|
|
80
98
|
Contract-first Workflow:
|
|
81
99
|
1. contract create → 2. Edit contract → 3. generate → 4. Edit slot → 5. contract validate
|
|
100
|
+
|
|
101
|
+
Brain (sLLM) Workflow:
|
|
102
|
+
1. brain setup → 2. doctor (분석) → 3. watch (감시)
|
|
82
103
|
`;
|
|
83
104
|
|
|
84
105
|
function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
|
|
@@ -224,6 +245,44 @@ async function main(): Promise<void> {
|
|
|
224
245
|
break;
|
|
225
246
|
}
|
|
226
247
|
|
|
248
|
+
case "doctor":
|
|
249
|
+
success = await doctor({
|
|
250
|
+
format: (options.format as "console" | "json" | "markdown") || "console",
|
|
251
|
+
useLLM: options["no-llm"] !== "true",
|
|
252
|
+
output: options.output,
|
|
253
|
+
});
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
case "watch":
|
|
257
|
+
success = await watch({
|
|
258
|
+
status: options.status === "true",
|
|
259
|
+
debounce: options.debounce ? Number(options.debounce) : undefined,
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
case "brain": {
|
|
264
|
+
const subCommand = args[1];
|
|
265
|
+
switch (subCommand) {
|
|
266
|
+
case "setup":
|
|
267
|
+
success = await brainSetup({
|
|
268
|
+
model: options.model,
|
|
269
|
+
url: options.url,
|
|
270
|
+
skipCheck: options["skip-check"] === "true",
|
|
271
|
+
});
|
|
272
|
+
break;
|
|
273
|
+
case "status":
|
|
274
|
+
success = await brainStatus({
|
|
275
|
+
verbose: options.verbose === "true",
|
|
276
|
+
});
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
console.error(`❌ Unknown brain subcommand: ${subCommand}`);
|
|
280
|
+
console.log("\nUsage: bunx mandu brain <setup|status>");
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
|
|
227
286
|
default:
|
|
228
287
|
console.error(`❌ Unknown command: ${command}`);
|
|
229
288
|
console.log(HELP_TEXT);
|