@mandujs/cli 0.9.21 β†’ 0.9.23

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.
Files changed (40) hide show
  1. package/README.ko.md +57 -4
  2. package/README.md +62 -15
  3. package/package.json +2 -2
  4. package/src/commands/check.ts +41 -5
  5. package/src/commands/contract.ts +135 -9
  6. package/src/commands/dev.ts +155 -95
  7. package/src/commands/guard-arch.ts +39 -9
  8. package/src/commands/guard-check.ts +3 -3
  9. package/src/commands/init.ts +264 -9
  10. package/src/commands/monitor.ts +301 -0
  11. package/src/main.ts +421 -361
  12. package/templates/default/app/globals.css +37 -0
  13. package/templates/default/app/layout.tsx +27 -0
  14. package/templates/default/app/page.tsx +27 -49
  15. package/templates/default/package.json +20 -11
  16. package/templates/default/postcss.config.js +6 -0
  17. package/templates/default/src/client/app/index.ts +1 -0
  18. package/templates/default/src/client/entities/index.ts +1 -0
  19. package/templates/default/src/client/features/index.ts +1 -0
  20. package/templates/default/src/client/pages/index.ts +1 -0
  21. package/templates/default/src/client/shared/index.ts +1 -0
  22. package/templates/default/src/client/shared/lib/utils.ts +16 -0
  23. package/templates/default/src/client/shared/ui/button.tsx +57 -0
  24. package/templates/default/src/client/shared/ui/card.tsx +78 -0
  25. package/templates/default/src/client/shared/ui/index.ts +21 -0
  26. package/templates/default/src/client/shared/ui/input.tsx +24 -0
  27. package/templates/default/src/client/widgets/index.ts +1 -0
  28. package/templates/default/src/server/api/index.ts +1 -0
  29. package/templates/default/src/server/application/index.ts +1 -0
  30. package/templates/default/src/server/core/index.ts +1 -0
  31. package/templates/default/src/server/domain/index.ts +1 -0
  32. package/templates/default/src/server/infra/index.ts +1 -0
  33. package/templates/default/src/shared/contracts/index.ts +1 -0
  34. package/templates/default/src/shared/env/index.ts +1 -0
  35. package/templates/default/src/shared/schema/index.ts +1 -0
  36. package/templates/default/src/shared/types/index.ts +1 -0
  37. package/templates/default/src/shared/utils/client/index.ts +1 -0
  38. package/templates/default/src/shared/utils/server/index.ts +1 -0
  39. package/templates/default/tailwind.config.ts +64 -0
  40. package/templates/default/tsconfig.json +14 -3
package/src/main.ts CHANGED
@@ -1,72 +1,81 @@
1
- #!/usr/bin/env bun
2
-
1
+ #!/usr/bin/env bun
2
+
3
3
  import { specUpsert } from "./commands/spec-upsert";
4
4
  import { generateApply } from "./commands/generate-apply";
5
5
  import { guardCheck } from "./commands/guard-check";
6
6
  import { guardArch } from "./commands/guard-arch";
7
7
  import { check } from "./commands/check";
8
8
  import { dev } from "./commands/dev";
9
- import { init } from "./commands/init";
10
- import { build } from "./commands/build";
11
- import { contractCreate, contractValidate } from "./commands/contract";
12
- import { openAPIGenerate, openAPIServe } from "./commands/openapi";
13
- import {
14
- changeBegin,
15
- changeCommit,
16
- changeRollback,
17
- changeStatus,
18
- changeList,
19
- changePrune,
20
- } from "./commands/change";
21
- import { doctor } from "./commands/doctor";
22
- import { watch } from "./commands/watch";
23
- import { brainSetup, brainStatus } from "./commands/brain";
24
- import { routesGenerate, routesList, routesWatch } from "./commands/routes";
25
-
26
- const HELP_TEXT = `
27
- πŸ₯Ÿ Mandu CLI - Agent-Native Fullstack Framework
28
-
29
- Usage: bunx mandu <command> [options]
30
-
9
+ import { init } from "./commands/init";
10
+ import { build } from "./commands/build";
11
+ import { contractCreate, contractValidate, contractBuild, contractDiff } from "./commands/contract";
12
+ import { openAPIGenerate, openAPIServe } from "./commands/openapi";
13
+ import {
14
+ changeBegin,
15
+ changeCommit,
16
+ changeRollback,
17
+ changeStatus,
18
+ changeList,
19
+ changePrune,
20
+ } from "./commands/change";
21
+ import { doctor } from "./commands/doctor";
22
+ import { watch } from "./commands/watch";
23
+ import { brainSetup, brainStatus } from "./commands/brain";
24
+ import { routesGenerate, routesList, routesWatch } from "./commands/routes";
25
+ import { monitor } from "./commands/monitor";
26
+
27
+ const HELP_TEXT = `
28
+ πŸ₯Ÿ Mandu CLI - Agent-Native Fullstack Framework
29
+
30
+ Usage: bunx mandu <command> [options]
31
+
31
32
  Commands:
