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.
@@ -6,14 +6,12 @@
6
6
  * MCP (Model Context Protocol) 서버를 stdio 전송으로 제공합니다.
7
7
  *
8
8
  * Resources:
9
- * - gencow://openapi-spec OpenAPI 3.0 JSON
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의 이름, 경로, 타입(query/mutation), 지원 HTTP 메서드를 반환합니다.",
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={}. 경로: POST /api/fn/{module}/{name}",
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: '새 태스크' }. 경로: POST /api/fn/{module}/{name}",
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
- const list = await fetchJSON("/api/fn");
113
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
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 path = "/api/fn/" + toolArgs.name.replace(/\./g, "/");
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 path = "/api/fn/" + toolArgs.name.replace(/\./g, "/");
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 endpoints with paths and methods",
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 list = await fetchJSON("/api/fn");
166
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(list, null, 2) }] };
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. REST API Direct Call ────────────────────────────
366
- md += `---\n\n## 🌐 REST API 직접 호출\n\n`;
367
- md += `> 📡 아래 REST 경로로 API를 호출하세요.\n`;
368
- md += `> 모든 요청에 \`credentials: "include"\` (쿠키)를 포함해야 합니다.\n\n`;
369
-
370
- md += `### Query (읽기 — GET 또는 POST)\n\n`;
371
- md += `| 경로 | 메서드 | 설명 |\n`;
372
- md += `| :--- | :--- | :--- |\n`;
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
- for (const q of fns.queries) {
375
- md += `| \`/api/fn/${ns}/${q}\` | GET, POST | ${ns}.${q} |\n`;
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
- md += `\n### Mutation (쓰기 POST만)\n\n`;
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 += `\n### 호출 예시\n\n`;
387
+ md += `\`\`\`\n\n`;
388
+ md += `### 절대 하지 마세요\n\n`;
389
389
  md += `\`\`\`typescript\n`;
390
- md += `// Query (GET)\n`;
391
- md += `const res = await fetch("http://localhost:5456/api/fn/${namespaces[0]}/${Object.values(apiObj)[0]?.queries?.[0] || "list"}", {\n`;
392
- md += ` credentials: "include", // 쿠키 전송 필수!\n`;
393
- md += `});\n\n`;
394
- md += `// Query (POST)\n`;
395
- md += `const res = await fetch("http://localhost:5456/api/fn/${namespaces[0]}/${Object.values(apiObj)[0]?.queries?.[0] || "list"}", {\n`;
396
- md += ` method: "POST",\n`;
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
- md += `---\n\n## ⚡ React 사용법 (Next.js / Vite)\n\n`;
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 += `import { api } from "@/gencow/api";\n`;
427
- md += `import { useQuery, useMutation } from "@gencow/react";\n\n`;
428
- for (const [ns, fns] of Object.entries(apiObj)) {
429
- if (fns.queries.length > 0) {
430
- md += `// ${ns} 데이터 조회\n`;
431
- md += `const ${ns} = useQuery(api.${ns}.${fns.queries[0]});\n`;
432
- }
433
- if (fns.mutations.length > 0) {
434
- md += `// ${ns} 데이터 변경\n`;
435
- md += `const [${fns.mutations[0]}${ns.charAt(0).toUpperCase() + ns.slice(1)}] = useMutation(api.${ns}.${fns.mutations[0]});\n`;
436
- }
437
- md += `\n`;
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) // 데이터 구독\n`;
454
- md += ` useMutation(api.namespace.fnName) // 데이터 변경\n`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Gencow — Backend OS for AI Agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,4 +28,4 @@
28
28
  "@types/node": "^25",
29
29
  "esbuild": "^0.27.3"
30
30
  }
31
- }
31
+ }