@mandujs/cli 0.12.2 → 0.13.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.
Files changed (51) hide show
  1. package/README.ko.md +234 -234
  2. package/README.md +354 -354
  3. package/package.json +2 -2
  4. package/src/commands/contract.ts +173 -173
  5. package/src/commands/dev.ts +8 -68
  6. package/src/commands/doctor.ts +27 -27
  7. package/src/commands/guard-arch.ts +303 -303
  8. package/src/commands/guard-check.ts +3 -3
  9. package/src/commands/monitor.ts +300 -300
  10. package/src/commands/openapi.ts +107 -107
  11. package/src/commands/registry.ts +367 -357
  12. package/src/commands/routes.ts +228 -228
  13. package/src/commands/start.ts +184 -0
  14. package/src/errors/codes.ts +35 -35
  15. package/src/errors/index.ts +2 -2
  16. package/src/errors/messages.ts +143 -143
  17. package/src/hooks/index.ts +17 -17
  18. package/src/hooks/preaction.ts +256 -256
  19. package/src/main.ts +37 -34
  20. package/src/terminal/banner.ts +166 -166
  21. package/src/terminal/help.ts +306 -306
  22. package/src/terminal/index.ts +71 -71
  23. package/src/terminal/output.ts +295 -295
  24. package/src/terminal/palette.ts +30 -30
  25. package/src/terminal/progress.ts +327 -327
  26. package/src/terminal/stream-writer.ts +214 -214
  27. package/src/terminal/table.ts +354 -354
  28. package/src/terminal/theme.ts +142 -142
  29. package/src/util/bun.ts +6 -6
  30. package/src/util/fs.ts +23 -23
  31. package/src/util/handlers.ts +96 -0
  32. package/src/util/manifest.ts +52 -52
  33. package/src/util/output.ts +22 -22
  34. package/src/util/port.ts +71 -71
  35. package/templates/default/AGENTS.md +96 -96
  36. package/templates/default/app/api/health/route.ts +13 -13
  37. package/templates/default/app/globals.css +49 -49
  38. package/templates/default/app/layout.tsx +27 -27
  39. package/templates/default/app/page.tsx +38 -38
  40. package/templates/default/package.json +1 -0
  41. package/templates/default/src/client/shared/lib/utils.ts +16 -16
  42. package/templates/default/src/client/shared/ui/button.tsx +57 -57
  43. package/templates/default/src/client/shared/ui/card.tsx +78 -78
  44. package/templates/default/src/client/shared/ui/index.ts +21 -21
  45. package/templates/default/src/client/shared/ui/input.tsx +24 -24
  46. package/templates/default/tests/example.test.ts +58 -58
  47. package/templates/default/tests/helpers.ts +52 -52
  48. package/templates/default/tests/setup.ts +9 -9
  49. package/templates/default/tsconfig.json +12 -14
  50. package/templates/default/apps/server/main.ts +0 -67
  51. package/templates/default/apps/web/entry.tsx +0 -35