32
- init μƒˆ ν”„λ‘œμ νŠΈ 생성
33
+ init μƒˆ ν”„λ‘œμ νŠΈ 생성 (Tailwind + shadcn/ui κΈ°λ³Έ 포함)
33
34
  check FS Routes + Guard 톡합 검사
34
35
  routes generate FS Routes μŠ€μΊ” 및 λ§€λ‹ˆνŽ˜μŠ€νŠΈ 생성
35
36
  routes list ν˜„μž¬ 라우트 λͺ©λ‘ 좜λ ₯
36
37
  routes watch μ‹€μ‹œκ°„ 라우트 κ°μ‹œ
37
38
  dev 개발 μ„œλ²„ μ‹€ν–‰ (FS Routes + Guard κΈ°λ³Έ)
38
39
  dev --no-guard Guard κ°μ‹œ λΉ„ν™œμ„±ν™”
39
- build ν΄λΌμ΄μ–ΈνŠΈ λ²ˆλ“€ λΉŒλ“œ (Hydration)
40
- guard Guard κ·œμΉ™ 검사 (λ ˆκ±°μ‹œ Spec 기반)
41
- guard arch μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (FSD/Clean/Hexagonal)
42
- guard arch --watch μ‹€μ‹œκ°„ μ•„ν‚€ν…μ²˜ κ°μ‹œ
43
- guard arch --list-presets μ‚¬μš© κ°€λŠ₯ν•œ 프리셋 λͺ©λ‘
44
- guard arch --output report.md 리포트 파일 생성
45
- guard arch --show-trend νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
46
- spec-upsert Spec 파일 검증 및 lock κ°±μ‹  (λ ˆκ±°μ‹œ)
47
- generate Specμ—μ„œ μ½”λ“œ 생성 (λ ˆκ±°μ‹œ)
48
-
49
- doctor Guard μ‹€νŒ¨ 뢄석 + 패치 μ œμ•ˆ (Brain)
50
- watch μ‹€μ‹œκ°„ 파일 κ°μ‹œ - 경고만 (Brain)
51
-
52
- brain setup sLLM μ„€μ • (선택)
53
- brain status Brain μƒνƒœ 확인
54
-
55
- contract create <routeId> λΌμš°νŠΈμ— λŒ€ν•œ Contract 생성
56
- contract validate Contract-Slot 일관성 검증
57
-
58
- openapi generate OpenAPI 3.0 μŠ€νŽ™ 생성
59
- openapi serve Swagger UI 둜컬 μ„œλ²„ μ‹€ν–‰
60
-
61
- change begin λ³€κ²½ νŠΈλžœμž­μ…˜ μ‹œμž‘ (μŠ€λƒ…μƒ· 생성)
62
- change commit λ³€κ²½ ν™•μ •
63
- change rollback μŠ€λƒ…μƒ·μœΌλ‘œ 볡원
64
- change status ν˜„μž¬ νŠΈλžœμž­μ…˜ μƒνƒœ
65
- change list λ³€κ²½ 이λ ₯ 쑰회
66
- change prune 였래된 μŠ€λƒ…μƒ· 정리
67
-
40
+ build ν΄λΌμ΄μ–ΈνŠΈ λ²ˆλ“€ λΉŒλ“œ (Hydration)
41
+ guard μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (κΈ°λ³Έ)
42
+ guard arch μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (FSD/Clean/Hexagonal)
43
+ guard legacy λ ˆκ±°μ‹œ Spec Guard 검사
44
+ guard arch --watch μ‹€μ‹œκ°„ μ•„ν‚€ν…μ²˜ κ°μ‹œ
45
+ guard arch --list-presets μ‚¬μš© κ°€λŠ₯ν•œ 프리셋 λͺ©λ‘
46
+ guard arch --output report.md 리포트 파일 생성
47
+ guard arch --show-trend νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
48
+ spec-upsert Spec 파일 검증 및 lock κ°±μ‹  (λ ˆκ±°μ‹œ)
49
+ generate Specμ—μ„œ μ½”λ“œ 생성 (λ ˆκ±°μ‹œ)
50
+
51
+ doctor Guard μ‹€νŒ¨ 뢄석 + 패치 μ œμ•ˆ (Brain)
52
+ watch μ‹€μ‹œκ°„ 파일 κ°μ‹œ - 경고만 (Brain)
53
+ monitor MCP Activity Monitor 둜그 슀트림
54
+
55
+ brain setup sLLM μ„€μ • (선택)
56
+ brain status Brain μƒνƒœ 확인
57
+
58
+ contract create <routeId> λΌμš°νŠΈμ— λŒ€ν•œ Contract 생성
59
+ contract validate Contract-Slot 일관성 검증
60
+ contract build Contract λ ˆμ§€μŠ€νŠΈλ¦¬ 생성
61
+ contract diff Contract 변경사항 비ꡐ
62
+
63
+ openapi generate OpenAPI 3.0 μŠ€νŽ™ 생성
64
+ openapi serve Swagger UI 둜컬 μ„œλ²„ μ‹€ν–‰
65
+
66
+ change begin λ³€κ²½ νŠΈλžœμž­μ…˜ μ‹œμž‘ (μŠ€λƒ…μƒ· 생성)
67
+ change commit λ³€κ²½ ν™•μ •
68
+ change rollback μŠ€λƒ…μƒ·μœΌλ‘œ 볡원
69
+ change status ν˜„μž¬ νŠΈλžœμž­μ…˜ μƒνƒœ
70
+ change list λ³€κ²½ 이λ ₯ 쑰회
71
+ change prune 였래된 μŠ€λƒ…μƒ· 정리
72
+
68
73
  Options:
