claudeship 0.2.24 → 0.2.25

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 (113) hide show
  1. package/apps/server/dist/chat/chat.module.js +8 -1
  2. package/apps/server/dist/chat/chat.module.js.map +1 -1
  3. package/apps/server/dist/chat/chat.service.d.ts +5 -1
  4. package/apps/server/dist/chat/chat.service.js +35 -4
  5. package/apps/server/dist/chat/chat.service.js.map +1 -1
  6. package/apps/server/dist/chat/framework-detector.service.d.ts +17 -0
  7. package/apps/server/dist/chat/framework-detector.service.js +193 -0
  8. package/apps/server/dist/chat/framework-detector.service.js.map +1 -0
  9. package/apps/server/dist/chat/prompts/backend/django.d.ts +1 -0
  10. package/apps/server/dist/chat/prompts/backend/django.js +207 -0
  11. package/apps/server/dist/chat/prompts/backend/django.js.map +1 -0
  12. package/apps/server/dist/chat/prompts/backend/express.d.ts +1 -0
  13. package/apps/server/dist/chat/prompts/backend/express.js +260 -0
  14. package/apps/server/dist/chat/prompts/backend/express.js.map +1 -0
  15. package/apps/server/dist/chat/prompts/backend/fastapi.d.ts +1 -0
  16. package/apps/server/dist/chat/prompts/backend/fastapi.js +246 -0
  17. package/apps/server/dist/chat/prompts/backend/fastapi.js.map +1 -0
  18. package/apps/server/dist/chat/prompts/backend/index.d.ts +4 -0
  19. package/apps/server/dist/chat/prompts/backend/index.js +12 -0
  20. package/apps/server/dist/chat/prompts/backend/index.js.map +1 -0
  21. package/apps/server/dist/chat/prompts/backend/nestjs.d.ts +1 -0
  22. package/apps/server/dist/chat/prompts/backend/nestjs.js +270 -0
  23. package/apps/server/dist/chat/prompts/backend/nestjs.js.map +1 -0
  24. package/apps/server/dist/chat/prompts/frontend/expo.d.ts +1 -0
  25. package/apps/server/dist/chat/prompts/frontend/expo.js +208 -0
  26. package/apps/server/dist/chat/prompts/frontend/expo.js.map +1 -0
  27. package/apps/server/dist/chat/prompts/frontend/flutter.d.ts +1 -0
  28. package/apps/server/dist/chat/prompts/frontend/flutter.js +271 -0
  29. package/apps/server/dist/chat/prompts/frontend/flutter.js.map +1 -0
  30. package/apps/server/dist/chat/prompts/frontend/index.d.ts +4 -0
  31. package/apps/server/dist/chat/prompts/frontend/index.js +12 -0
  32. package/apps/server/dist/chat/prompts/frontend/index.js.map +1 -0
  33. package/apps/server/dist/chat/prompts/frontend/nextjs.d.ts +1 -0
  34. package/apps/server/dist/chat/prompts/frontend/nextjs.js +195 -0
  35. package/apps/server/dist/chat/prompts/frontend/nextjs.js.map +1 -0
  36. package/apps/server/dist/chat/prompts/frontend/react-native.d.ts +1 -0
  37. package/apps/server/dist/chat/prompts/frontend/react-native.js +224 -0
  38. package/apps/server/dist/chat/prompts/frontend/react-native.js.map +1 -0
  39. package/apps/server/dist/chat/prompts/frontend/react-vite.d.ts +1 -0
  40. package/apps/server/dist/chat/prompts/frontend/react-vite.js +187 -0
  41. package/apps/server/dist/chat/prompts/frontend/react-vite.js.map +1 -0
  42. package/apps/server/dist/chat/prompts/frontend/svelte.d.ts +1 -0
  43. package/apps/server/dist/chat/prompts/frontend/svelte.js +255 -0
  44. package/apps/server/dist/chat/prompts/frontend/svelte.js.map +1 -0
  45. package/apps/server/dist/chat/prompts/frontend/vue.d.ts +1 -0
  46. package/apps/server/dist/chat/prompts/frontend/vue.js +267 -0
  47. package/apps/server/dist/chat/prompts/frontend/vue.js.map +1 -0
  48. package/apps/server/dist/chat/prompts/index.d.ts +4 -0
  49. package/apps/server/dist/chat/prompts/index.js +20 -1
  50. package/apps/server/dist/chat/prompts/index.js.map +1 -1
  51. package/apps/server/dist/chat/prompts/prompt-builder.service.d.ts +15 -0
  52. package/apps/server/dist/chat/prompts/prompt-builder.service.js +177 -0
  53. package/apps/server/dist/chat/prompts/prompt-builder.service.js.map +1 -0
  54. package/apps/server/dist/chat/prompts/sections/core.d.ts +9 -0
  55. package/apps/server/dist/chat/prompts/sections/core.js +149 -0
  56. package/apps/server/dist/chat/prompts/sections/core.js.map +1 -0
  57. package/apps/server/dist/project/project.controller.d.ts +6 -0
  58. package/apps/server/dist/project/project.service.d.ts +6 -0
  59. package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
  60. package/apps/server/package.json +1 -1
  61. package/apps/server/prisma/dev.db +0 -0
  62. package/apps/server/prisma/migrations/20260127071040_add_frontend_framework/migration.sql +24 -0
  63. package/apps/server/prisma/migrations/20260127071520_add_app_type_and_mobile/migration.sql +25 -0
  64. package/apps/server/prisma/schema.prisma +33 -5
  65. package/apps/web/.next/BUILD_ID +1 -1
  66. package/apps/web/.next/app-build-manifest.json +5 -3
  67. package/apps/web/.next/build-manifest.json +2 -2
  68. package/apps/web/.next/cache/.previewinfo +1 -1
  69. package/apps/web/.next/cache/.rscinfo +1 -1
  70. package/apps/web/.next/cache/.tsbuildinfo +1 -1
  71. package/apps/web/.next/cache/config.json +3 -3
  72. package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
  73. package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
  74. package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
  75. package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
  76. package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
  77. package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
  78. package/apps/web/.next/prerender-manifest.json +3 -3
  79. package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  80. package/apps/web/.next/server/app/_not-found.html +1 -1
  81. package/apps/web/.next/server/app/_not-found.rsc +2 -2
  82. package/apps/web/.next/server/app/index.html +1 -1
  83. package/apps/web/.next/server/app/index.rsc +3 -3
  84. package/apps/web/.next/server/app/page.js +2 -2
  85. package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  86. package/apps/web/.next/server/app/project/[id]/page.js +2 -2
  87. package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
  88. package/apps/web/.next/server/app/settings/page.js +1 -1
  89. package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  90. package/apps/web/.next/server/app/settings.html +1 -1
  91. package/apps/web/.next/server/app/settings.rsc +2 -2
  92. package/apps/web/.next/server/pages/404.html +1 -1
  93. package/apps/web/.next/server/pages/500.html +1 -1
  94. package/apps/web/.next/server/server-reference-manifest.json +1 -1
  95. package/apps/web/.next/static/chunks/18-22b1cf4231121555.js +1 -0
  96. package/apps/web/.next/static/chunks/app/page-6ea560755549086e.js +1 -0
  97. package/apps/web/.next/static/chunks/app/project/[id]/page-3e4777b355c4aec9.js +1 -0
  98. package/apps/web/.next/static/css/45ddb08a7b4470d5.css +3 -0
  99. package/apps/web/.next/trace +18 -18
  100. package/apps/web/package.json +1 -1
  101. package/apps/web/src/app/page.tsx +2 -5
  102. package/apps/web/src/components/project/CreateProjectModal.tsx +175 -80
  103. package/apps/web/src/components/project/ProjectCard.tsx +122 -31
  104. package/apps/web/src/stores/useProjectStore.ts +2 -1
  105. package/package.json +1 -1
  106. package/packages/shared/src/index.ts +1 -0
  107. package/packages/shared/src/types/project.ts +18 -3
  108. package/packages/shared/src/types/tech-stack.ts +74 -0
  109. package/apps/web/.next/static/chunks/app/page-f73b6424c8cbf96d.js +0 -1
  110. package/apps/web/.next/static/chunks/app/project/[id]/page-e9304c25ba897608.js +0 -1
  111. package/apps/web/.next/static/css/70f2a13cf3d254d8.css +0 -3
  112. /package/apps/web/.next/static/{fQQI0X4R8kXYUbd-0V8mo → IMWKpuHss3gLOeJ7K93sg}/_buildManifest.js +0 -0
  113. /package/apps/web/.next/static/{fQQI0X4R8kXYUbd-0V8mo → IMWKpuHss3gLOeJ7K93sg}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudeship/web",
