@mandujs/cli 0.9.46 โ†’ 0.11.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/src/main.ts CHANGED
@@ -1,428 +1,237 @@
1
- #!/usr/bin/env bun
2
-
3
- import { specUpsert } from "./commands/spec-upsert";
4
- import { generateApply } from "./commands/generate-apply";
5
- import { guardCheck } from "./commands/guard-check";
6
- import { guardArch } from "./commands/guard-arch";
7
- import { check } from "./commands/check";
8
- import { dev } from "./commands/dev";
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
- import { CLI_ERROR_CODES, handleCLIError, printCLIError } from "./errors";
27
-
28
- const HELP_TEXT = `
29
- ๐ŸฅŸ Mandu CLI - Agent-Native Fullstack Framework
30
-
31
- Usage: bunx mandu <command> [options]
32
-
33
- Commands:
34
- init ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (Tailwind + shadcn/ui ๊ธฐ๋ณธ ํฌํ•จ)
35
- check FS Routes + Guard ํ†ตํ•ฉ ๊ฒ€์‚ฌ
36
- routes generate FS Routes ์Šค์บ” ๋ฐ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ์ƒ์„ฑ
37
- routes list ํ˜„์žฌ ๋ผ์šฐํŠธ ๋ชฉ๋ก ์ถœ๋ ฅ
38
- routes watch ์‹ค์‹œ๊ฐ„ ๋ผ์šฐํŠธ ๊ฐ์‹œ
39
- dev ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ (FS Routes + Guard ๊ธฐ๋ณธ)
40
- build ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค ๋นŒ๋“œ (Hydration)
41
- guard ์•„ํ‚คํ…์ฒ˜ ์œ„๋ฐ˜ ๊ฒ€์‚ฌ (๊ธฐ๋ณธ)
42
- guard arch ์•„ํ‚คํ…์ฒ˜ ์œ„๋ฐ˜ ๊ฒ€์‚ฌ (FSD/Clean/Hexagonal)
43
- guard legacy ๋ ˆ๊ฑฐ์‹œ Spec Guard ๊ฒ€์‚ฌ
44
- spec-upsert Spec ํŒŒ์ผ ๊ฒ€์ฆ ๋ฐ lock ๊ฐฑ์‹  (๋ ˆ๊ฑฐ์‹œ)
45
- generate Spec์—์„œ ์ฝ”๋“œ ์ƒ์„ฑ (๋ ˆ๊ฑฐ์‹œ)
46
-
47
- doctor Guard ์‹คํŒจ ๋ถ„์„ + ํŒจ์น˜ ์ œ์•ˆ (Brain)
48
- watch ์‹ค์‹œ๊ฐ„ ํŒŒ์ผ ๊ฐ์‹œ - ๊ฒฝ๊ณ ๋งŒ (Brain)
49
- monitor MCP Activity Monitor ๋กœ๊ทธ ์ŠคํŠธ๋ฆผ
50
-
51
- brain setup sLLM ์„ค์ • (์„ ํƒ)
52
- brain status Brain ์ƒํƒœ ํ™•์ธ
53
-
54
- contract create <routeId> ๋ผ์šฐํŠธ์— ๋Œ€ํ•œ Contract ์ƒ์„ฑ
55
- contract validate Contract-Slot ์ผ๊ด€์„ฑ ๊ฒ€์ฆ
56
- contract build Contract ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒ์„ฑ
57
- contract diff Contract ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋น„๊ต
58
-
59
- openapi generate OpenAPI 3.0 ์ŠคํŽ™ ์ƒ์„ฑ
60
- openapi serve Swagger UI ๋กœ์ปฌ ์„œ๋ฒ„ ์‹คํ–‰
61
-
62
- change begin ๋ณ€๊ฒฝ ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ (์Šค๋ƒ…์ƒท ์ƒ์„ฑ)
63
- change commit ๋ณ€๊ฒฝ ํ™•์ •
64
- change rollback ์Šค๋ƒ…์ƒท์œผ๋กœ ๋ณต์›
65
- change status ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜ ์ƒํƒœ
66
- change list ๋ณ€๊ฒฝ ์ด๋ ฅ ์กฐํšŒ
67
- change prune ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท ์ •๋ฆฌ
68
-
69
- Options:
70
- --name <name> init ์‹œ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ (๊ธฐ๋ณธ: my-mandu-app)
71
- --css <framework> init ์‹œ CSS ํ”„๋ ˆ์ž„์›Œํฌ: tailwind, panda, none (๊ธฐ๋ณธ: tailwind)
72
- --ui <library> init ์‹œ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: shadcn, ark, none (๊ธฐ๋ณธ: shadcn)
73
- --theme init ์‹œ ๋‹คํฌ๋ชจ๋“œ ํ…Œ๋งˆ ์‹œ์Šคํ…œ ์ถ”๊ฐ€
74
- --minimal init ์‹œ CSS/UI ์—†์ด ์ตœ์†Œ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ (--css none --ui none)
75
- --file <path> spec-upsert spec ํŒŒ์ผ/monitor ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ
76
- --watch build/guard arch ํŒŒ์ผ ๊ฐ์‹œ ๋ชจ๋“œ
77
- --output <path> routes/openapi/doctor/contract/guard ์ถœ๋ ฅ ๊ฒฝ๋กœ
78
- --verbose routes list/watch, contract validate, brain status ์ƒ์„ธ ์ถœ๋ ฅ
79
- --from <path> contract diff ๊ธฐ์ค€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ฒฝ๋กœ
80
- --to <path> contract diff ๋Œ€์ƒ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ฒฝ๋กœ
81
- --json contract diff ๊ฒฐ๊ณผ JSON ์ถœ๋ ฅ
82
- --title <title> openapi generate title
83
- --version <ver> openapi generate version
84
- --summary monitor ์š”์•ฝ ์ถœ๋ ฅ (JSON ๋กœ๊ทธ์—์„œ๋งŒ)
85
- --since <duration> monitor ์š”์•ฝ ๊ธฐ๊ฐ„ (์˜ˆ: 5m, 30s, 1h)
86
- --follow <bool> monitor follow ๋ชจ๋“œ (๊ธฐ๋ณธ: true)
87
- --message <msg> change begin ์‹œ ์„ค๋ช… ๋ฉ”์‹œ์ง€
88
- --id <id> change rollback ์‹œ ํŠน์ • ๋ณ€๊ฒฝ ID
89
- --keep <n> change prune ์‹œ ์œ ์ง€ํ•  ์Šค๋ƒ…์ƒท ์ˆ˜ (๊ธฐ๋ณธ: 5)
90
- --no-llm doctor์—์„œ LLM ์‚ฌ์šฉ ์•ˆ ํ•จ (ํ…œํ”Œ๋ฆฟ ๋ชจ๋“œ)
91
- --status watch ์ƒํƒœ๋งŒ ์ถœ๋ ฅ
92
- --debounce <ms> watch debounce (ms)
93
- --model <name> brain setup ์‹œ ๋ชจ๋ธ ์ด๋ฆ„ (๊ธฐ๋ณธ: llama3.2)
94
- --url <url> brain setup ์‹œ Ollama URL
95
- --skip-check brain setup ์‹œ ๋ชจ๋ธ/์„œ๋ฒ„ ์ฒดํฌ ๊ฑด๋„ˆ๋œ€
96
- --help, -h ๋„์›€๋ง ํ‘œ์‹œ
97
-
98
- Notes:
99
- - ์ถœ๋ ฅ ํฌ๋งท์€ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์ž๋™ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค (TTY/CI/MANDU_OUTPUT).
100
- - doctor ์ถœ๋ ฅ์€ .json์ด๋ฉด JSON, ๊ทธ ์™ธ๋Š” markdown์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
101
- - guard arch ๋ฆฌํฌํŠธ๋Š” .json/.html/.md ํ™•์žฅ์ž๋ฅผ ์ž๋™ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
102
- - ํฌํŠธ๋Š” PORT ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋˜๋Š” mandu.config์˜ server.port๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
103
- - ํฌํŠธ ์ถฉ๋Œ ์‹œ ๋‹ค์Œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํฌํŠธ๋กœ ์ž๋™ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
104
-
105
- Examples:
106
- bunx mandu init --name my-app # Tailwind + shadcn/ui ๊ธฐ๋ณธ
107
- bunx mandu init my-app --minimal # CSS/UI ์—†์ด ์ตœ์†Œ ํ…œํ”Œ๋ฆฟ
108
- bunx mandu dev
109
- bunx mandu build --watch
110
- bunx mandu guard
111
- bunx mandu guard arch --watch
112
- bunx mandu guard arch --output guard-report.md
113
- bunx mandu check
114
- bunx mandu routes list --verbose
115
- bunx mandu contract create users
116
- bunx mandu contract validate --verbose
117
- bunx mandu contract build --output .mandu/contracts.json
118
- bunx mandu contract diff --json
119
- bunx mandu openapi generate --output docs/openapi.json
120
- bunx mandu openapi serve
121
- bunx mandu monitor --summary --since 5m
122
- bunx mandu doctor --output reports/doctor.json
123
- bunx mandu brain setup --model codellama
124
- bunx mandu change begin --message "Add new route"
125
-
126
- FS Routes Workflow (๊ถŒ์žฅ):
127
- 1. init โ†’ 2. app/ ํด๋”์— page.tsx ์ƒ์„ฑ โ†’ 3. dev โ†’ 4. build
128
-
129
- Legacy Workflow:
130
- 1. init โ†’ 2. spec-upsert โ†’ 3. generate โ†’ 4. build โ†’ 5. guard โ†’ 6. dev
131
-
132
- Contract-first Workflow:
133
- 1. contract create โ†’ 2. Edit contract โ†’ 3. generate โ†’ 4. Edit slot โ†’ 5. contract validate
134
-
135
- Brain (sLLM) Workflow:
136
- 1. brain setup โ†’ 2. doctor (๋ถ„์„) โ†’ 3. watch (๊ฐ์‹œ)
137
- `;
138
-
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Mandu CLI - Agent-Native Fullstack Framework
5
+ *
6
+ * DNA-010: Command Registry Pattern ์ ์šฉ
7
+ * - ์„ ์–ธ์  ๋ช…๋ น์–ด ๋“ฑ๋ก
8
+ * - ๋ ˆ์ด์ง€ ๋กœ๋”ฉ์œผ๋กœ ์‹œ์ž‘ ์‹œ๊ฐ„ ์ตœ์ ํ™”
9
+ */
10
+
11
+ import { commandRegistry, getCommand, type CommandContext } from "./commands/registry";
12
+ import { CLI_ERROR_CODES, handleCLIError, printCLIError } from "./errors";
13
+ import { shouldShowBanner, renderHeroBanner, theme } from "./terminal";
14
+
15
+ const VERSION = "0.10.0";
16
+
17
+ const HELP_TEXT = `
18
+ ${theme.heading("๐ŸฅŸ Mandu CLI")} ${theme.muted(`v${VERSION}`)} - Agent-Native Fullstack Framework
19
+
20
+ ${theme.heading("Usage:")} ${theme.command("bunx mandu")} ${theme.option("<command>")} [options]
21
+
22
+ Commands:
23
+ init ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (Tailwind + shadcn/ui ๊ธฐ๋ณธ ํฌํ•จ)
24
+ check FS Routes + Guard ํ†ตํ•ฉ ๊ฒ€์‚ฌ
25
+ routes generate FS Routes ์Šค์บ” ๋ฐ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ์ƒ์„ฑ
26
+ routes list ํ˜„์žฌ ๋ผ์šฐํŠธ ๋ชฉ๋ก ์ถœ๋ ฅ
27
+ routes watch ์‹ค์‹œ๊ฐ„ ๋ผ์šฐํŠธ ๊ฐ์‹œ
28
+ dev ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ (FS Routes + Guard ๊ธฐ๋ณธ)
29
+ build ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค ๋นŒ๋“œ (Hydration)
30
+ guard ์•„ํ‚คํ…์ฒ˜ ์œ„๋ฐ˜ ๊ฒ€์‚ฌ (๊ธฐ๋ณธ)
31
+ guard arch ์•„ํ‚คํ…์ฒ˜ ์œ„๋ฐ˜ ๊ฒ€์‚ฌ (FSD/Clean/Hexagonal)
32
+ guard legacy ๋ ˆ๊ฑฐ์‹œ Spec Guard ๊ฒ€์‚ฌ
33
+ spec-upsert Spec ํŒŒ์ผ ๊ฒ€์ฆ ๋ฐ lock ๊ฐฑ์‹  (๋ ˆ๊ฑฐ์‹œ)
34
+ generate Spec์—์„œ ์ฝ”๋“œ ์ƒ์„ฑ (๋ ˆ๊ฑฐ์‹œ)
35
+
36
+ doctor Guard ์‹คํŒจ ๋ถ„์„ + ํŒจ์น˜ ์ œ์•ˆ (Brain)
37
+ watch ์‹ค์‹œ๊ฐ„ ํŒŒ์ผ ๊ฐ์‹œ - ๊ฒฝ๊ณ ๋งŒ (Brain)
38
+ monitor MCP Activity Monitor ๋กœ๊ทธ ์ŠคํŠธ๋ฆผ
39
+
40
+ brain setup sLLM ์„ค์ • (์„ ํƒ)
41
+ brain status Brain ์ƒํƒœ ํ™•์ธ
42
+
43
+ contract create <routeId> ๋ผ์šฐํŠธ์— ๋Œ€ํ•œ Contract ์ƒ์„ฑ
44
+ contract validate Contract-Slot ์ผ๊ด€์„ฑ ๊ฒ€์ฆ
45
+ contract build Contract ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒ์„ฑ
46
+ contract diff Contract ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋น„๊ต
47
+
48
+ openapi generate OpenAPI 3.0 ์ŠคํŽ™ ์ƒ์„ฑ
49
+ openapi serve Swagger UI ๋กœ์ปฌ ์„œ๋ฒ„ ์‹คํ–‰
50
+
51
+ change begin ๋ณ€๊ฒฝ ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ (์Šค๋ƒ…์ƒท ์ƒ์„ฑ)
52
+ change commit ๋ณ€๊ฒฝ ํ™•์ •
53
+ change rollback ์Šค๋ƒ…์ƒท์œผ๋กœ ๋ณต์›
54
+ change status ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜ ์ƒํƒœ
55
+ change list ๋ณ€๊ฒฝ ์ด๋ ฅ ์กฐํšŒ
56
+ change prune ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท ์ •๋ฆฌ
57
+
58
+ lock Lockfile ์ƒ์„ฑ/๊ฐฑ์‹ 
59
+ lock --verify Lockfile ๊ฒ€์ฆ (์„ค์ • ๋ฌด๊ฒฐ์„ฑ ํ™•์ธ)
60
+ lock --diff Lockfile๊ณผ ํ˜„์žฌ ์„ค์ • ๋น„๊ต
61
+
62
+ Options:
63
+ --name <name> init ์‹œ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ (๊ธฐ๋ณธ: my-mandu-app)
64
+ --css <framework> init ์‹œ CSS ํ”„๋ ˆ์ž„์›Œํฌ: tailwind, panda, none (๊ธฐ๋ณธ: tailwind)
65
+ --ui <library> init ์‹œ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: shadcn, ark, none (๊ธฐ๋ณธ: shadcn)
66
+ --theme init ์‹œ ๋‹คํฌ๋ชจ๋“œ ํ…Œ๋งˆ ์‹œ์Šคํ…œ ์ถ”๊ฐ€
67
+ --minimal init ์‹œ CSS/UI ์—†์ด ์ตœ์†Œ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ (--css none --ui none)
68
+ --file <path> spec-upsert spec ํŒŒ์ผ/monitor ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ
69
+ --watch build/guard arch ํŒŒ์ผ ๊ฐ์‹œ ๋ชจ๋“œ
70
+ --output <path> routes/openapi/doctor/contract/guard ์ถœ๋ ฅ ๊ฒฝ๋กœ
71
+ --verbose routes list/watch, contract validate, brain status ์ƒ์„ธ ์ถœ๋ ฅ
72
+ --from <path> contract diff ๊ธฐ์ค€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ฒฝ๋กœ
73
+ --to <path> contract diff ๋Œ€์ƒ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ฒฝ๋กœ
74
+ --json contract diff ๊ฒฐ๊ณผ JSON ์ถœ๋ ฅ
75
+ --title <title> openapi generate title
76
+ --version <ver> openapi generate version
77
+ --summary monitor ์š”์•ฝ ์ถœ๋ ฅ (JSON ๋กœ๊ทธ์—์„œ๋งŒ)
78
+ --since <duration> monitor ์š”์•ฝ ๊ธฐ๊ฐ„ (์˜ˆ: 5m, 30s, 1h)
79
+ --follow <bool> monitor follow ๋ชจ๋“œ (๊ธฐ๋ณธ: true)
80
+ --message <msg> change begin ์‹œ ์„ค๋ช… ๋ฉ”์‹œ์ง€
81
+ --id <id> change rollback ์‹œ ํŠน์ • ๋ณ€๊ฒฝ ID
82
+ --keep <n> change prune ์‹œ ์œ ์ง€ํ•  ์Šค๋ƒ…์ƒท ์ˆ˜ (๊ธฐ๋ณธ: 5)
83
+ --verify, -v lock ์‹œ lockfile ๊ฒ€์ฆ๋งŒ ์ˆ˜ํ–‰
84
+ --diff, -d lock ์‹œ lockfile๊ณผ ํ˜„์žฌ ์„ค์ • ๋น„๊ต
85
+ --show-secrets lock diff ์‹œ ๋ฏผ๊ฐ์ •๋ณด ์ถœ๋ ฅ ํ—ˆ์šฉ
86
+ --include-snapshot lock ์‹œ ์„ค์ • ์Šค๋ƒ…์ƒท ํฌํ•จ (diff ๊ธฐ๋Šฅ์— ํ•„์š”)
87
+ --mode <mode> lock verify ์‹œ ๋ชจ๋“œ (development|build|ci|production)
88
+ --no-llm doctor์—์„œ LLM ์‚ฌ์šฉ ์•ˆ ํ•จ (ํ…œํ”Œ๋ฆฟ ๋ชจ๋“œ)
89
+ --status watch ์ƒํƒœ๋งŒ ์ถœ๋ ฅ
90
+ --debounce <ms> watch debounce (ms)
91
+ --model <name> brain setup ์‹œ ๋ชจ๋ธ ์ด๋ฆ„ (๊ธฐ๋ณธ: llama3.2)
92
+ --url <url> brain setup ์‹œ Ollama URL
93
+ --skip-check brain setup ์‹œ ๋ชจ๋ธ/์„œ๋ฒ„ ์ฒดํฌ ๊ฑด๋„ˆ๋œ€
94
+ --help, -h ๋„์›€๋ง ํ‘œ์‹œ
95
+
96
+ Notes:
97
+ - ์ถœ๋ ฅ ํฌ๋งท์€ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์ž๋™ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค (TTY/CI/MANDU_OUTPUT).
98
+ - doctor ์ถœ๋ ฅ์€ .json์ด๋ฉด JSON, ๊ทธ ์™ธ๋Š” markdown์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
99
+ - guard arch ๋ฆฌํฌํŠธ๋Š” .json/.html/.md ํ™•์žฅ์ž๋ฅผ ์ž๋™ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
100
+ - ํฌํŠธ๋Š” PORT ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋˜๋Š” mandu.config์˜ server.port๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
101
+ - ํฌํŠธ ์ถฉ๋Œ ์‹œ ๋‹ค์Œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํฌํŠธ๋กœ ์ž๋™ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
102
+
103
+ Examples:
104
+ bunx mandu init --name my-app # Tailwind + shadcn/ui ๊ธฐ๋ณธ
105
+ bunx mandu init my-app --minimal # CSS/UI ์—†์ด ์ตœ์†Œ ํ…œํ”Œ๋ฆฟ
106
+ bunx mandu dev
107
+ bunx mandu build --watch
108
+ bunx mandu guard
109
+ bunx mandu guard arch --watch
110
+ bunx mandu guard arch --output guard-report.md
111
+ bunx mandu check
112
+ bunx mandu routes list --verbose
113
+ bunx mandu contract create users
114
+ bunx mandu contract validate --verbose
115
+ bunx mandu contract build --output .mandu/contracts.json
116
+ bunx mandu contract diff --json
117
+ bunx mandu openapi generate --output docs/openapi.json
118
+ bunx mandu openapi serve
119
+ bunx mandu monitor --summary --since 5m
120
+ bunx mandu doctor --output reports/doctor.json
121
+ bunx mandu brain setup --model codellama
122
+ bunx mandu change begin --message "Add new route"
123
+ bunx mandu lock # Lockfile ์ƒ์„ฑ/๊ฐฑ์‹ 
124
+ bunx mandu lock --verify # ์„ค์ • ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ
125
+ bunx mandu lock --diff --show-secrets # ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ƒ์„ธ ๋น„๊ต
126
+
127
+ FS Routes Workflow (๊ถŒ์žฅ):
128
+ 1. init โ†’ 2. app/ ํด๋”์— page.tsx ์ƒ์„ฑ โ†’ 3. dev โ†’ 4. build
129
+
130
+ Legacy Workflow:
131
+ 1. init โ†’ 2. spec-upsert โ†’ 3. generate โ†’ 4. build โ†’ 5. guard โ†’ 6. dev
132
+
133
+ Contract-first Workflow:
134
+ 1. contract create โ†’ 2. Edit contract โ†’ 3. generate โ†’ 4. Edit slot โ†’ 5. contract validate
135
+
136
+ Brain (sLLM) Workflow:
137
+ 1. brain setup โ†’ 2. doctor (๋ถ„์„) โ†’ 3. watch (๊ฐ์‹œ)
138
+ `;
139
+
140
+ /**
141
+ * ์ธ์ž ํŒŒ์‹ฑ
142
+ */
139
143
  function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