69
74
  --name <name> init μ‹œ ν”„λ‘œμ νŠΈ 이름 (κΈ°λ³Έ: my-mandu-app)
75
+ --css <framework> init μ‹œ CSS ν”„λ ˆμž„μ›Œν¬: tailwind, panda, none (κΈ°λ³Έ: tailwind)
76
+ --ui <library> init μ‹œ UI 라이브러리: shadcn, ark, none (κΈ°λ³Έ: shadcn)
77
+ --theme init μ‹œ 닀크λͺ¨λ“œ ν…Œλ§ˆ μ‹œμŠ€ν…œ μΆ”κ°€
78
+ --minimal init μ‹œ CSS/UI 없이 μ΅œμ†Œ ν…œν”Œλ¦Ώ 생성 (--css none --ui none)
70
79
  --file <path> spec-upsert μ‹œ μ‚¬μš©ν•  spec 파일 경둜
71
80
  --port <port> dev/openapi serve 포트 (κΈ°λ³Έ: 3000/8080)
72
81
  --guard dev μ‹œ Architecture Guard μ‹€μ‹œκ°„ κ°μ‹œ ν™œμ„±ν™” (κΈ°λ³Έ: ON)
@@ -76,125 +85,144 @@ Options:
76
85
  --legacy FS Routes λΉ„ν™œμ„±ν™” (λ ˆκ±°μ‹œ λͺ¨λ“œ)
77
86
  --no-auto-correct guard μ‹œ μžλ™ μˆ˜μ • λΉ„ν™œμ„±ν™”
78
87
  --preset <name> guard/check 프리셋 (κΈ°λ³Έ: mandu) - fsd, clean, hexagonal, atomic 선택 κ°€λŠ₯
79
- --ci guard/check CI λͺ¨λ“œ (μ—λŸ¬ μ‹œ exit 1)
88
+ --ci guard/check CI λͺ¨λ“œ (warning도 μ‹€νŒ¨ 처리)
80
89
  --quiet guard/check μš”μ•½λ§Œ 좜λ ₯
81
- --report-format guard arch 리포트 ν˜•μ‹: json, markdown, html
82
- --save-stats guard arch 톡계 μ €μž₯ (νŠΈλ Œλ“œ λΆ„μ„μš©)
83
- --show-trend guard arch νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
84
- --minify build μ‹œ μ½”λ“œ μ••μΆ•
85
- --sourcemap build μ‹œ μ†ŒμŠ€λ§΅ 생성
86
- --watch build/guard arch 파일 κ°μ‹œ λͺ¨λ“œ
87
- --message <msg> change begin μ‹œ μ„€λͺ… λ©”μ‹œμ§€
88
- --id <id> change rollback μ‹œ νŠΉμ • λ³€κ²½ ID
89
- --keep <n> change prune μ‹œ μœ μ§€ν•  μŠ€λƒ…μƒ· 수 (κΈ°λ³Έ: 5)
90
- --output <path> openapi/doctor 좜λ ₯ 경둜
90
+ --report-format guard arch 리포트 ν˜•μ‹: json, markdown, html
91
+ --save-stats guard arch 톡계 μ €μž₯ (νŠΈλ Œλ“œ λΆ„μ„μš©)
92
+ --show-trend guard arch νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
93
+ --minify build μ‹œ μ½”λ“œ μ••μΆ•
94
+ --sourcemap build μ‹œ μ†ŒμŠ€λ§΅ 생성
95
+ --watch build/guard arch 파일 κ°μ‹œ λͺ¨λ“œ
96
+ --summary monitor μš”μ•½ 좜λ ₯ (JSON λ‘œκ·Έμ—μ„œλ§Œ)
97
+ --since <duration> monitor μš”μ•½ κΈ°κ°„ (예: 5m, 30s, 1h)
98
+ --follow <bool> monitor follow λͺ¨λ“œ (κΈ°λ³Έ: true)
99
+ --file <path> monitor 둜그 파일 직접 μ§€μ •
100
+ --message <msg> change begin μ‹œ μ„€λͺ… λ©”μ‹œμ§€
101
+ --id <id> change rollback μ‹œ νŠΉμ • λ³€κ²½ ID
102
+ --keep <n> change prune μ‹œ μœ μ§€ν•  μŠ€λƒ…μƒ· 수 (κΈ°λ³Έ: 5)
103
+ --output <path> openapi/doctor 좜λ ₯ 경둜
104
+ --from <path> contract diff κΈ°μ€€ λ ˆμ§€μŠ€νŠΈλ¦¬ 경둜
105
+ --to <path> contract diff λŒ€μƒ λ ˆμ§€μŠ€νŠΈλ¦¬ 경둜
106
+ --json contract diff κ²°κ³Ό JSON 좜λ ₯
91
107
  --format <fmt> guard/check 좜λ ₯ ν˜•μ‹: console, json, agent (κΈ°λ³Έ: μžλ™)
