@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/cli",
3
- "version": "0.8.2",
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.8.2"
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 generate 시 출력 경로 (기본: openapi.json)
58
- --verbose contract validate 상세 출력
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);