3
- "version": "0.2.24",
3
+ "version": "0.2.25",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "node scripts/dev-with-recovery.mjs",
@@ -7,7 +7,7 @@ import { ProjectList } from "@/components/project/ProjectList";
7
7
  import { CreateProjectModal } from "@/components/project/CreateProjectModal";
8
8
  import { useProjectStore } from "@/stores/useProjectStore";
9
9
  import { useTranslation } from "@/lib/i18n";
10
- import type { ProjectType } from "@claudeship/shared";
10
+ import type { CreateProjectInput } from "@claudeship/shared";
11
11
 
12
12
  export default function Home() {
13
13
  const router = useRouter();
@@ -20,10 +20,7 @@ export default function Home() {
20
20
  fetchProjects();
21
21
  }, [fetchProjects]);
22
22
 
23
- const handleCreateProject = async (data: {
24
- name: string;
25
- projectType: ProjectType;
26
- }) => {
23
+ const handleCreateProject = async (data: CreateProjectInput) => {
27
24
  try {
28
25
  const project = await createProject(data);
29
26
  setIsModalOpen(false);
@@ -11,7 +11,11 @@ import {
11
11
  } from "@/components/ui/dialog";
12
12
  import { Button } from "@/components/ui/button";
13
13
  import { Input } from "@/components/ui/input";
14
- import { ProjectType, BackendFramework } from "@claudeship/shared";
14
+ import {
15
+ AppType,
16
+ FrontendFramework,
17
+ BackendFramework,
18
+ } from "@claudeship/shared";
15
19
  import { useTranslation } from "@/lib/i18n";
16
20
 
17
21
  interface CreateProjectModalProps {
@@ -19,12 +23,80 @@ interface CreateProjectModalProps {
19
23
  onOpenChange: (open: boolean) => void;
20
24
  onSubmit: (data: {
21
25
  name: string;
22
- projectType: ProjectType;
23
- backendFramework: BackendFramework;
26
+ appType: AppType;
27
+ frontendFramework?: FrontendFramework;
28
+ backendFramework?: BackendFramework;
24
29
  }) => void;
25
30
  isLoading?: boolean;
26
31
  }
27
32
 
33
+ // App type configurations
34
+ const APP_TYPE_CONFIG = {
35
+ [AppType.FULLSTACK_WEB]: {
36
+ icon: "🌐",
37
+ label: "풀스택 웹앱",
38
+ description: "프론트엔드 + 백엔드",
39
+ showFrontend: true,
40
+ showBackend: true,
41
+ webOnly: true,
42
+ },
43
+ [AppType.FRONTEND_ONLY]: {
44
+ icon: "⚡",
45
+ label: "프론트엔드",
46
+ description: "정적 사이트 / SPA",
47
+ showFrontend: true,
48
+ showBackend: false,
49
+ webOnly: true,
50
+ },
51
+ [AppType.API_ONLY]: {
52
+ icon: "🔌",
53
+ label: "API 서버",
54
+ description: "백엔드만",
55
+ showFrontend: false,
56
+ showBackend: true,
57
+ webOnly: false,
58
+ },
59
+ [AppType.MOBILE]: {
60
+ icon: "📱",
61
+ label: "모바일 앱",
62
+ description: "React Native / Flutter",
63
+ showFrontend: true,
64
+ showBackend: false,
65
+ webOnly: false,
66
+ },
67
+ [AppType.MOBILE_WITH_API]: {
68
+ icon: "📱🔌",
69
+ label: "모바일 + API",
70
+ description: "모바일 앱 + 백엔드",
71
+ showFrontend: true,
72
+ showBackend: true,
73
+ webOnly: false,
74
+ },
75
+ };
76
+
77
+ // Web frontend frameworks
78
+ const WEB_FRONTENDS = [
79
+ { value: FrontendFramework.REACT_VITE, icon: "⚛️", label: "React + Vite" },
80
+ { value: FrontendFramework.NEXTJS, icon: "▲", label: "Next.js" },
81
+ { value: FrontendFramework.VUE, icon: "💚", label: "Vue 3" },
82
+ { value: FrontendFramework.SVELTE, icon: "🔶", label: "SvelteKit" },
83
+ ];
84
+
85
+ // Mobile frontend frameworks
86
+ const MOBILE_FRONTENDS = [
87
+ { value: FrontendFramework.EXPO, icon: "📱", label: "Expo" },
88
+ { value: FrontendFramework.REACT_NATIVE, icon: "⚛️", label: "React Native" },
89
+ { value: FrontendFramework.FLUTTER, icon: "🐦", label: "Flutter" },
90
+ ];
91
+
92
+ // Backend frameworks
93
+ const BACKENDS = [
94
+ { value: BackendFramework.EXPRESS, icon: "🟢", label: "Express" },
95
+ { value: BackendFramework.FASTAPI, icon: "🐍", label: "FastAPI" },
96
+ { value: BackendFramework.DJANGO, icon: "🎸", label: "Django" },
97
+ { value: BackendFramework.NESTJS, icon: "🔴", label: "NestJS" },
98
+ ];
99
+
28
100
  export function CreateProjectModal({
29
101
  open,
30
102
  onOpenChange,
@@ -33,32 +105,66 @@ export function CreateProjectModal({
33
105
  }: CreateProjectModalProps) {
34
106
  const { t } = useTranslation();
35
107
  const [name, setName] = useState("");
36
- const [projectType, setProjectType] = useState<ProjectType>(ProjectType.WEB);
108
+ const [appType, setAppType] = useState<AppType>(AppType.FULLSTACK_WEB);
109
+ const [frontendFramework, setFrontendFramework] = useState<FrontendFramework>(
110
+ FrontendFramework.REACT_VITE
111
+ );
37
112
  const [backendFramework, setBackendFramework] = useState<BackendFramework>(
38
- BackendFramework.NONE
113
+ BackendFramework.EXPRESS
39
114
  );
40
115
 
116
+ const config = APP_TYPE_CONFIG[appType];
117
+ const isMobileApp =
118
+ appType === AppType.MOBILE || appType === AppType.MOBILE_WITH_API;
119
+
120
+ const handleAppTypeChange = (newAppType: AppType) => {
121
+ setAppType(newAppType);
122
+ // Reset frameworks based on app type
123
+ const newConfig = APP_TYPE_CONFIG[newAppType];
124
+ const isMobile =
125
+ newAppType === AppType.MOBILE || newAppType === AppType.MOBILE_WITH_API;
126
+
127
+ if (newConfig.showFrontend) {
128
+ setFrontendFramework(
129
+ isMobile ? FrontendFramework.EXPO : FrontendFramework.REACT_VITE
130
+ );
131
+ }
132
+ if (newConfig.showBackend) {
133
+ setBackendFramework(BackendFramework.EXPRESS);
134
+ }
135
+ };
136
+
41
137
  const handleSubmit = (e: React.FormEvent) => {
42
138
  e.preventDefault();
43
139
  if (!name.trim()) return;
44
- onSubmit({ name: name.trim(), projectType, backendFramework });
140
+
141
+ onSubmit({
142
+ name: name.trim(),
143
+ appType,
144
+ frontendFramework: config.showFrontend ? frontendFramework : undefined,
145
+ backendFramework: config.showBackend ? backendFramework : undefined,
146
+ });
147
+
148
+ // Reset form
45
149
  setName("");
46
- setProjectType(ProjectType.WEB);
47
- setBackendFramework(BackendFramework.NONE);
150
+ setAppType(AppType.FULLSTACK_WEB);
151
+ setFrontendFramework(FrontendFramework.REACT_VITE);
152
+ setBackendFramework(BackendFramework.EXPRESS);
48
153
  };
49
154
 
50
155
  return (
51
156
  <Dialog open={open} onOpenChange={onOpenChange}>
52
- <DialogContent className="max-w-lg">
157
+ <DialogContent className="max-w-lg max-h-[90vh] overflow-y-auto">
53
158
  <form onSubmit={handleSubmit}>
54
159
  <DialogHeader>
55
160
  <DialogTitle>{t("project.createTitle")}</DialogTitle>
56
161
  <DialogDescription>
57
- {t("project.name")}
162
+ 새 프로젝트의 이름과 타입을 선택하세요
58
163
  </DialogDescription>
59
164
  </DialogHeader>
60
165
 
61
166
  <div className="grid gap-4 py-4">
167
+ {/* Project Name */}
62
168
  <div className="grid gap-2">
63
169
  <label htmlFor="name" className="text-sm font-medium">
64
170
  {t("project.name")}
@@ -72,83 +178,72 @@ export function CreateProjectModal({
72
178
  />
73
179
  </div>
74
180
 
181
+ {/* App Type Selection */}
75
182
  <div className="grid gap-2">
76
- <label className="text-sm font-medium">{t("project.type")}</label>
183
+ <label className="text-sm font-medium">앱 타입</label>
77
184
  <div className="grid grid-cols-2 gap-2">
78
- <Button
79
- type="button"
80
- variant={projectType === ProjectType.WEB ? "default" : "outline"}
81
- className="h-16 flex-col gap-1"
82
- onClick={() => setProjectType(ProjectType.WEB)}
83
- >
84
- <span className="text-xl">🌐</span>
85
- <span className="text-xs">{t("project.typeWeb")}</span>
86
- </Button>
87
- <Button
88
- type="button"
89
- variant={projectType === ProjectType.NATIVE ? "default" : "outline"}
90
- className="h-16 flex-col gap-1"
91
- onClick={() => setProjectType(ProjectType.NATIVE)}
92
- >
93
- <span className="text-xl">📱</span>
94
- <span className="text-xs">{t("project.typeNative")}</span>
95
- </Button>
96
- </div>
97
- </div>
98
-
99
- {projectType === ProjectType.WEB && (
100
- <div className="grid gap-2">
101
- <label className="text-sm font-medium">{t("project.backendStack")}</label>
102
- <div className="grid grid-cols-3 gap-2">
185
+ {Object.entries(APP_TYPE_CONFIG).map(([type, cfg]) => (
103
186
  <Button
187
+ key={type}
104
188
  type="button"
105
- variant={
106
- backendFramework === BackendFramework.NONE
107
- ? "default"
108
- : "outline"
109
- }
110
- className="h-20 flex-col gap-1"
111
- onClick={() => setBackendFramework(BackendFramework.NONE)}
189
+ variant={appType === type ? "default" : "outline"}
190
+ className="h-16 flex-col gap-0.5"
191
+ onClick={() => handleAppTypeChange(type as AppType)}
112
192
  >
113
- <span className="text-lg">⚡</span>
114
- <span className="text-xs text-center leading-tight">
115
- {t("project.backendNone")}
116
- </span>
117
- </Button>
118
- <Button
119
- type="button"
120
- variant={
121
- backendFramework === BackendFramework.EXPRESS
122
- ? "default"
123
- : "outline"
124
- }
125
- className="h-20 flex-col gap-1"
126
- onClick={() => setBackendFramework(BackendFramework.EXPRESS)}
127
- >
128
- <span className="text-lg">🟢</span>
129
- <span className="text-xs text-center leading-tight">
130
- Express
131
- <br />
132
- (Node.js)
133
- </span>
134
- </Button>
135
- <Button
136
- type="button"
137
- variant={
138
- backendFramework === BackendFramework.FASTAPI
139
- ? "default"
140
- : "outline"
141
- }
142
- className="h-20 flex-col gap-1"
143
- onClick={() => setBackendFramework(BackendFramework.FASTAPI)}
144
- >
145
- <span className="text-lg">🐍</span>
146
- <span className="text-xs text-center leading-tight">
147
- FastAPI
148
- <br />
149
- (Python)
193
+ <span className="text-lg">{cfg.icon}</span>
194
+ <span className="text-xs font-medium">{cfg.label}</span>
195
+ <span className="text-[10px] text-muted-foreground">
196
+ {cfg.description}
150
197
  </span>
151
198
  </Button>
199
+ ))}
200
+ </div>
201
+ </div>
202
+
203
+ {/* Frontend Framework Selection */}
204
+ {config.showFrontend && (
205
+ <div className="grid gap-2">
206
+ <label className="text-sm font-medium">
207
+ {isMobileApp ? "모바일 프레임워크" : "프론트엔드"}
208
+ </label>
209
+ <div className="grid grid-cols-4 gap-2">
210
+ {(isMobileApp ? MOBILE_FRONTENDS : WEB_FRONTENDS).map((fw) => (
211
+ <Button
212
+ key={fw.value}
213
+ type="button"
214
+ variant={
215
+ frontendFramework === fw.value ? "default" : "outline"
216
+ }
217
+ className="h-14 flex-col gap-0.5"
218
+ onClick={() => setFrontendFramework(fw.value)}
219
+ >
220
+ <span className="text-base">{fw.icon}</span>
221
+ <span className="text-[10px]">{fw.label}</span>
222
+ </Button>
223
+ ))}
224
+ </div>
225
+ </div>
226
+ )}
227
+
228
+ {/* Backend Framework Selection */}
229
+ {config.showBackend && (
230
+ <div className="grid gap-2">
231
+ <label className="text-sm font-medium">백엔드</label>
232
+ <div className="grid grid-cols-4 gap-2">
233
+ {BACKENDS.map((fw) => (
234
+ <Button
235
+ key={fw.value}
236
+ type="button"
237
+ variant={
238
+ backendFramework === fw.value ? "default" : "outline"
239
+ }
240
+ className="h-14 flex-col gap-0.5"
241
+ onClick={() => setBackendFramework(fw.value)}
242
+ >
243
+ <span className="text-base">{fw.icon}</span>
244
+ <span className="text-[10px]">{fw.label}</span>
245
+ </Button>
246
+ ))}
152
247
  </div>
153
248
  </div>
154
249
  )}
@@ -1,11 +1,16 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { Trash2, Globe, Smartphone, Server, Database } from "lucide-react";
4
+ import { Trash2, Globe, Smartphone, Server, Database, Zap, Code } from "lucide-react";
5
5
  import { Card } from "@/components/ui/card";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import type { ProjectListItem } from "@claudeship/shared";
8
- import { ProjectType, BackendFramework, DatabaseProvider } from "@claudeship/shared";
8
+ import {
9
+ AppType,
10
+ BackendFramework,
11
+ FrontendFramework,
12
+ DatabaseProvider,
13
+ } from "@claudeship/shared";
9
14
  import { useTranslation } from "@/lib/i18n";
10
15
 
11
16
  interface ProjectCardProps {
@@ -35,43 +40,111 @@ function formatRelativeTime(date: Date, locale: string): string {
35
40
  }
36
41
  }
37
42
 
38
- function getProjectIcon(projectType: ProjectType) {
39
- switch (projectType) {
40
- case ProjectType.WEB:
43
+ function getAppTypeIcon(appType: AppType) {
44
+ switch (appType) {
45
+ case AppType.FULLSTACK_WEB:
41
46
  return <Globe className="h-5 w-5" />;
42
- case ProjectType.NATIVE:
47
+ case AppType.FRONTEND_ONLY:
48
+ return <Zap className="h-5 w-5" />;
49
+ case AppType.API_ONLY:
50
+ return <Server className="h-5 w-5" />;
51
+ case AppType.MOBILE:
52
+ return <Smartphone className="h-5 w-5" />;
53
+ case AppType.MOBILE_WITH_API:
43
54
  return <Smartphone className="h-5 w-5" />;
44
55
  default:
45
- return <Globe className="h-5 w-5" />;
56
+ return <Code className="h-5 w-5" />;
57
+ }
58
+ }
59
+
60
+ function getGradientClass(appType: AppType) {
61
+ switch (appType) {
62
+ case AppType.FULLSTACK_WEB:
63
+ return "from-blue-500/20 to-indigo-500/5";
64
+ case AppType.FRONTEND_ONLY:
65
+ return "from-yellow-500/20 to-orange-500/5";
66
+ case AppType.API_ONLY:
67
+ return "from-green-500/20 to-emerald-500/5";
68
+ case AppType.MOBILE:
69
+ return "from-purple-500/20 to-pink-500/5";
70
+ case AppType.MOBILE_WITH_API:
71
+ return "from-violet-500/20 to-purple-500/5";
72
+ default:
73
+ return "from-gray-500/20 to-slate-500/5";
46
74
  }
47
75
  }
48
76
 
49
- function getGradientClass(projectType: ProjectType, backendFramework: BackendFramework) {
50
- if (backendFramework === BackendFramework.EXPRESS) {
51
- return "from-green-500/20 to-emerald-500/5";
77
+ function getAppTypeLabel(appType: AppType): string {
78
+ switch (appType) {
79
+ case AppType.FULLSTACK_WEB:
80
+ return "Fullstack";
81
+ case AppType.FRONTEND_ONLY:
82
+ return "Frontend";
83
+ case AppType.API_ONLY:
84
+ return "API";
85
+ case AppType.MOBILE:
86
+ return "Mobile";
87
+ case AppType.MOBILE_WITH_API:
88
+ return "Mobile+API";
89
+ default:
90
+ return "Unknown";
91
+ }
92
+ }
93
+
94
+ function getFrontendLabel(framework: FrontendFramework): string {
95
+ switch (framework) {
96
+ case FrontendFramework.REACT_VITE:
97
+ return "React";
98
+ case FrontendFramework.NEXTJS:
99
+ return "Next.js";
100
+ case FrontendFramework.VUE:
101
+ return "Vue";
102
+ case FrontendFramework.SVELTE:
103
+ return "Svelte";
104
+ case FrontendFramework.REACT_NATIVE:
105
+ return "RN";
106
+ case FrontendFramework.EXPO:
107
+ return "Expo";
108
+ case FrontendFramework.FLUTTER:
109
+ return "Flutter";
110
+ default:
111
+ return "";
52
112
  }
53
- if (backendFramework === BackendFramework.FASTAPI) {
54
- return "from-teal-500/20 to-cyan-500/5";
113
+ }
114
+
115
+ function getBackendLabel(framework: BackendFramework): string {
116
+ switch (framework) {
117
+ case BackendFramework.EXPRESS:
118
+ return "Express";
119
+ case BackendFramework.FASTAPI:
120
+ return "FastAPI";
121
+ case BackendFramework.DJANGO:
122
+ return "Django";
123
+ case BackendFramework.NESTJS:
124
+ return "NestJS";
125
+ default:
126
+ return "";
55
127
  }
56
- return "from-blue-500/20 to-indigo-500/5";
57
128
  }
58
129
 
59
130
  export function ProjectCard({ project, onDelete }: ProjectCardProps) {
60
131
  const { locale } = useTranslation();
61
- const gradientClass = getGradientClass(project.projectType, project.backendFramework);
132
+ const gradientClass = getGradientClass(project.appType);
62
133
 
63
134
  return (
64
135
  <Link href={`/project/${project.id}`}>
65
136
  <Card className="group relative cursor-pointer overflow-hidden border transition-all hover:border-primary/50 hover:shadow-lg">
66
137
  {/* Gradient background */}
67
- <div className={`absolute inset-0 bg-gradient-to-br ${gradientClass} opacity-0 transition-opacity group-hover:opacity-100`} />
138
+ <div
139
+ className={`absolute inset-0 bg-gradient-to-br ${gradientClass} opacity-0 transition-opacity group-hover:opacity-100`}
140
+ />
68
141
 
69
142
  <div className="relative p-4">
70
143
  {/* Header */}
71
144
  <div className="flex items-start justify-between gap-2">
72
145
  <div className="flex items-center gap-3">
73
146
  <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
74
- {getProjectIcon(project.projectType)}
147
+ {getAppTypeIcon(project.appType)}
75
148
  </div>
76
149
  <div className="min-w-0 flex-1">
77
150
  <h3 className="truncate font-semibold">{project.name}</h3>
@@ -98,25 +171,43 @@ export function ProjectCard({ project, onDelete }: ProjectCardProps) {
98
171
 
99
172
  {/* Tags */}
100
173
  <div className="mt-4 flex flex-wrap gap-2">
101
- {project.backendFramework !== BackendFramework.NONE && (
102
- <span className="inline-flex items-center gap-1 rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium">
103
- <Server className="h-3 w-3" />
104
- {project.backendFramework === BackendFramework.EXPRESS ? "Express" : "FastAPI"}
105
- </span>
106
- )}
174
+ {/* App Type */}
175
+ <span className="rounded-full bg-secondary px-2.5 py-0.5 text-xs text-muted-foreground">
176
+ {getAppTypeLabel(project.appType)}
177
+ </span>
178
+
179
+ {/* Frontend Framework */}
180
+ {project.frontendFramework &&
181
+ project.frontendFramework !== FrontendFramework.NONE && (
182
+ <span className="inline-flex items-center gap-1 rounded-full bg-blue-500/10 text-blue-600 px-2.5 py-0.5 text-xs font-medium">
183
+ {getFrontendLabel(project.frontendFramework)}
184
+ </span>
185
+ )}
186
+
187
+ {/* Backend Framework */}
188
+ {project.backendFramework &&
189
+ project.backendFramework !== BackendFramework.NONE && (
190
+ <span className="inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-600 px-2.5 py-0.5 text-xs font-medium">
191
+ <Server className="h-3 w-3" />
192
+ {getBackendLabel(project.backendFramework)}
193
+ </span>
194
+ )}
195
+
196
+ {/* Database Provider */}
107
197
  {project.databaseProvider && (
108
- <span className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${
109
- project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
110
- ? "bg-blue-500/10 text-blue-600"
111
- : "bg-amber-500/10 text-amber-600"
112
- }`}>
198
+ <span
199
+ className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${
200
+ project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
201
+ ? "bg-blue-500/10 text-blue-600"
202
+ : "bg-amber-500/10 text-amber-600"
203
+ }`}
204
+ >
113
205
  <Database className="h-3 w-3" />
114
- {project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER ? "PostgreSQL" : "SQLite"}
206
+ {project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
207
+ ? "PostgreSQL"
208
+ : "SQLite"}
115
209
  </span>
116
210
  )}
117
- <span className="rounded-full bg-secondary px-2.5 py-0.5 text-xs text-muted-foreground">
118
- {project.projectType === ProjectType.WEB ? "Web" : "Native"}
119
- </span>
120
211
  </div>
121
212
  </div>
122
213
  </Card>
@@ -44,7 +44,8 @@ export const useProjectStore = create<ProjectState>((set, get) => ({
44
44
  {
45
45
  id: project.id,
46
46
  name: project.name,
47
- projectType: project.projectType,
47
+ appType: project.appType,
48
+ frontendFramework: project.frontendFramework,
48
49
  backendFramework: project.backendFramework,
49
50
  updatedAt: project.updatedAt,
50
51
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeship",
3
- "version": "0.2.24",
3
+ "version": "0.2.25",
4
4
  "description": "AI-Powered App Builder using Claude Code CLI",
5
5
  "bin": {
6
6
  "claudeship": "./bin/claudeship.js"
@@ -1,3 +1,4 @@
1
1
  export * from "./types/project";
2
2
  export * from "./types/chat";
3
3
  export * from "./types/preview";
4
+ export * from "./types/tech-stack";
@@ -1,3 +1,6 @@
1
+ import { FrontendFramework, TechStackConfig, AppType } from "./tech-stack";
2
+
3
+ // @deprecated Use AppType instead
1
4
  export enum ProjectType {
2
5
  WEB = "WEB",
3
6
  NATIVE = "NATIVE",
@@ -7,8 +10,13 @@ export enum BackendFramework {
7
10
  NONE = "NONE",
8
11
  EXPRESS = "EXPRESS",
9
12
  FASTAPI = "FASTAPI",
13
+ DJANGO = "DJANGO",
14
+ NESTJS = "NESTJS",
10
15
  }
11
16
 
17
+ export { FrontendFramework, AppType } from "./tech-stack";
18
+ export type { TechStackConfig } from "./tech-stack";
19
+
12
20
  export enum DatabaseProvider {
13
21
  POSTGRES_DOCKER = "POSTGRES_DOCKER",
14
22
  SQLITE = "SQLITE",
@@ -23,13 +31,18 @@ export const backendFrameworkLabels: Record<BackendFramework, string> = {
23
31
  [BackendFramework.NONE]: "프론트엔드 전용",
24
32
  [BackendFramework.EXPRESS]: "Express (Node.js)",
25
33
  [BackendFramework.FASTAPI]: "FastAPI (Python)",
34
+ [BackendFramework.DJANGO]: "Django (Python)",
35
+ [BackendFramework.NESTJS]: "NestJS (Node.js)",
26
36
  };
27
37
 
28
38
  export interface Project {
29
39
  id: string;
30
40
  name: string;
31
- projectType: ProjectType;
41
+ appType: AppType;
42
+ projectType: ProjectType; // @deprecated
43
+ frontendFramework: FrontendFramework;
32
44
  backendFramework: BackendFramework;
45
+ techStackConfig?: TechStackConfig;
33
46
  path: string;
34
47
  description?: string;
35
48
  createdAt: Date;
@@ -38,7 +51,8 @@ export interface Project {
38
51
 
39
52
  export interface CreateProjectInput {
40
53
  name: string;
41
- projectType: ProjectType;
54
+ appType: AppType;
55
+ frontendFramework?: FrontendFramework;
42
56
  backendFramework?: BackendFramework;
43
57
  description?: string;
44
58
  }
@@ -46,7 +60,8 @@ export interface CreateProjectInput {
46
60
  export interface ProjectListItem {
47
61
  id: string;
48
62
  name: string;
49
- projectType: ProjectType;
63
+ appType: AppType;
64
+ frontendFramework: FrontendFramework;
50
65
  backendFramework: BackendFramework;
51
66
  databaseProvider?: DatabaseProvider | null;
52
67
  updatedAt: Date;