92
108
  --format <fmt> doctor 좜λ ₯ ν˜•μ‹: console, json, markdown (κΈ°λ³Έ: console)
93
- --no-llm doctorμ—μ„œ LLM μ‚¬μš© μ•ˆ 함 (ν…œν”Œλ¦Ώ λͺ¨λ“œ)
94
- --model <name> brain setup μ‹œ λͺ¨λΈ 이름 (κΈ°λ³Έ: llama3.2)
95
- --url <url> brain setup μ‹œ Ollama URL
96
- --verbose 상세 좜λ ₯
97
- --help, -h 도움말 ν‘œμ‹œ
98
-
109
+ --no-llm doctorμ—μ„œ LLM μ‚¬μš© μ•ˆ 함 (ν…œν”Œλ¦Ώ λͺ¨λ“œ)
110
+ --model <name> brain setup μ‹œ λͺ¨λΈ 이름 (κΈ°λ³Έ: llama3.2)
111
+ --url <url> brain setup μ‹œ Ollama URL
112
+ --verbose 상세 좜λ ₯
113
+ --help, -h 도움말 ν‘œμ‹œ
114
+
99
115
  Examples:
100
- bunx mandu init --name my-app
116
+ bunx mandu init --name my-app # Tailwind + shadcn/ui κΈ°λ³Έ
117
+ bunx mandu init my-app --minimal # CSS/UI 없이 μ΅œμ†Œ ν…œν”Œλ¦Ώ
118
+ bunx mandu init my-app --theme # 닀크λͺ¨λ“œ ν…Œλ§ˆ 포함
119
+ bunx mandu init my-app --ui none # UI 라이브러리 없이
101
120
  bunx mandu check
102
121
  bunx mandu routes list
103
122
  bunx mandu routes generate
104
123
  bunx mandu dev --port 3000
105
124
  bunx mandu dev --no-guard
106
125
  bunx mandu build --minify
