gencow 0.1.4 → 0.1.6
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/bin/gencow-mcp.mjs +14 -36
- package/bin/gencow.mjs +64 -62
- package/package.json +2 -2
- package/server/index.js +45 -5447
- package/server/index.js.map +4 -4
- package/templates/ai-chat/README.md +23 -17
- package/templates/ai-chat/chat.ts +4 -0
- package/templates/ai-chat/prompt.md +19 -22
- package/templates/default/README.md +11 -18
- package/templates/default/index.ts +12 -0
- package/templates/fullstack/README.md +23 -17
- package/templates/fullstack/files.ts +4 -0
- package/templates/fullstack/prompt.md +24 -30
- package/templates/fullstack/tasks.ts +4 -0
- package/templates/task-app/README.md +23 -17
- package/templates/task-app/files.ts +4 -0
- package/templates/task-app/prompt.md +27 -46
- package/templates/task-app/tasks.ts +4 -0
package/bin/gencow-mcp.mjs
CHANGED
|
@@ -6,14 +6,12 @@
|
|
|
6
6
|
* MCP (Model Context Protocol) 서버를 stdio 전송으로 제공합니다.
|
|
7
7
|
*
|
|
8
8
|
* Resources:
|
|
9
|
-
* - gencow://
|
|
10
|
-
* - gencow://api-list → 등록된 API 경로 목록
|
|
9
|
+
* - gencow://api-list → 등록된 API 목록
|
|
11
10
|
*
|
|
12
11
|
* Tools:
|
|
13
12
|
* - list_apis → 등록된 query/mutation 목록
|
|
14
|
-
* - call_query → query 호출
|
|
15
|
-
* - call_mutation → mutation 호출
|
|
16
|
-
* - get_openapi_spec → OpenAPI 스펙 조회
|
|
13
|
+
* - call_query → query 호출 (POST /api/query)
|
|
14
|
+
* - call_mutation → mutation 호출 (POST /api/mutation)
|
|
17
15
|
*
|
|
18
16
|
* Usage:
|
|
19
17
|
* gencow mcp (기본 포트 5456)
|
|
@@ -67,12 +65,12 @@ const server = new Server(
|
|
|
67
65
|
const TOOLS = [
|
|
68
66
|
{
|
|
69
67
|
name: "list_apis",
|
|
70
|
-
description: "등록된 전체 API 목록을 조회합니다. 각 API의
|
|
68
|
+
description: "등록된 전체 API 목록을 조회합니다. 각 API의 이름과 타입(query/mutation)을 반환합니다.",
|
|
71
69
|
inputSchema: { type: "object", properties: {} },
|
|
72
70
|
},
|
|
73
71
|
{
|
|
74
72
|
name: "call_query",
|
|
75
|
-
description: "Gencow query를 호출합니다. 예: name='tasks.list', args={}.
|
|
73
|
+
description: "Gencow query를 호출합니다. 예: name='tasks.list', args={}. RPC 방식: POST /api/query",
|
|
76
74
|
inputSchema: {
|
|
77
75
|
type: "object",
|
|
78
76
|
required: ["name"],
|
|
@@ -84,7 +82,7 @@ const TOOLS = [
|
|
|
84
82
|
},
|
|
85
83
|
{
|
|
86
84
|
name: "call_mutation",
|
|
87
|
-
description: "Gencow mutation을 호출합니다. 예: name='tasks.create', args={ title: '새 태스크' }.
|
|
85
|
+
description: "Gencow mutation을 호출합니다. 예: name='tasks.create', args={ title: '새 태스크' }. RPC 방식: POST /api/mutation",
|
|
88
86
|
inputSchema: {
|
|
89
87
|
type: "object",
|
|
90
88
|
required: ["name"],
|
|
@@ -94,11 +92,6 @@ const TOOLS = [
|
|
|
94
92
|
},
|
|
95
93
|
},
|
|
96
94
|
},
|
|
97
|
-
{
|
|
98
|
-
name: "get_openapi_spec",
|
|
99
|
-
description: "OpenAPI 3.0 스펙을 JSON으로 반환합니다. API 구조, 요청/응답 스키마, 인증 방법을 포함합니다.",
|
|
100
|
-
inputSchema: { type: "object", properties: {} },
|
|
101
|
-
},
|
|
102
95
|
];
|
|
103
96
|
|
|
104
97
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
@@ -109,23 +102,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
109
102
|
try {
|
|
110
103
|
switch (toolName) {
|
|
111
104
|
case "list_apis": {
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
// Use the health endpoint which returns queries list
|
|
106
|
+
const info = await fetchJSON("/");
|
|
107
|
+
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
|
114
108
|
}
|
|
115
109
|
case "call_query": {
|
|
116
|
-
const
|
|
117
|
-
const result = await postJSON(path, toolArgs.args || {});
|
|
110
|
+
const result = await postJSON("/api/query", { name: toolArgs.name, args: toolArgs.args || {} });
|
|
118
111
|
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
119
112
|
}
|
|
120
113
|
case "call_mutation": {
|
|
121
|
-
const
|
|
122
|
-
const result = await postJSON(path, toolArgs.args || {});
|
|
114
|
+
const result = await postJSON("/api/mutation", { name: toolArgs.name, args: toolArgs.args || {} });
|
|
123
115
|
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
124
116
|
}
|
|
125
|
-
case "get_openapi_spec": {
|
|
126
|
-
const spec = await fetchJSON("/api/openapi.json");
|
|
127
|
-
return { content: [{ type: "text", text: JSON.stringify(spec, null, 2) }] };
|
|
128
|
-
}
|
|
129
117
|
default:
|
|
130
118
|
return { content: [{ type: "text", text: `Unknown tool: ${toolName}` }], isError: true };
|
|
131
119
|
}
|
|
@@ -137,16 +125,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
137
125
|
// ── Resources ───────────────────────────────────────────
|
|
138
126
|
|
|
139
127
|
const RESOURCES = [
|
|
140
|
-
{
|
|
141
|
-
uri: "gencow://openapi-spec",
|
|
142
|
-
name: "openapi-spec",
|
|
143
|
-
description: "Gencow OpenAPI 3.0 specification (auto-generated from registered queries/mutations)",
|
|
144
|
-
mimeType: "application/json",
|
|
145
|
-
},
|
|
146
128
|
{
|
|
147
129
|
uri: "gencow://api-list",
|
|
148
130
|
name: "api-list",
|
|
149
|
-
description: "List of all registered API
|
|
131
|
+
description: "List of all registered API queries and server information",
|
|
150
132
|
mimeType: "application/json",
|
|
151
133
|
},
|
|
152
134
|
];
|
|
@@ -157,13 +139,9 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
157
139
|
const { uri } = request.params;
|
|
158
140
|
|
|
159
141
|
switch (uri) {
|
|
160
|
-
case "gencow://openapi-spec": {
|
|
161
|
-
const spec = await fetchJSON("/api/openapi.json");
|
|
162
|
-
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(spec, null, 2) }] };
|
|
163
|
-
}
|
|
164
142
|
case "gencow://api-list": {
|
|
165
|
-
const
|
|
166
|
-
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(
|
|
143
|
+
const info = await fetchJSON("/");
|
|
144
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(info, null, 2) }] };
|
|
167
145
|
}
|
|
168
146
|
default:
|
|
169
147
|
throw new Error(`Unknown resource: ${uri}`);
|
package/bin/gencow.mjs
CHANGED
|
@@ -320,6 +320,9 @@ process.exit(0);
|
|
|
320
320
|
// Generate the AI vibe-coding README alongside api.ts
|
|
321
321
|
generateReadmeMd(config, apiObj);
|
|
322
322
|
|
|
323
|
+
// Update README with installed component docs (gencow add로 추가된 컴포넌트)
|
|
324
|
+
updateReadme(config);
|
|
325
|
+
|
|
323
326
|
|
|
324
327
|
} catch (e) {
|
|
325
328
|
warn(`API Codegen failed: ${e.message}`);
|
|
@@ -362,56 +365,37 @@ function generateReadmeMd(config, apiObj) {
|
|
|
362
365
|
md += `\n`;
|
|
363
366
|
}
|
|
364
367
|
|
|
365
|
-
// ── 1-1.
|
|
366
|
-
md += `---\n\n##
|
|
367
|
-
md += `>
|
|
368
|
-
md += `>
|
|
369
|
-
|
|
370
|
-
md +=
|
|
371
|
-
md +=
|
|
372
|
-
md +=
|
|
368
|
+
// ── 1-1. React 사용법 (Primary — 최상단 배치) ────────────
|
|
369
|
+
md += `---\n\n## ⚡ 데이터 사용법 (필수 — 반드시 이 방식을 사용하세요)\n\n`;
|
|
370
|
+
md += `> ⚠️ **\`useQuery\` / \`useMutation\` Hook을 반드시 사용하세요.**\n`;
|
|
371
|
+
md += `> Gencow는 WebSocket 실시간 동기화를 내장하고 있어, Hook을 사용하면 데이터가 **자동으로 동기화**됩니다.\n`;
|
|
372
|
+
md += `> \`fetch()\`를 직접 호출하거나 \`apiPost()\` 같은 래퍼를 만들지 마세요.\n\n`;
|
|
373
|
+
md += `\`\`\`typescript\n`;
|
|
374
|
+
md += `import { api } from "@/gencow/api"; // 자동 생성됨\n`;
|
|
375
|
+
md += `import { useQuery, useMutation } from "@gencow/react";\n\n`;
|
|
373
376
|
for (const [ns, fns] of Object.entries(apiObj)) {
|
|
374
|
-
|
|
375
|
-
md +=
|
|
377
|
+
if (fns.queries.length > 0) {
|
|
378
|
+
md += `// ${ns} 데이터 조회 (실시간 자동 갱신)\n`;
|
|
379
|
+
md += `const ${ns} = useQuery(api.${ns}.${fns.queries[0]}); // ${ns.charAt(0).toUpperCase() + ns.slice(1)}[] | undefined\n`;
|
|
376
380
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
md += `| 경로 | 메서드 | 설명 |\n`;
|
|
381
|
-
md += `| :--- | :--- | :--- |\n`;
|
|
382
|
-
for (const [ns, fns] of Object.entries(apiObj)) {
|
|
383
|
-
for (const m of fns.mutations) {
|
|
384
|
-
md += `| \`/api/fn/${ns}/${m}\` | POST | ${ns}.${m} |\n`;
|
|
381
|
+
if (fns.mutations.length > 0) {
|
|
382
|
+
md += `// ${ns} 데이터 변경 (로딩/에러 자동 관리)\n`;
|
|
383
|
+
md += `const [${fns.mutations[0]}${ns.charAt(0).toUpperCase() + ns.slice(1)}, isPending] = useMutation(api.${ns}.${fns.mutations[0]});\n`;
|
|
385
384
|
}
|
|
385
|
+
md += `\n`;
|
|
386
386
|
}
|
|
387
|
-
|
|
388
|
-
md +=
|
|
387
|
+
md += `\`\`\`\n\n`;
|
|
388
|
+
md += `### ❌ 절대 하지 마세요\n\n`;
|
|
389
389
|
md += `\`\`\`typescript\n`;
|
|
390
|
-
md += `//
|
|
391
|
-
md += `
|
|
392
|
-
md += `
|
|
393
|
-
md +=
|
|
394
|
-
md +=
|
|
395
|
-
md += `
|
|
396
|
-
md +=
|
|
397
|
-
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
398
|
-
md += ` credentials: "include",\n`;
|
|
399
|
-
md += ` body: JSON.stringify({}), // args\n`;
|
|
400
|
-
md += `});\n\n`;
|
|
401
|
-
if (Object.values(apiObj)[0]?.mutations?.length > 0) {
|
|
402
|
-
const firstMut = Object.values(apiObj)[0].mutations[0];
|
|
403
|
-
md += `// Mutation\n`;
|
|
404
|
-
md += `const res = await fetch("http://localhost:5456/api/fn/${namespaces[0]}/${firstMut}", {\n`;
|
|
405
|
-
md += ` method: "POST",\n`;
|
|
406
|
-
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
407
|
-
md += ` credentials: "include",\n`;
|
|
408
|
-
md += ` body: JSON.stringify({ /* args */ }),\n`;
|
|
409
|
-
md += `});\n`;
|
|
410
|
-
}
|
|
390
|
+
md += `// ❌ fetch()로 직접 API 호출하지 마세요\n`;
|
|
391
|
+
md += `fetch("/api/query", { body: JSON.stringify({ name: "tasks.create", args: {...} }) })\n`;
|
|
392
|
+
md += `apiPost("tasks/create", { ... }) // ❌ 래퍼도 만들지 마세요\n\n`;
|
|
393
|
+
md += `// ✅ useMutation을 사용하세요 — 실시간 동기화 + 로딩 상태 자동 관리\n`;
|
|
394
|
+
md += `const [create] = useMutation(api.tasks.create);\n`;
|
|
395
|
+
md += `await create({ title: "새 태스크" });\n`;
|
|
396
|
+
md += `// → 서버가 WebSocket으로 useQuery를 자동 갱신 — fetchTasks() 같은 수동 리페치 불필요!\n`;
|
|
411
397
|
md += `\`\`\`\n\n`;
|
|
412
398
|
|
|
413
|
-
md += `> **API 목록 자동 조회:** \`GET /api/fn\` 으로 등록된 전체 API 목록을 JSON으로 받을 수 있습니다.\n\n`;
|
|
414
|
-
|
|
415
399
|
// ── 1-2. Auth ────────────────────────────────────────────
|
|
416
400
|
md += `---\n\n## 🔐 인증 (better-auth, 세션 기반)\n\n`;
|
|
417
401
|
md += `| 엔드포인트 | 메서드 | 설명 |\n`;
|
|
@@ -421,22 +405,36 @@ function generateReadmeMd(config, apiObj) {
|
|
|
421
405
|
md += `| \`/api/auth/sign-out\` | POST | 로그아웃 |\n\n`;
|
|
422
406
|
md += `> ⚠️ 모든 API 요청에 \`credentials: "include"\`를 포함해야 합니다 (세션 쿠키 전송)\n`;
|
|
423
407
|
md += `> ⚠️ JWT 토큰이나 \`/auth/register\` 같은 커스텀 경로를 만들지 마세요.\n\n`;
|
|
424
|
-
|
|
408
|
+
|
|
409
|
+
// ── 1-3. RPC 직접 호출 (접힘 — 비-React 환경용) ──────────
|
|
410
|
+
md += `---\n\n`;
|
|
411
|
+
md += `<details>\n`;
|
|
412
|
+
md += `<summary>📡 RPC 직접 호출 (Node.js / cURL / 비-React 환경)</summary>\n\n`;
|
|
413
|
+
md += `> ⚠️ React에서는 이 방법을 사용하지 마세요. 위의 \`useQuery\`/\`useMutation\`을 사용하세요.\n\n`;
|
|
425
414
|
md += `\`\`\`typescript\n`;
|
|
426
|
-
md +=
|
|
427
|
-
md += `
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
415
|
+
md += `// Query 호출\n`;
|
|
416
|
+
md += `const res = await fetch("http://localhost:5456/api/query", {\n`;
|
|
417
|
+
md += ` method: "POST",\n`;
|
|
418
|
+
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
419
|
+
md += ` credentials: "include",\n`;
|
|
420
|
+
if (namespaces.length > 0 && Object.values(apiObj)[0]?.queries?.[0]) {
|
|
421
|
+
md += ` body: JSON.stringify({ name: "${namespaces[0]}.${Object.values(apiObj)[0].queries[0]}", args: {} }),\n`;
|
|
422
|
+
} else {
|
|
423
|
+
md += ` body: JSON.stringify({ name: "namespace.functionName", args: {} }),\n`;
|
|
424
|
+
}
|
|
425
|
+
md += `});\n\n`;
|
|
426
|
+
if (Object.values(apiObj)[0]?.mutations?.length > 0) {
|
|
427
|
+
const firstMut = Object.values(apiObj)[0].mutations[0];
|
|
428
|
+
md += `// Mutation 호출\n`;
|
|
429
|
+
md += `const res = await fetch("http://localhost:5456/api/mutation", {\n`;
|
|
430
|
+
md += ` method: "POST",\n`;
|
|
431
|
+
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
432
|
+
md += ` credentials: "include",\n`;
|
|
433
|
+
md += ` body: JSON.stringify({ name: "${namespaces[0]}.${firstMut}", args: { /* ... */ } }),\n`;
|
|
434
|
+
md += `});\n`;
|
|
438
435
|
}
|
|
439
436
|
md += `\`\`\`\n\n`;
|
|
437
|
+
md += `</details>\n\n`;
|
|
440
438
|
|
|
441
439
|
// ── 3. Copy-Paste AI Prompt ────────────────────────────
|
|
442
440
|
md += `---\n\n## 🤖 AI Vibe-Coding Prompt (복사해서 AI에게 전달)\n\n`;
|
|
@@ -450,11 +448,15 @@ function generateReadmeMd(config, apiObj) {
|
|
|
450
448
|
}
|
|
451
449
|
md += `\n`;
|
|
452
450
|
md += `React Hook 사용법:\n`;
|
|
453
|
-
md += ` useQuery(api.namespace.fnName) // 데이터
|
|
454
|
-
md += ` useMutation(api.namespace.fnName) // 데이터
|
|
451
|
+
md += ` useQuery(api.namespace.fnName) // 데이터 구독 (실시간 자동 갱신)\n`;
|
|
452
|
+
md += ` useMutation(api.namespace.fnName) // 데이터 변경 (로딩/에러 자동 관리)\n`;
|
|
453
|
+
md += `\n`;
|
|
454
|
+
md += `⚠️ 중요 규칙:\n`;
|
|
455
|
+
md += `- 반드시 useQuery와 useMutation을 사용해서 데이터와 연결해줘.\n`;
|
|
456
|
+
md += `- fetch()를 직접 호출하거나 apiPost() 같은 래퍼를 만들지 마.\n`;
|
|
457
|
+
md += `- gencow/api.ts는 자동 생성된 파일이야. 수동으로 만들지 마.\n`;
|
|
455
458
|
md += `\n`;
|
|
456
459
|
md += `위 API를 기반으로 Next.js + Tailwind CSS UI 컴포넌트를 만들어줘.\n`;
|
|
457
|
-
md += `- useQuery와 useMutation을 사용해서 실제 데이터와 연결해줘.\n`;
|
|
458
460
|
md += `- TypeScript 타입을 최대한 활용하고, 로딩/에러 상태도 처리해줘.\n`;
|
|
459
461
|
md += `\`\`\`\n\n`;
|
|
460
462
|
|
|
@@ -1764,7 +1766,7 @@ ${BOLD}${CYAN}Gencow Dev Remote${RESET}
|
|
|
1764
1766
|
}
|
|
1765
1767
|
|
|
1766
1768
|
// ── 모든 컴포넌트 설치 후 README 1회 업데이트 ───────────
|
|
1767
|
-
await updateReadme();
|
|
1769
|
+
await updateReadme(loadConfig());
|
|
1768
1770
|
|
|
1769
1771
|
log(`\n${GREEN}✔${RESET} ${BOLD}완료!${RESET} ${DIM}gencow dev를 실행하면 바로 사용할 수 있습니다.${RESET}\n`);
|
|
1770
1772
|
},
|
|
@@ -1965,10 +1967,10 @@ const reply = await ai.chat({
|
|
|
1965
1967
|
},
|
|
1966
1968
|
};
|
|
1967
1969
|
|
|
1968
|
-
async function updateReadme() {
|
|
1970
|
+
async function updateReadme(config) {
|
|
1969
1971
|
const fs = await import("fs");
|
|
1970
1972
|
const path = await import("path");
|
|
1971
|
-
const gencowDir = path.resolve(process.cwd(), "gencow");
|
|
1973
|
+
const gencowDir = path.resolve(process.cwd(), config?.functionsDir || "gencow");
|
|
1972
1974
|
const readmePath = path.resolve(gencowDir, "README.md");
|
|
1973
1975
|
|
|
1974
1976
|
if (!fs.existsSync(gencowDir)) return;
|
package/package.json
CHANGED