140
- const command = args[0] || "";
141
144
  const options: Record<string, string> = {};
142
-
143
- for (let i = 1; i < args.length; i++) {
145
+ let command = "";
146
+ const shortFlags: Record<string, string> = {
147
+ h: "help",
148
+ q: "quiet",
149
+ v: "verify",
150
+ d: "diff",
151
+ };
152
+
153
+ for (let i = 0; i < args.length; i++) {
144
154
  const arg = args[i];
155
+
156
+ // ํ”Œ๋ž˜๊ทธ ์ฒ˜๋ฆฌ
145
157
  if (arg.startsWith("--")) {
146
158
  const key = arg.slice(2);
147
159
  const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
148
160
  options[key] = value;
149
- } else if (arg === "-h") {
150
- options["help"] = "true";
151
- } else if (!options._positional) {
152
- // First non-flag argument after command is positional (e.g., project name)
153
- options._positional = arg;
154
- }
155
- }
156
-
157
- return { command, options };
158
- }
159
-
160
- async function main(): Promise<void> {
161
- const args = process.argv.slice(2);
162
- const { command, options } = parseArgs(args);
163
-
164
- if (options.help || command === "help" || !command) {
165
- console.log(HELP_TEXT);
166
- process.exit(0);
167
- }
168
-
169
- let success = true;
170
-
171
- switch (command) {
172
- case "init":
173
- success = await init({
174
- name: options.name || options._positional,
175
- css: options.css as any,
176
- ui: options.ui as any,
177
- theme: options.theme === "true",
178
- minimal: options.minimal === "true",
179
- });
180
- break;
181
-
182
- case "spec-upsert":
183
- success = await specUpsert({ file: options.file });
184
- break;
185
-
186
- case "generate":
187
- success = await generateApply();
188
- break;
189
-
190
- case "check":
191
- success = await check();
192
- break;
193
-
194
- case "guard": {
195
- const subCommand = args[1];
196
- const hasSubCommand = subCommand && !subCommand.startsWith("--");
197
- const guardArchOptions = {
198
- watch: options.watch === "true",
199
- output: options.output,
200
- };
201
- switch (subCommand) {
202
- case "arch":
203
- success = await guardArch(guardArchOptions);
204
- break;
205
- case "legacy":
206
- case "spec":
207
- success = await guardCheck();
208
- break;
209
- default:
210
- if (hasSubCommand) {
211
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
212
- command: "guard",
213
- subcommand,
214
- });
215
- console.log("\nUsage: bunx mandu guard <arch|legacy>");
216
- process.exit(1);
217
- }
218
- // ๊ธฐ๋ณธ๊ฐ’: architecture guard
219
- success = await guardArch(guardArchOptions);
220
- }
221
- break;
222
- }
223
-
224
- case "build":
225
- success = await build({
226
- watch: options.watch === "true",
227
- });
228
- break;
229
-
230
- case "dev":
231
- await dev();
232
- break;
233
-
234
- case "routes": {
235
- const subCommand = args[1];
236
- switch (subCommand) {
237
- case "generate":
238
- success = await routesGenerate({
239
- output: options.output,
240
- verbose: options.verbose === "true",
241
- });
242
- break;
243
- case "list":
244
- success = await routesList({
245
- verbose: options.verbose === "true",
246
- });
247
- break;
248
- case "watch":
249
- success = await routesWatch({
250
- output: options.output,
251
- verbose: options.verbose === "true",
252
- });
253
- break;
254
- default:
255
- // ๊ธฐ๋ณธ๊ฐ’: list
256
- if (!subCommand) {
257
- success = await routesList({
258
- verbose: options.verbose === "true",
259
- });
260
- } else {
261
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
262
- command: "routes",
263
- subcommand,
264
- });
265
- console.log("\nUsage: bunx mandu routes <generate|list|watch>");
266
- process.exit(1);
267
- }
268
- }
269
- break;
270
- }
271
-
272
- case "contract": {
273
- const subCommand = args[1];
274
- switch (subCommand) {
275
- case "create": {
276
- const routeId = args[2] || options._positional;
277
- if (!routeId) {
278
- printCLIError(CLI_ERROR_CODES.MISSING_ARGUMENT, { argument: "routeId" });
279
- console.log("\nUsage: bunx mandu contract create <routeId>");
280
- process.exit(1);
281
- }
282
- success = await contractCreate({ routeId });
283
- break;
161
+ } else if (arg.startsWith("-") && arg.length > 1) {
162
+ const flags = arg.slice(1).split("");
163
+ for (const flag of flags) {
164
+ const mapped = shortFlags[flag];
165
+ if (mapped) {
166
+ options[mapped] = "true";
167
+ } else {
168
+ options[flag] = "true";
284
169
  }
285
- case "validate":
286
- success = await contractValidate({ verbose: options.verbose === "true" });
287
- break;
288
- case "build":
289
- success = await contractBuild({ output: options.output });
290
- break;
291
- case "diff":
292
- success = await contractDiff({
293
- from: options.from,
294
- to: options.to,
295
- output: options.output,
296
- json: options.json === "true",
297
- });
298
- break;
299
- default:
300
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
301
- command: "contract",
302
- subcommand,
303
- });
304
- console.log("\nUsage: bunx mandu contract <create|validate|build|diff>");
305
- process.exit(1);
306
- }
307
- break;
308
- }
309
-
310
- case "openapi": {
311
- const subCommand = args[1];
312
- switch (subCommand) {
313
- case "generate":
314
- success = await openAPIGenerate({
315
- output: options.output,
316
- title: options.title,
317
- version: options.version,
318
- });
319
- break;
320
- case "serve":
321
- success = await openAPIServe();
322
- break;
323
- default:
324
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
325
- command: "openapi",
326
- subcommand,
327
- });
328
- console.log("\nUsage: bunx mandu openapi <generate|serve>");
329
- process.exit(1);
330
- }
331
- break;
332
- }
333
-
334
- case "change": {
335
- const subCommand = args[1];
336
- switch (subCommand) {
337
- case "begin":
338
- success = await changeBegin({ message: options.message });
339
- break;
340
- case "commit":
341
- success = await changeCommit();
342
- break;
343
- case "rollback":
344
- success = await changeRollback({ id: options.id });
345
- break;
346
- case "status":
347
- success = await changeStatus();
348
- break;
349
- case "list":
350
- success = await changeList();
351
- break;
352
- case "prune":
353
- success = await changePrune({
354
- keep: options.keep ? Number(options.keep) : undefined,
355
- });
356
- break;
357
- default:
358
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
359
- command: "change",
360
- subcommand,
361
- });
362
- console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
363
- process.exit(1);
364
- }
365
- break;
366
- }
367
-
368
- case "doctor":
369
- success = await doctor({
370
- useLLM: options["no-llm"] !== "true",
371
- output: options.output,
372
- });
373
- break;
374
-
375
- case "watch":
376
- success = await watch({
377
- status: options.status === "true",
378
- debounce: options.debounce ? Number(options.debounce) : undefined,
379
- });
380
- break;
381
-
382
- case "monitor":
383
- success = await monitor({
384
- summary: options.summary === "true",
385
- since: options.since,
386
- follow: options.follow === "false" ? false : true,
387
- file: options.file,
388
- });
389
- break;
390
-
391
- case "brain": {
392
- const subCommand = args[1];
393
- switch (subCommand) {
394
- case "setup":
395
- success = await brainSetup({
396
- model: options.model,
397
- url: options.url,
398
- skipCheck: options["skip-check"] === "true",
399
- });
400
- break;
401
- case "status":
402
- success = await brainStatus({
403
- verbose: options.verbose === "true",
404
- });
405
- break;
406
- default:
407
- printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
408
- command: "brain",
409
- subcommand,
410
- });
411
- console.log("\nUsage: bunx mandu brain <setup|status>");
412
- process.exit(1);
413
170
  }