107
- bunx mandu guard
108
- bunx mandu guard arch --preset fsd
109
- bunx mandu guard arch --watch
110
- bunx mandu guard arch --ci --format json
111
- bunx mandu doctor
112
- bunx mandu brain setup --model codellama
113
- bunx mandu contract create users
114
- bunx mandu openapi generate --output docs/api.json
115
- bunx mandu change begin --message "Add new route"
116
-
117
- FS Routes Workflow (ꢌμž₯):
118
- 1. init β†’ 2. app/ 폴더에 page.tsx 생성 β†’ 3. dev β†’ 4. build
119
-
120
- Legacy Workflow:
121
- 1. init β†’ 2. spec-upsert β†’ 3. generate β†’ 4. build β†’ 5. guard β†’ 6. dev
122
-
123
- Contract-first Workflow:
124
- 1. contract create β†’ 2. Edit contract β†’ 3. generate β†’ 4. Edit slot β†’ 5. contract validate
125
-
126
- Brain (sLLM) Workflow:
127
- 1. brain setup β†’ 2. doctor (뢄석) β†’ 3. watch (κ°μ‹œ)
128
- `;
129
-
130
- function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
131
- const command = args[0] || "";
132
- const options: Record<string, string> = {};
133
-
134
- for (let i = 1; i < args.length; i++) {
135
- const arg = args[i];
136
- if (arg.startsWith("--")) {
137
- const key = arg.slice(2);
138
- const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
139
- options[key] = value;
140
- } else if (arg === "-h") {
141
- options["help"] = "true";
142
- } else if (!options._positional) {
143
- // First non-flag argument after command is positional (e.g., project name)
144
- options._positional = arg;
145
- }
146
- }
147
-
148
- return { command, options };
149
- }
150
-
151
- /**
152
- * 포트 μ˜΅μ…˜ μ•ˆμ „ν•˜κ²Œ νŒŒμ‹±
153
- * - μˆ«μžκ°€ μ•„λ‹ˆλ©΄ undefined λ°˜ν™˜ (κΈ°λ³Έκ°’ μ‚¬μš©)
154
- * - 유효 λ²”μœ„: 1-65535
155
- */
156
- function parsePort(value: string | undefined, optionName = "port"): number | undefined {
157
- if (!value || value === "true") {
158
- return undefined; // κΈ°λ³Έκ°’ μ‚¬μš©
159
- }
160
-
161
- const port = Number(value);
162
-
163
- if (Number.isNaN(port)) {
164
- console.warn(`⚠️ Invalid --${optionName} value: "${value}" (using default)`);
165
- return undefined;
166
- }
167
-
168
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
169
- console.warn(`⚠️ Invalid --${optionName} range: ${port} (must be 1-65535, using default)`);
170
- return undefined;
171
- }
172
-
173
- return port;
174
- }
175
-
176
- async function main(): Promise<void> {
177
- const args = process.argv.slice(2);
178
- const { command, options } = parseArgs(args);
179
-
180
- if (options.help || command === "help" || !command) {
181
- console.log(HELP_TEXT);
182
- process.exit(0);
183
- }
184
-
185
- let success = true;
186
-
187
- switch (command) {
188
- case "init":
189
- success = await init({
190
- name: options.name || options._positional
191
- });
192
- break;
193
-
194
- case "spec-upsert":
195
- success = await specUpsert({ file: options.file });
196
- break;
197
-
126
+ bunx mandu guard
127
+ bunx mandu guard arch --preset fsd
128
+ bunx mandu guard arch --watch
129
+ bunx mandu guard arch --ci --format json
130
+ bunx mandu guard legacy
131
+ bunx mandu monitor
132
+ bunx mandu monitor --summary --since 5m
133
+ bunx mandu doctor
134
+ bunx mandu brain setup --model codellama
135
+ bunx mandu contract create users
136
+ bunx mandu contract build
137
+ bunx mandu contract diff
138
+ bunx mandu openapi generate --output docs/api.json
139
+ bunx mandu change begin --message "Add new route"
140
+
141
+ FS Routes Workflow (ꢌμž₯):
142
+ 1. init β†’ 2. app/ 폴더에 page.tsx 생성 β†’ 3. dev β†’ 4. build
143
+
144
+ Legacy Workflow:
145
+ 1. init β†’ 2. spec-upsert β†’ 3. generate β†’ 4. build β†’ 5. guard β†’ 6. dev
146
+
147
+ Contract-first Workflow:
148
+ 1. contract create β†’ 2. Edit contract β†’ 3. generate β†’ 4. Edit slot β†’ 5. contract validate
149
+
150
+ Brain (sLLM) Workflow:
151
+ 1. brain setup β†’ 2. doctor (뢄석) β†’ 3. watch (κ°μ‹œ)
152
+ `;
153
+
154
+ function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
155
+ const command = args[0] || "";
156
+ const options: Record<string, string> = {};
157
+
158
+ for (let i = 1; i < args.length; i++) {
159
+ const arg = args[i];
160
+ if (arg.startsWith("--")) {
161
+ const key = arg.slice(2);
162
+ const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
163
+ options[key] = value;
164
+ } else if (arg === "-h") {
165
+ options["help"] = "true";
166
+ } else if (!options._positional) {
167
+ // First non-flag argument after command is positional (e.g., project name)
168
+ options._positional = arg;
169
+ }
170
+ }
171
+
172
+ return { command, options };
173
+ }
174
+
175
+ /**
176
+ * 포트 μ˜΅μ…˜ μ•ˆμ „ν•˜κ²Œ νŒŒμ‹±
177
+ * - μˆ«μžκ°€ μ•„λ‹ˆλ©΄ undefined λ°˜ν™˜ (κΈ°λ³Έκ°’ μ‚¬μš©)
178
+ * - 유효 λ²”μœ„: 1-65535
179
+ */
180
+ function parsePort(value: string | undefined, optionName = "port"): number | undefined {
181
+ if (!value || value === "true") {
182
+ return undefined; // κΈ°λ³Έκ°’ μ‚¬μš©
183
+ }
184
+
185
+ const port = Number(value);
186
+
187
+ if (Number.isNaN(port)) {
188
+ console.warn(`⚠️ Invalid --${optionName} value: "${value}" (using default)`);
189
+ return undefined;
190
+ }
191
+
192
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
193
+ console.warn(`⚠️ Invalid --${optionName} range: ${port} (must be 1-65535, using default)`);
194
+ return undefined;
195
+ }
196
+
197
+ return port;
198
+ }
199
+
200
+ async function main(): Promise<void> {
201
+ const args = process.argv.slice(2);
202
+ const { command, options } = parseArgs(args);
203
+
204
+ if (options.help || command === "help" || !command) {
205
+ console.log(HELP_TEXT);
206
+ process.exit(0);
207
+ }
208
+
209
+ let success = true;
210
+
211
+ switch (command) {
212
+ case "init":
213
+ success = await init({
214
+ name: options.name || options._positional,
215
+ css: options.css as any,
216
+ ui: options.ui as any,
217
+ theme: options.theme === "true",
218
+ minimal: options.minimal === "true",
219
+ });
220
+ break;
221
+
222
+ case "spec-upsert":
223
+ success = await specUpsert({ file: options.file });
224
+ break;
225
+
198
226
  case "generate":
199
227
  success = await generateApply();
200
228
  break;
@@ -211,39 +239,50 @@ async function main(): Promise<void> {
211
239
 
212
240
  case "guard": {
213
241
  const subCommand = args[1];
242
+ const hasSubCommand = subCommand && !subCommand.startsWith("--");
243
+ const guardArchOptions = {
244
+ preset: options.preset as any,
245
+ watch: options.watch === "true",
246
+ ci: options.ci === "true",
247
+ format: options.format as any,
248
+ quiet: options.quiet === "true",
249
+ srcDir: options["src-dir"],
250
+ listPresets: options["list-presets"] === "true",
251
+ output: options.output,
252
+ reportFormat: (options["report-format"] as any) || "markdown",
253
+ saveStats: options["save-stats"] === "true",
254
+ showTrend: options["show-trend"] === "true",
255
+ };
214
256
  switch (subCommand) {
215
257
  case "arch":
216
- success = await guardArch({
217
- preset: (options.preset as any) || "fsd",
218
- watch: options.watch === "true",
219
- ci: options.ci === "true",
220
- format: options.format as any,
221
- quiet: options.quiet === "true",
222
- srcDir: options["src-dir"],
223
- listPresets: options["list-presets"] === "true",
224
- output: options.output,
225
- reportFormat: (options["report-format"] as any) || "markdown",
226
- saveStats: options["save-stats"] === "true",
227
- showTrend: options["show-trend"] === "true",
228
- });
229
- break;
230
- default:
231
- // κΈ°λ³Έκ°’: λ ˆκ±°μ‹œ guard-check
232
- success = await guardCheck({
233
- autoCorrect: options["no-auto-correct"] !== "true",
234
- });
235
- }
236
- break;
237
- }
238
-
239
- case "build":
240
- success = await build({
241
- minify: options.minify === "true",
242
- sourcemap: options.sourcemap === "true",
243
- watch: options.watch === "true",
244
- });
245
- break;
246
-
258
+ success = await guardArch(guardArchOptions);
259
+ break;
260
+ case "legacy":
261
+ case "spec":
262
+ success = await guardCheck({
263
+ autoCorrect: options["no-auto-correct"] !== "true",
264
+ });
265
+ break;
266
+ default:
267
+ if (hasSubCommand) {
268
+ console.error(`❌ Unknown guard subcommand: ${subCommand}`);
269
+ console.log("\nUsage: bunx mandu guard <arch|legacy>");
270
+ process.exit(1);
271
+ }
272
+ // κΈ°λ³Έκ°’: architecture guard
273
+ success = await guardArch(guardArchOptions);
274
+ }
275
+ break;
276
+ }
277
+
278
+ case "build":
279
+ success = await build({
280
+ minify: options.minify === "true",
281
+ sourcemap: options.sourcemap === "true",
282
+ watch: options.watch === "true",
283
+ });
284
+ break;
285
+
247
286
  case "dev":
248
287
  await dev({
249
288
  port: parsePort(options.port),
@@ -253,170 +292,191 @@ async function main(): Promise<void> {
253
292
  legacy: options.legacy === "true",
254
293
  });
255
294
  break;
256
-
257
- case "routes": {
258
- const subCommand = args[1];
259
- switch (subCommand) {
260
- case "generate":
261
- success = await routesGenerate({
262
- output: options.output,
263
- verbose: options.verbose === "true",
264
- });
265
- break;
266
- case "list":
267
- success = await routesList({
268
- verbose: options.verbose === "true",
269
- });
270
- break;
271
- case "watch":
272
- success = await routesWatch({
273
- output: options.output,
274
- verbose: options.verbose === "true",
275
- });
276
- break;
277
- default:
278
- // κΈ°λ³Έκ°’: list
279
- if (!subCommand) {
280
- success = await routesList({
281
- verbose: options.verbose === "true",
282
- });
283
- } else {
284
- console.error(`❌ Unknown routes subcommand: ${subCommand}`);
285
- console.log("\nUsage: bunx mandu routes <generate|list|watch>");
286
- process.exit(1);
287
- }
288
- }
289
- break;
290
- }
291
-
292
- case "contract": {
293
- const subCommand = args[1];
294
- switch (subCommand) {
295
- case "create": {
296
- const routeId = args[2] || options._positional;
297
- if (!routeId) {
298
- console.error("❌ Route ID is required");
299
- console.log("\nUsage: bunx mandu contract create <routeId>");
300
- process.exit(1);
301
- }
302
- success = await contractCreate({ routeId });
303
- break;
304
- }
305
- case "validate":
306
- success = await contractValidate({ verbose: options.verbose === "true" });
307
- break;
308
- default:
309
- console.error(`❌ Unknown contract subcommand: ${subCommand}`);
310
- console.log("\nUsage: bunx mandu contract <create|validate>");
311
- process.exit(1);
312
- }
313
- break;
314
- }
315
-
316
- case "openapi": {
317
- const subCommand = args[1];
318
- switch (subCommand) {
319
- case "generate":
320
- success = await openAPIGenerate({
321
- output: options.output,
322
- title: options.title,
323
- version: options.version,
324
- });
325
- break;
326
- case "serve":
327
- success = await openAPIServe({
328
- port: parsePort(options.port),
329
- });
330
- break;
331
- default:
332
- console.error(`❌ Unknown openapi subcommand: ${subCommand}`);
333
- console.log("\nUsage: bunx mandu openapi <generate|serve>");
334
- process.exit(1);
335
- }
336
- break;
337
- }
338
-
339
- case "change": {
340
- const subCommand = args[1];
341
- switch (subCommand) {
342
- case "begin":
343
- success = await changeBegin({ message: options.message });
344
- break;
345
- case "commit":
346
- success = await changeCommit();
347
- break;
348
- case "rollback":
349
- success = await changeRollback({ id: options.id });
350
- break;
351
- case "status":
352
- success = await changeStatus();
353
- break;
354
- case "list":
355
- success = await changeList();
356
- break;
357
- case "prune":
358
- success = await changePrune({
359
- keep: options.keep ? Number(options.keep) : undefined,
360
- });
361
- break;
362
- default:
363
- console.error(`❌ Unknown change subcommand: ${subCommand}`);
364
- console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
365
- process.exit(1);
366
- }
367
- break;
368
- }
369
-
370
- case "doctor":
371
- success = await doctor({
372
- format: (options.format as "console" | "json" | "markdown") || "console",
373
- useLLM: options["no-llm"] !== "true",
374
- output: options.output,
375
- });
376
- break;
377
-
378
- case "watch":
379
- success = await watch({
380
- status: options.status === "true",
381
- debounce: options.debounce ? Number(options.debounce) : undefined,
382
- });
383
- break;
384
-
385
- case "brain": {
386
- const subCommand = args[1];
387
- switch (subCommand) {
388
- case "setup":
389
- success = await brainSetup({
390
- model: options.model,
391
- url: options.url,
392
- skipCheck: options["skip-check"] === "true",
393
- });
394
- break;
395
- case "status":
396
- success = await brainStatus({
397
- verbose: options.verbose === "true",
398
- });
399
- break;
400
- default:
401
- console.error(`❌ Unknown brain subcommand: ${subCommand}`);
402
- console.log("\nUsage: bunx mandu brain <setup|status>");
403
- process.exit(1);
404
- }
405
- break;
406
- }
407
-
408
- default:
409
- console.error(`❌ Unknown command: ${command}`);
410
- console.log(HELP_TEXT);
411
- process.exit(1);
412
- }
413
-
414
- if (!success) {
415
- process.exit(1);
416
- }
417
- }
418
-
419
- main().catch((error) => {
420
- console.error("❌ μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜:", error);
421
- process.exit(1);
422
- });
295
+
296
+ case "routes": {
297
+ const subCommand = args[1];
298
+ switch (subCommand) {
299
+ case "generate":
300
+ success = await routesGenerate({
301
+ output: options.output,
302
+ verbose: options.verbose === "true",
303
+ });
304
+ break;
305
+ case "list":
306
+ success = await routesList({
307
+ verbose: options.verbose === "true",
308
+ });
309
+ break;
310
+ case "watch":
311
+ success = await routesWatch({
312
+ output: options.output,
313
+ verbose: options.verbose === "true",
314
+ });
315
+ break;
316
+ default:
317
+ // κΈ°λ³Έκ°’: list
318
+ if (!subCommand) {
319
+ success = await routesList({
320
+ verbose: options.verbose === "true",
321
+ });
322
+ } else {
323
+ console.error(`❌ Unknown routes subcommand: ${subCommand}`);
324
+ console.log("\nUsage: bunx mandu routes <generate|list|watch>");
325
+ process.exit(1);
326
+ }
327
+ }
328
+ break;
329
+ }
330
+
331
+ case "contract": {
332
+ const subCommand = args[1];
333
+ switch (subCommand) {
334
+ case "create": {
335
+ const routeId = args[2] || options._positional;
336
+ if (!routeId) {
337
+ console.error("❌ Route ID is required");
338
+ console.log("\nUsage: bunx mandu contract create <routeId>");
339
+ process.exit(1);
340
+ }
341
+ success = await contractCreate({ routeId });
342
+ break;
343
+ }
344
+ case "validate":
345
+ success = await contractValidate({ verbose: options.verbose === "true" });
346
+ break;
347
+ case "build":
348
+ success = await contractBuild({ output: options.output });
349
+ break;
350
+ case "diff":
351
+ success = await contractDiff({
352
+ from: options.from,
353
+ to: options.to,
354
+ output: options.output,
355
+ json: options.json === "true",
356
+ });
357
+ break;
358
+ default:
359
+ console.error(`❌ Unknown contract subcommand: ${subCommand}`);
360
+ console.log("\nUsage: bunx mandu contract <create|validate|build|diff>");
361
+ process.exit(1);
362
+ }
363
+ break;
364
+ }
365
+
366
+ case "openapi": {
367
+ const subCommand = args[1];
368
+ switch (subCommand) {
369
+ case "generate":
370
+ success = await openAPIGenerate({
371
+ output: options.output,
372
+ title: options.title,
373
+ version: options.version,
374
+ });
375
+ break;
376
+ case "serve":
377
+ success = await openAPIServe({
378
+ port: parsePort(options.port),
379
+ });
380
+ break;
381
+ default:
382
+ console.error(`❌ Unknown openapi subcommand: ${subCommand}`);
383
+ console.log("\nUsage: bunx mandu openapi <generate|serve>");
384
+ process.exit(1);
385
+ }
386
+ break;
387
+ }
388
+
389
+ case "change": {
390
+ const subCommand = args[1];
391
+ switch (subCommand) {
392
+ case "begin":
393
+ success = await changeBegin({ message: options.message });
394
+ break;
395
+ case "commit":
396
+ success = await changeCommit();
397
+ break;
398
+ case "rollback":
399
+ success = await changeRollback({ id: options.id });
400
+ break;
401
+ case "status":
402
+ success = await changeStatus();
403
+ break;
404
+ case "list":
405
+ success = await changeList();
406
+ break;
407
+ case "prune":
408
+ success = await changePrune({
409
+ keep: options.keep ? Number(options.keep) : undefined,
410
+ });
411
+ break;
412
+ default:
413
+ console.error(`❌ Unknown change subcommand: ${subCommand}`);
414
+ console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
415
+ process.exit(1);
416
+ }
417
+ break;
418
+ }
419
+
420
+ case "doctor":
421
+ success = await doctor({
422
+ format: (options.format as "console" | "json" | "markdown") || "console",
423
+ useLLM: options["no-llm"] !== "true",
424
+ output: options.output,
425
+ });
426
+ break;
427
+
428
+ case "watch":
429
+ success = await watch({
430
+ status: options.status === "true",
431
+ debounce: options.debounce ? Number(options.debounce) : undefined,
432
+ });
433
+ break;
434
+
435
+ case "monitor":
436
+ success = await monitor({
437
+ format: options.format as any,
438
+ summary: options.summary === "true",
439
+ since: options.since,
440
+ follow: options.follow === "false" ? false : true,
441
+ file: options.file,
442
+ });
443
+ break;
444
+
445
+ case "brain": {
446
+ const subCommand = args[1];
447
+ switch (subCommand) {
448
+ case "setup":
449
+ success = await brainSetup({
450
+ model: options.model,
451
+ url: options.url,
452
+ skipCheck: options["skip-check"] === "true",
453
+ });
454
+ break;
455
+ case "status":
456
+ success = await brainStatus({
457
+ verbose: options.verbose === "true",
458
+ });
459
+ break;
460
+ default:
461
+ console.error(`❌ Unknown brain subcommand: ${subCommand}`);
462
+ console.log("\nUsage: bunx mandu brain <setup|status>");
463
+ process.exit(1);
464
+ }
465
+ break;
466
+ }
467
+
468
+ default:
469
+ console.error(`❌ Unknown command: ${command}`);
470
+ console.log(HELP_TEXT);
471
+ process.exit(1);
472
+ }
473
+
474
+ if (!success) {
475
+ process.exit(1);
476
+ }
477
+ }
478
+
479
+ main().catch((error) => {
480
+ console.error("❌ μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜:", error);
481
+ process.exit(1);
482
+ });