gencow 0.1.4 → 0.1.5
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 +58 -59
- package/package.json +29 -30
- package/server/index.js +6 -5441
- package/server/index.js.map +4 -4
- package/templates/ai-chat/README.md +23 -17
- package/templates/ai-chat/prompt.md +19 -22
- package/templates/default/README.md +11 -18
- package/templates/fullstack/README.md +23 -17
- package/templates/fullstack/prompt.md +24 -30
- package/templates/task-app/README.md +23 -17
- package/templates/task-app/prompt.md +27 -46
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
|
@@ -362,56 +362,37 @@ function generateReadmeMd(config, apiObj) {
|
|
|
362
362
|
md += `\n`;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
// ── 1-1.
|
|
366
|
-
md += `---\n\n##
|
|
367
|
-
md += `>
|
|
368
|
-
md += `>
|
|
369
|
-
|
|
370
|
-
md +=
|
|
371
|
-
md +=
|
|
372
|
-
md +=
|
|
365
|
+
// ── 1-1. React 사용법 (Primary — 최상단 배치) ────────────
|
|
366
|
+
md += `---\n\n## ⚡ 데이터 사용법 (필수 — 반드시 이 방식을 사용하세요)\n\n`;
|
|
367
|
+
md += `> ⚠️ **\`useQuery\` / \`useMutation\` Hook을 반드시 사용하세요.**\n`;
|
|
368
|
+
md += `> Gencow는 WebSocket 실시간 동기화를 내장하고 있어, Hook을 사용하면 데이터가 **자동으로 동기화**됩니다.\n`;
|
|
369
|
+
md += `> \`fetch()\`를 직접 호출하거나 \`apiPost()\` 같은 래퍼를 만들지 마세요.\n\n`;
|
|
370
|
+
md += `\`\`\`typescript\n`;
|
|
371
|
+
md += `import { api } from "@/gencow/api"; // 자동 생성됨\n`;
|
|
372
|
+
md += `import { useQuery, useMutation } from "@gencow/react";\n\n`;
|
|
373
373
|
for (const [ns, fns] of Object.entries(apiObj)) {
|
|
374
|
-
|
|
375
|
-
md +=
|
|
374
|
+
if (fns.queries.length > 0) {
|
|
375
|
+
md += `// ${ns} 데이터 조회 (실시간 자동 갱신)\n`;
|
|
376
|
+
md += `const ${ns} = useQuery(api.${ns}.${fns.queries[0]}); // ${ns.charAt(0).toUpperCase() + ns.slice(1)}[] | undefined\n`;
|
|
376
377
|
}
|
|
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`;
|
|
378
|
+
if (fns.mutations.length > 0) {
|
|
379
|
+
md += `// ${ns} 데이터 변경 (로딩/에러 자동 관리)\n`;
|
|
380
|
+
md += `const [${fns.mutations[0]}${ns.charAt(0).toUpperCase() + ns.slice(1)}, isPending] = useMutation(api.${ns}.${fns.mutations[0]});\n`;
|
|
385
381
|
}
|
|
382
|
+
md += `\n`;
|
|
386
383
|
}
|
|
387
|
-
|
|
388
|
-
md +=
|
|
384
|
+
md += `\`\`\`\n\n`;
|
|
385
|
+
md += `### ❌ 절대 하지 마세요\n\n`;
|
|
389
386
|
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
|
-
}
|
|
387
|
+
md += `// ❌ fetch()로 직접 API 호출하지 마세요\n`;
|
|
388
|
+
md += `fetch("/api/query", { body: JSON.stringify({ name: "tasks.create", args: {...} }) })\n`;
|
|
389
|
+
md += `apiPost("tasks/create", { ... }) // ❌ 래퍼도 만들지 마세요\n\n`;
|
|
390
|
+
md += `// ✅ useMutation을 사용하세요 — 실시간 동기화 + 로딩 상태 자동 관리\n`;
|
|
391
|
+
md += `const [create] = useMutation(api.tasks.create);\n`;
|
|
392
|
+
md += `await create({ title: "새 태스크" });\n`;
|
|
393
|
+
md += `// → 서버가 WebSocket으로 useQuery를 자동 갱신 — fetchTasks() 같은 수동 리페치 불필요!\n`;
|
|
411
394
|
md += `\`\`\`\n\n`;
|
|
412
395
|
|
|
413
|
-
md += `> **API 목록 자동 조회:** \`GET /api/fn\` 으로 등록된 전체 API 목록을 JSON으로 받을 수 있습니다.\n\n`;
|
|
414
|
-
|
|
415
396
|
// ── 1-2. Auth ────────────────────────────────────────────
|
|
416
397
|
md += `---\n\n## 🔐 인증 (better-auth, 세션 기반)\n\n`;
|
|
417
398
|
md += `| 엔드포인트 | 메서드 | 설명 |\n`;
|
|
@@ -421,22 +402,36 @@ function generateReadmeMd(config, apiObj) {
|
|
|
421
402
|
md += `| \`/api/auth/sign-out\` | POST | 로그아웃 |\n\n`;
|
|
422
403
|
md += `> ⚠️ 모든 API 요청에 \`credentials: "include"\`를 포함해야 합니다 (세션 쿠키 전송)\n`;
|
|
423
404
|
md += `> ⚠️ JWT 토큰이나 \`/auth/register\` 같은 커스텀 경로를 만들지 마세요.\n\n`;
|
|
424
|
-
|
|
405
|
+
|
|
406
|
+
// ── 1-3. RPC 직접 호출 (접힘 — 비-React 환경용) ──────────
|
|
407
|
+
md += `---\n\n`;
|
|
408
|
+
md += `<details>\n`;
|
|
409
|
+
md += `<summary>📡 RPC 직접 호출 (Node.js / cURL / 비-React 환경)</summary>\n\n`;
|
|
410
|
+
md += `> ⚠️ React에서는 이 방법을 사용하지 마세요. 위의 \`useQuery\`/\`useMutation\`을 사용하세요.\n\n`;
|
|
425
411
|
md += `\`\`\`typescript\n`;
|
|
426
|
-
md +=
|
|
427
|
-
md += `
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
412
|
+
md += `// Query 호출\n`;
|
|
413
|
+
md += `const res = await fetch("http://localhost:5456/api/query", {\n`;
|
|
414
|
+
md += ` method: "POST",\n`;
|
|
415
|
+
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
416
|
+
md += ` credentials: "include",\n`;
|
|
417
|
+
if (namespaces.length > 0 && Object.values(apiObj)[0]?.queries?.[0]) {
|
|
418
|
+
md += ` body: JSON.stringify({ name: "${namespaces[0]}.${Object.values(apiObj)[0].queries[0]}", args: {} }),\n`;
|
|
419
|
+
} else {
|
|
420
|
+
md += ` body: JSON.stringify({ name: "namespace.functionName", args: {} }),\n`;
|
|
421
|
+
}
|
|
422
|
+
md += `});\n\n`;
|
|
423
|
+
if (Object.values(apiObj)[0]?.mutations?.length > 0) {
|
|
424
|
+
const firstMut = Object.values(apiObj)[0].mutations[0];
|
|
425
|
+
md += `// Mutation 호출\n`;
|
|
426
|
+
md += `const res = await fetch("http://localhost:5456/api/mutation", {\n`;
|
|
427
|
+
md += ` method: "POST",\n`;
|
|
428
|
+
md += ` headers: { "Content-Type": "application/json" },\n`;
|
|
429
|
+
md += ` credentials: "include",\n`;
|
|
430
|
+
md += ` body: JSON.stringify({ name: "${namespaces[0]}.${firstMut}", args: { /* ... */ } }),\n`;
|
|
431
|
+
md += `});\n`;
|
|
438
432
|
}
|
|
439
433
|
md += `\`\`\`\n\n`;
|
|
434
|
+
md += `</details>\n\n`;
|
|
440
435
|
|
|
441
436
|
// ── 3. Copy-Paste AI Prompt ────────────────────────────
|
|
442
437
|
md += `---\n\n## 🤖 AI Vibe-Coding Prompt (복사해서 AI에게 전달)\n\n`;
|
|
@@ -450,11 +445,15 @@ function generateReadmeMd(config, apiObj) {
|
|
|
450
445
|
}
|
|
451
446
|
md += `\n`;
|
|
452
447
|
md += `React Hook 사용법:\n`;
|
|
453
|
-
md += ` useQuery(api.namespace.fnName) // 데이터
|
|
454
|
-
md += ` useMutation(api.namespace.fnName) // 데이터
|
|
448
|
+
md += ` useQuery(api.namespace.fnName) // 데이터 구독 (실시간 자동 갱신)\n`;
|
|
449
|
+
md += ` useMutation(api.namespace.fnName) // 데이터 변경 (로딩/에러 자동 관리)\n`;
|
|
450
|
+
md += `\n`;
|
|
451
|
+
md += `⚠️ 중요 규칙:\n`;
|
|
452
|
+
md += `- 반드시 useQuery와 useMutation을 사용해서 데이터와 연결해줘.\n`;
|
|
453
|
+
md += `- fetch()를 직접 호출하거나 apiPost() 같은 래퍼를 만들지 마.\n`;
|
|
454
|
+
md += `- gencow/api.ts는 자동 생성된 파일이야. 수동으로 만들지 마.\n`;
|
|
455
455
|
md += `\n`;
|
|
456
456
|
md += `위 API를 기반으로 Next.js + Tailwind CSS UI 컴포넌트를 만들어줘.\n`;
|
|
457
|
-
md += `- useQuery와 useMutation을 사용해서 실제 데이터와 연결해줘.\n`;
|
|
458
457
|
md += `- TypeScript 타입을 최대한 활용하고, 로딩/에러 상태도 처리해줘.\n`;
|
|
459
458
|
md += `\`\`\`\n\n`;
|
|
460
459
|
|
package/package.json
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
2
|
+
"name": "gencow",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Gencow — Backend OS for AI Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gencow": "./bin/gencow.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"templates/",
|
|
12
|
+
"server/",
|
|
13
|
+
"scripts/",
|
|
14
|
+
"dashboard/"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
18
|
+
"open": "^10.1.0",
|
|
19
|
+
"tar": "^7",
|
|
20
|
+
"ws": "^8.19.0",
|
|
21
|
+
"zod": "^4.3.6"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25",
|
|
25
|
+
"esbuild": "^0.27.3"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node scripts/bundle-server.mjs"
|
|
29
|
+
}
|
|
30
|
+
}
|