414
- break;
415
- }
416
-
417
- default:
418
- printCLIError(CLI_ERROR_CODES.UNKNOWN_COMMAND, { command });
419
- console.log(HELP_TEXT);
420
- process.exit(1);
421
- }
422
-
423
- if (!success) {
424
- process.exit(1);
425
- }
426
- }
427
-
428
- main().catch((error) => handleCLIError(error));
171
+ } else if (!command) {
172
+ // ์ฒซ ๋ฒˆ์งธ ๋น„ํ”Œ๋ž˜๊ทธ ์ธ์ž๊ฐ€ ๋ช…๋ น์–ด
173
+ command = arg;
174
+ } else if (!options._positional) {
175
+ // ๋‘ ๋ฒˆ์งธ ๋น„ํ”Œ๋ž˜๊ทธ ์ธ์ž๊ฐ€ positional
176
+ options._positional = arg;
177
+ }
178
+ }
179
+
180
+ return { command, options };
181
+ }
182
+
183
+ /**
184
+ * ๋ฉ”์ธ ํ•จ์ˆ˜
185
+ */
186
+ async function main(): Promise<void> {
187
+ const args = process.argv.slice(2);
188
+ const { command, options } = parseArgs(args);
189
+
190
+ // ๋„์›€๋ง ์ฒ˜๋ฆฌ
191
+ if (options.help || command === "help" || !command) {
192
+ console.log(HELP_TEXT);
193
+ process.exit(0);
194
+ }
195
+
196
+ // ํžˆ์–ด๋กœ ๋ฐฐ๋„ˆ ํ‘œ์‹œ
197
+ if (shouldShowBanner(args)) {
198
+ await renderHeroBanner(VERSION);
199
+ }
200
+
201
+ // DNA-010: ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์—์„œ ๋ช…๋ น์–ด ์กฐํšŒ
202
+ const registration = getCommand(command);
203
+
204
+ if (!registration) {
205
+ printCLIError(CLI_ERROR_CODES.UNKNOWN_COMMAND, { command });
206
+ console.log(HELP_TEXT);
207
+ process.exit(1);
208
+ }
209
+
210
+ // ๋ช…๋ น์–ด ์‹คํ–‰ ์ปจํ…์ŠคํŠธ
211
+ const ctx: CommandContext = { args, options };
212
+
213
+ // ๋ช…๋ น์–ด ์‹คํ–‰
214
+ const success = await registration.run(ctx);
215
+
216
+ // ์„œ๋ธŒ์ปค๋งจ๋“œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
217
+ if (!success) {
218
+ const subCommand = args[1];
219
+ if (registration.subcommands && subCommand && !subCommand.startsWith("--")) {
220
+ // ์•Œ ์ˆ˜ ์—†๋Š” ์„œ๋ธŒ์ปค๋งจ๋“œ
221
+ printCLIError(CLI_ERROR_CODES.UNKNOWN_SUBCOMMAND, {
222
+ command,
223
+ subcommand: subCommand,
224
+ });
225
+ console.log(`\nUsage: bunx mandu ${command} <${registration.subcommands.join("|")}>`);
226
+ } else if (registration.subcommands) {
227
+ // ์„œ๋ธŒ์ปค๋งจ๋“œ ํ•„์š”
228
+ printCLIError(CLI_ERROR_CODES.MISSING_ARGUMENT, {
229
+ argument: "subcommand",
230
+ });
231
+ console.log(`\nUsage: bunx mandu ${command} <${registration.subcommands.join("|")}>`);
232
+ }
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ main().catch((error) => handleCLIError(error));