@@ -1,295 +1,295 @@
1
- /**
2
- * DNA-014: Adaptive Output Format (JSON/Pretty/Plain)
3
- *
4
- * 환경에 따라 출력 형식을 자동 결정
5
- * - TTY: Pretty (색상 + 포맷팅)
6
- * - CI/pipe/agent: JSON (자동 처리/에이전트 친화)
7
- * - --json 플래그: JSON
8
- * - MANDU_OUTPUT 환경변수: 강제 지정
9
- */
10
-
11
- import { theme, isRich, stripAnsi } from "./theme.js";
12
-
13
- /**
14
- * 출력 모드
15
- */
16
- export type OutputMode = "json" | "pretty" | "plain";
17
-
18
- /**
19
- * 출력 옵션
20
- */
21
- export interface OutputOptions {
22
- /** JSON 출력 강제 */
23
- json?: boolean;
24
- /** Plain 텍스트 강제 */
25
- plain?: boolean;
26
- }
27
-
28
- /**
29
- * 출력 모드 결정
30
- *
31
- * 우선순위:
32
- * 1. --json 플래그 → "json"
33
- * 2. --plain 플래그 → "plain"
34
- * 3. MANDU_OUTPUT 환경변수 → 지정된 값
35
- * 4. 에이전트 환경 → "json"
36
- * 5. !TTY (파이프), CI → "json"
37
- * 6. 기본값 → "pretty"
38
- */
39
- export function getOutputMode(opts: OutputOptions = {}): OutputMode {
40
- // 플래그 우선
41
- if (opts.json) return "json";
42
- if (opts.plain) return "plain";
43
-
44
- // 환경변수 체크
45
- const envOutput = process.env.MANDU_OUTPUT?.toLowerCase();
46
- if (envOutput === "json") return "json";
47
- if (envOutput === "plain") return "plain";
48
- if (envOutput === "pretty") return "pretty";
49
- if (envOutput === "agent") return "json";
50
-
51
- // 에이전트 환경이면 JSON
52
- const agentSignals = [
53
- "MANDU_AGENT",
54
- "CODEX_AGENT",
55
- "CODEX",
56
- "CLAUDE_CODE",
57
- "ANTHROPIC_CLAUDE_CODE",
58
- ];
59
- for (const key of agentSignals) {
60
- const value = process.env[key];
61
- if (value === "1" || value === "true") {
62
- return "json";
63
- }
64
- }
65
-
66
- // CI 환경이면 JSON
67
- if (process.env.CI) return "json";
68
-
69
- // TTY가 아니면 JSON
70
- if (!process.stdout.isTTY) return "json";
71
-
72
- return "pretty";
73
- }
74
-
75
- /**
76
- * 출력 모드에 따른 포맷팅 컨텍스트
77
- */
78
- export interface FormatContext {
79
- mode: OutputMode;
80
- rich: boolean;
81
- }
82
-
83
- /**
84
- * 포맷팅 컨텍스트 생성
85
- */
86
- export function createFormatContext(opts: OutputOptions = {}): FormatContext {
87
- const mode = getOutputMode(opts);
88
- return {
89
- mode,
90
- rich: mode === "pretty" && isRich(),
91
- };
92
- }
93
-
94
- /**
95
- * 데이터를 모드에 맞게 포맷팅
96
- */
97
- export function formatOutput<T>(
98
- data: T,
99
- ctx: FormatContext,
100
- formatters: {
101
- json?: (data: T) => unknown;
102
- pretty?: (data: T, rich: boolean) => string;
103
- plain?: (data: T) => string;
104
- }
105
- ): string {
106
- const { mode, rich } = ctx;
107
-
108
- if (mode === "json") {
109
- const jsonData = formatters.json ? formatters.json(data) : data;
110
- return JSON.stringify(jsonData, null, 2);
111
- }
112
-
113
- if (mode === "plain") {
114
- if (formatters.plain) {
115
- return formatters.plain(data);
116
- }
117
- // Pretty 포맷터에서 ANSI 코드 제거
118
- if (formatters.pretty) {
119
- return stripAnsi(formatters.pretty(data, false));
120
- }
121
- return String(data);
122
- }
123
-
124
- // Pretty 모드
125
- if (formatters.pretty) {
126
- return formatters.pretty(data, rich);
127
- }
128
-
129
- return String(data);
130
- }
131
-
132
- /**
133
- * 에러 출력 포맷팅
134
- */
135
- export interface ErrorOutput {
136
- type: "error";
137
- message: string;
138
- error?: string;
139
- hint?: string;
140
- code?: string;
141
- }
142
-
143
- /**
144
- * 에러를 모드에 맞게 포맷팅
145
- */
146
- export function formatError(
147
- error: Error | string,
148
- ctx: FormatContext,
149
- options: {
150
- hint?: string;
151
- code?: string;
152
- } = {}
153
- ): string {
154
- const { mode, rich } = ctx;
155
- const message = error instanceof Error ? error.message : error;
156
- const errorStack = error instanceof Error ? error.stack : undefined;
157
-
158
- if (mode === "json") {
159
- const output: ErrorOutput = {
160
- type: "error",
161
- message,
162
- code: options.code,
163
- hint: options.hint,
164
- };
165
- if (error instanceof Error && error.stack) {
166
- output.error = error.stack;
167
- }
168
- return JSON.stringify(output, null, 2);
169
- }
170
-
171
- const lines: string[] = [];
172
-
173
- // 에러 메시지
174
- if (options.code) {
175
- lines.push(
176
- rich
177
- ? `${theme.error("❌ Error")} [${theme.muted(options.code)}]`
178
- : `Error [${options.code}]`
179
- );
180
- } else {
181
- lines.push(rich ? theme.error("❌ Error") : "Error");
182
- }
183
- lines.push(rich ? ` ${message}` : ` ${message}`);
184
-
185
- // 힌트
186
- if (options.hint) {
187
- lines.push("");
188
- lines.push(rich ? theme.muted(`💡 ${options.hint}`) : `Hint: ${options.hint}`);
189
- }
190
-
191
- return lines.join("\n");
192
- }
193
-
194
- /**
195
- * 성공 메시지 포맷팅
196
- */
197
- export function formatSuccess(
198
- message: string,
199
- ctx: FormatContext,
200
- details?: Record<string, unknown>
201
- ): string {
202
- const { mode, rich } = ctx;
203
-
204
- if (mode === "json") {
205
- return JSON.stringify({ type: "success", message, ...details }, null, 2);
206
- }
207
-
208
- if (rich) {
209
- return `${theme.success("✓")} ${message}`;
210
- }
211
-
212
- return `[OK] ${message}`;
213
- }
214
-
215
- /**
216
- * 경고 메시지 포맷팅
217
- */
218
- export function formatWarning(
219
- message: string,
220
- ctx: FormatContext,
221
- details?: Record<string, unknown>
222
- ): string {
223
- const { mode, rich } = ctx;
224
-
225
- if (mode === "json") {
226
- return JSON.stringify({ type: "warning", message, ...details }, null, 2);
227
- }
228
-
229
- if (rich) {
230
- return `${theme.warn("⚠")} ${message}`;
231
- }
232
-
233
- return `[WARN] ${message}`;
234
- }
235
-
236
- /**
237
- * 정보 메시지 포맷팅
238
- */
239
- export function formatInfo(
240
- message: string,
241
- ctx: FormatContext,
242
- details?: Record<string, unknown>
243
- ): string {
244
- const { mode, rich } = ctx;
245
-
246
- if (mode === "json") {
247
- return JSON.stringify({ type: "info", message, ...details }, null, 2);
248
- }
249
-
250
- if (rich) {
251
- return `${theme.info("ℹ")} ${message}`;
252
- }
253
-
254
- return `[INFO] ${message}`;
255
- }
256
-
257
- /**
258
- * 리스트 출력 포맷팅
259
- */
260
- export function formatList<T>(
261
- items: T[],
262
- ctx: FormatContext,
263
- options: {
264
- title?: string;
265
- itemFormatter?: (item: T, rich: boolean) => string;
266
- emptyMessage?: string;
267
- } = {}
268
- ): string {
269
- const { mode, rich } = ctx;
270
- const { title, itemFormatter, emptyMessage = "No items" } = options;
271
-
272
- if (mode === "json") {
273
- return JSON.stringify({ title, items, count: items.length }, null, 2);
274
- }
275
-
276
- const lines: string[] = [];
277
-
278
- if (title) {
279
- lines.push(rich ? theme.heading(title) : title);
280
- lines.push("");
281
- }
282
-
283
- if (items.length === 0) {
284
- lines.push(rich ? theme.muted(emptyMessage) : emptyMessage);
285
- } else {
286
- for (const item of items) {
287
- const formatted = itemFormatter
288
- ? itemFormatter(item, rich)
289
- : String(item);
290
- lines.push(` ${formatted}`);
291
- }
292
- }
293
-
294
- return lines.join("\n");
295
- }
1
+ /**
2
+ * DNA-014: Adaptive Output Format (JSON/Pretty/Plain)
3
+ *
4
+ * 환경에 따라 출력 형식을 자동 결정
5
+ * - TTY: Pretty (색상 + 포맷팅)
6
+ * - CI/pipe/agent: JSON (자동 처리/에이전트 친화)
7
+ * - --json 플래그: JSON
8
+ * - MANDU_OUTPUT 환경변수: 강제 지정
9
+ */
10
+
11
+ import { theme, isRich, stripAnsi } from "./theme.js";
12
+
13
+ /**
14
+ * 출력 모드
15
+ */
16
+ export type OutputMode = "json" | "pretty" | "plain";
17
+
18
+ /**
19
+ * 출력 옵션
20
+ */
21
+ export interface OutputOptions {
22
+ /** JSON 출력 강제 */
23
+ json?: boolean;
24
+ /** Plain 텍스트 강제 */
25
+ plain?: boolean;
26
+ }
27
+
28
+ /**
29
+ * 출력 모드 결정
30
+ *
31
+ * 우선순위:
32
+ * 1. --json 플래그 → "json"
33
+ * 2. --plain 플래그 → "plain"
34
+ * 3. MANDU_OUTPUT 환경변수 → 지정된 값
35
+ * 4. 에이전트 환경 → "json"
36
+ * 5. !TTY (파이프), CI → "json"
37
+ * 6. 기본값 → "pretty"
38
+ */
39
+ export function getOutputMode(opts: OutputOptions = {}): OutputMode {
40
+ // 플래그 우선
41
+ if (opts.json) return "json";
42
+ if (opts.plain) return "plain";
43
+
44
+ // 환경변수 체크
45
+ const envOutput = process.env.MANDU_OUTPUT?.toLowerCase();
46
+ if (envOutput === "json") return "json";
47
+ if (envOutput === "plain") return "plain";
48
+ if (envOutput === "pretty") return "pretty";
49
+ if (envOutput === "agent") return "json";
50
+
51
+ // 에이전트 환경이면 JSON
52
+ const agentSignals = [
53
+ "MANDU_AGENT",
54
+ "CODEX_AGENT",
55
+ "CODEX",
56
+ "CLAUDE_CODE",
57
+ "ANTHROPIC_CLAUDE_CODE",
58
+ ];
59
+ for (const key of agentSignals) {
60
+ const value = process.env[key];
61
+ if (value === "1" || value === "true") {
62
+ return "json";
63
+ }
64
+ }
65
+
66
+ // CI 환경이면 JSON
67
+ if (process.env.CI) return "json";
68
+
69
+ // TTY가 아니면 JSON
70
+ if (!process.stdout.isTTY) return "json";
71
+
72
+ return "pretty";
73
+ }
74
+
75
+ /**
76
+ * 출력 모드에 따른 포맷팅 컨텍스트
77
+ */
78
+ export interface FormatContext {
79
+ mode: OutputMode;
80
+ rich: boolean;
81
+ }
82
+
83
+ /**
84
+ * 포맷팅 컨텍스트 생성
85
+ */
86
+ export function createFormatContext(opts: OutputOptions = {}): FormatContext {
87
+ const mode = getOutputMode(opts);
88
+ return {
89
+ mode,
90
+ rich: mode === "pretty" && isRich(),
91
+ };
92
+ }
93
+
94
+ /**
95
+ * 데이터를 모드에 맞게 포맷팅
96
+ */
97
+ export function formatOutput<T>(
98
+ data: T,
99
+ ctx: FormatContext,
100
+ formatters: {
101
+ json?: (data: T) => unknown;
102
+ pretty?: (data: T, rich: boolean) => string;
103
+ plain?: (data: T) => string;
104
+ }
105
+ ): string {
106
+ const { mode, rich } = ctx;
107
+
108
+ if (mode === "json") {
109
+ const jsonData = formatters.json ? formatters.json(data) : data;
110
+ return JSON.stringify(jsonData, null, 2);
111
+ }
112
+
113
+ if (mode === "plain") {
114
+ if (formatters.plain) {
115
+ return formatters.plain(data);
116
+ }
117
+ // Pretty 포맷터에서 ANSI 코드 제거
118
+ if (formatters.pretty) {
119
+ return stripAnsi(formatters.pretty(data, false));
120
+ }
121
+ return String(data);
122
+ }
123
+
124
+ // Pretty 모드
125
+ if (formatters.pretty) {
126
+ return formatters.pretty(data, rich);
127
+ }
128
+
129
+ return String(data);
130
+ }
131
+
132
+ /**
133
+ * 에러 출력 포맷팅
134
+ */
135
+ export interface ErrorOutput {
136
+ type: "error";
137
+ message: string;
138
+ error?: string;
139
+ hint?: string;
140
+ code?: string;
141
+ }
142
+
143
+ /**
144
+ * 에러를 모드에 맞게 포맷팅
145
+ */
146
+ export function formatError(
147
+ error: Error | string,
148
+ ctx: FormatContext,
149
+ options: {
150
+ hint?: string;
151
+ code?: string;
152
+ } = {}
153
+ ): string {
154
+ const { mode, rich } = ctx;
155
+ const message = error instanceof Error ? error.message : error;
156
+ const errorStack = error instanceof Error ? error.stack : undefined;
157
+
158
+ if (mode === "json") {
159
+ const output: ErrorOutput = {
160
+ type: "error",
161
+ message,
162
+ code: options.code,
163
+ hint: options.hint,
164
+ };
165
+ if (error instanceof Error && error.stack) {
166
+ output.error = error.stack;
167
+ }
168
+ return JSON.stringify(output, null, 2);
169
+ }
170
+
171
+ const lines: string[] = [];
172
+
173
+ // 에러 메시지
174
+ if (options.code) {
175
+ lines.push(
176
+ rich
177
+ ? `${theme.error("❌ Error")} [${theme.muted(options.code)}]`
178
+ : `Error [${options.code}]`
179
+ );
180
+ } else {
181
+ lines.push(rich ? theme.error("❌ Error") : "Error");
182
+ }
183
+ lines.push(rich ? ` ${message}` : ` ${message}`);
184
+
185
+ // 힌트
186
+ if (options.hint) {
187
+ lines.push("");
188
+ lines.push(rich ? theme.muted(`💡 ${options.hint}`) : `Hint: ${options.hint}`);
189
+ }
190
+
191
+ return lines.join("\n");
192
+ }
193
+
194
+ /**
195
+ * 성공 메시지 포맷팅
196
+ */
197
+ export function formatSuccess(
198
+ message: string,
199
+ ctx: FormatContext,
200
+ details?: Record<string, unknown>
201
+ ): string {
202
+ const { mode, rich } = ctx;
203
+
204
+ if (mode === "json") {
205
+ return JSON.stringify({ type: "success", message, ...details }, null, 2);
206
+ }
207
+
208
+ if (rich) {
209
+ return `${theme.success("✓")} ${message}`;
210
+ }
211
+
212
+ return `[OK] ${message}`;
213
+ }
214
+
215
+ /**
216
+ * 경고 메시지 포맷팅
217
+ */
218
+ export function formatWarning(
219
+ message: string,
220
+ ctx: FormatContext,
221
+ details?: Record<string, unknown>
222
+ ): string {
223
+ const { mode, rich } = ctx;
224
+
225
+ if (mode === "json") {
226
+ return JSON.stringify({ type: "warning", message, ...details }, null, 2);
227
+ }
228
+
229
+ if (rich) {
230
+ return `${theme.warn("⚠")} ${message}`;
231
+ }
232
+
233
+ return `[WARN] ${message}`;
234
+ }
235
+
236
+ /**
237
+ * 정보 메시지 포맷팅
238
+ */
239
+ export function formatInfo(
240
+ message: string,
241
+ ctx: FormatContext,
242
+ details?: Record<string, unknown>
243
+ ): string {
244
+ const { mode, rich } = ctx;
245
+
246
+ if (mode === "json") {
247
+ return JSON.stringify({ type: "info", message, ...details }, null, 2);
248
+ }
249
+
250
+ if (rich) {
251
+ return `${theme.info("ℹ")} ${message}`;
252
+ }
253
+
254
+ return `[INFO] ${message}`;
255
+ }
256
+
257
+ /**
258
+ * 리스트 출력 포맷팅
259
+ */
260
+ export function formatList<T>(
261
+ items: T[],
262
+ ctx: FormatContext,
263
+ options: {
264
+ title?: string;
265
+ itemFormatter?: (item: T, rich: boolean) => string;
266
+ emptyMessage?: string;
267
+ } = {}
268
+ ): string {
269
+ const { mode, rich } = ctx;
270
+ const { title, itemFormatter, emptyMessage = "No items" } = options;
271
+
272
+ if (mode === "json") {
273
+ return JSON.stringify({ title, items, count: items.length }, null, 2);
274
+ }
275
+
276
+ const lines: string[] = [];
277
+
278
+ if (title) {
279
+ lines.push(rich ? theme.heading(title) : title);
280
+ lines.push("");
281
+ }
282
+
283
+ if (items.length === 0) {
284
+ lines.push(rich ? theme.muted(emptyMessage) : emptyMessage);
285
+ } else {
286
+ for (const item of items) {
287
+ const formatted = itemFormatter
288
+ ? itemFormatter(item, rich)
289
+ : String(item);
290
+ lines.push(` ${formatted}`);
291
+ }
292
+ }
293
+
294
+ return lines.join("\n");
295
+ }
@@ -1,30 +1,30 @@
1
- /**
2
- * DNA-009: Mandu Color Palette
3
- *
4
- * Inspired by OpenClaw's "Lobster Seam" palette
5
- * @see https://github.com/dominikwilkowski/cfonts
6
- */
7
-
8
- /**
9
- * Mandu 브랜드 색상 팔레트
10
- * 분홍색 기반의 따뜻한 톤
11
- */
12
- export const MANDU_PALETTE = {
13
- // 브랜드 컬러 (만두 분홍)
14
- accent: "#E8B4B8",
15
- accentBright: "#F5D0D3",
16
- accentDim: "#C9A0A4",
17
-
18
- // 시맨틱 컬러
19
- info: "#87CEEB", // 스카이 블루
20
- success: "#90EE90", // 라이트 그린
21
- warn: "#FFD700", // 골드
22
- error: "#FF6B6B", // 코랄 레드
23
-
24
- // 뉴트럴
25
- muted: "#9CA3AF", // 그레이
26
- dim: "#6B7280", // 다크 그레이
27
- text: "#F9FAFB", // 화이트
28
- } as const;
29
-
30
- export type ManduColor = keyof typeof MANDU_PALETTE;
1
+ /**
2
+ * DNA-009: Mandu Color Palette
3
+ *
4
+ * Inspired by OpenClaw's "Lobster Seam" palette
5
+ * @see https://github.com/dominikwilkowski/cfonts
6
+ */
7
+
8
+ /**
9
+ * Mandu 브랜드 색상 팔레트
10
+ * 분홍색 기반의 따뜻한 톤
11
+ */
12
+ export const MANDU_PALETTE = {
13
+ // 브랜드 컬러 (만두 분홍)
14
+ accent: "#E8B4B8",
15
+ accentBright: "#F5D0D3",
16
+ accentDim: "#C9A0A4",
17
+
18
+ // 시맨틱 컬러
19
+ info: "#87CEEB", // 스카이 블루
20
+ success: "#90EE90", // 라이트 그린
21
+ warn: "#FFD700", // 골드
22
+ error: "#FF6B6B", // 코랄 레드
23
+
24
+ // 뉴트럴
25
+ muted: "#9CA3AF", // 그레이
26
+ dim: "#6B7280", // 다크 그레이
27
+ text: "#F9FAFB", // 화이트
28
+ } as const;
29
+
30
+ export type ManduColor = keyof typeof MANDU_PALETTE;