gencow 0.1.7 → 0.1.8
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.mjs +709 -33
- package/dashboard/404/index.html +1 -1
- package/dashboard/404.html +1 -1
- package/dashboard/__next.__PAGE__.txt +2 -2
- package/dashboard/__next._full.txt +12 -12
- package/dashboard/__next._head.txt +4 -4
- package/dashboard/__next._index.txt +7 -7
- package/dashboard/__next._tree.txt +1 -1
- package/dashboard/_next/static/chunks/260e9d64f07248c4.js +1 -0
- package/dashboard/_next/static/chunks/{33074d3ff5d0cd2d.js → 29f56225c1ab40ff.js} +1 -1
- package/dashboard/_next/static/chunks/35f92271baf1e273.js +2 -0
- package/dashboard/_next/static/chunks/46e5e8d7b7abb32f.js +1 -0
- package/dashboard/_next/static/chunks/544729b1d14e133f.js +1 -0
- package/dashboard/_next/static/chunks/5b9b63f901e3a179.js +1 -0
- package/dashboard/_next/static/chunks/5f1dc5123f43f954.js +1 -0
- package/dashboard/_next/static/chunks/69c2a0d587153425.js +1 -0
- package/dashboard/_next/static/chunks/6dd7133fff916008.js +1 -0
- package/dashboard/_next/static/chunks/6fa4d7b0382eb69a.js +4 -0
- package/dashboard/_next/static/chunks/{9fe49c7e68147829.js → 6faf0a47507abee5.js} +1 -1
- package/dashboard/_next/static/chunks/{c21e3239bd705e2c.js → 806eb71c7bda3038.js} +1 -1
- package/dashboard/_next/static/chunks/8157631bf108576e.js +1 -0
- package/dashboard/_next/static/chunks/967d50dc43e363ee.js +1 -0
- package/dashboard/_next/static/chunks/a6d4e29fef94fdae.js +1 -0
- package/dashboard/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
- package/dashboard/_next/static/chunks/{90c074f7c3ac4f8f.js → bf1d0c48a304cdf5.js} +1 -1
- package/dashboard/_next/static/chunks/{b929becb73e54029.js → c0d4e7aa4b1e8cd6.js} +23 -23
- package/dashboard/_next/static/chunks/{c8e3802643f7a4fb.js → ca7b9255bef4d853.js} +1 -1
- package/dashboard/_next/static/chunks/{34d22de4090e001f.js → d8490871b9f9d51f.js} +1 -1
- package/dashboard/_next/static/chunks/{turbopack-e4c70080f9797654.js → turbopack-00aed88e1fb5f17d.js} +1 -1
- package/dashboard/_not-found/__next._full.txt +12 -12
- package/dashboard/_not-found/__next._head.txt +4 -4
- package/dashboard/_not-found/__next._index.txt +7 -7
- package/dashboard/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/dashboard/_not-found/__next._not-found.txt +3 -3
- package/dashboard/_not-found/__next._tree.txt +1 -1
- package/dashboard/_not-found/index.html +1 -1
- package/dashboard/_not-found/index.txt +12 -12
- package/dashboard/auth/__next._full.txt +20 -17
- package/dashboard/auth/__next._head.txt +4 -4
- package/dashboard/auth/__next._index.txt +7 -7
- package/dashboard/auth/__next._tree.txt +1 -1
- package/dashboard/auth/__next.auth.__PAGE__.txt +8 -5
- package/dashboard/auth/__next.auth.txt +3 -3
- package/dashboard/auth/index.html +1 -1
- package/dashboard/auth/index.txt +20 -17
- package/dashboard/data/__next._full.txt +20 -17
- package/dashboard/data/__next._head.txt +4 -4
- package/dashboard/data/__next._index.txt +7 -7
- package/dashboard/data/__next._tree.txt +1 -1
- package/dashboard/data/__next.data.__PAGE__.txt +8 -5
- package/dashboard/data/__next.data.txt +3 -3
- package/dashboard/data/index.html +1 -1
- package/dashboard/data/index.txt +20 -17
- package/dashboard/files/__next._full.txt +13 -13
- package/dashboard/files/__next._head.txt +4 -4
- package/dashboard/files/__next._index.txt +7 -7
- package/dashboard/files/__next._tree.txt +1 -1
- package/dashboard/files/__next.files.__PAGE__.txt +3 -3
- package/dashboard/files/__next.files.txt +3 -3
- package/dashboard/files/index.html +1 -1
- package/dashboard/files/index.txt +13 -13
- package/dashboard/functions/__next._full.txt +13 -13
- package/dashboard/functions/__next._head.txt +4 -4
- package/dashboard/functions/__next._index.txt +7 -7
- package/dashboard/functions/__next._tree.txt +1 -1
- package/dashboard/functions/__next.functions.__PAGE__.txt +3 -3
- package/dashboard/functions/__next.functions.txt +3 -3
- package/dashboard/functions/index.html +1 -1
- package/dashboard/functions/index.txt +13 -13
- package/dashboard/index.html +1 -1
- package/dashboard/index.txt +12 -12
- package/dashboard/logs/__next._full.txt +13 -13
- package/dashboard/logs/__next._head.txt +4 -4
- package/dashboard/logs/__next._index.txt +7 -7
- package/dashboard/logs/__next._tree.txt +1 -1
- package/dashboard/logs/__next.logs.__PAGE__.txt +3 -3
- package/dashboard/logs/__next.logs.txt +3 -3
- package/dashboard/logs/index.html +1 -1
- package/dashboard/logs/index.txt +13 -13
- package/dashboard/scheduler/__next._full.txt +13 -13
- package/dashboard/scheduler/__next._head.txt +4 -4
- package/dashboard/scheduler/__next._index.txt +7 -7
- package/dashboard/scheduler/__next._tree.txt +1 -1
- package/dashboard/scheduler/__next.scheduler.__PAGE__.txt +3 -3
- package/dashboard/scheduler/__next.scheduler.txt +3 -3
- package/dashboard/scheduler/index.html +1 -1
- package/dashboard/scheduler/index.txt +13 -13
- package/dashboard/settings/__next._full.txt +14 -14
- package/dashboard/settings/__next._head.txt +4 -4
- package/dashboard/settings/__next._index.txt +7 -7
- package/dashboard/settings/__next._tree.txt +1 -1
- package/dashboard/settings/__next.settings.__PAGE__.txt +4 -4
- package/dashboard/settings/__next.settings.txt +3 -3
- package/dashboard/settings/index.html +1 -1
- package/dashboard/settings/index.txt +14 -14
- package/package.json +28 -29
- package/server/index.js +117 -0
- package/server/index.js.map +2 -2
- package/templates/ai.ts +154 -1
- package/templates/guardrails.ts +150 -0
- package/templates/parsers.ts +66 -0
- package/templates/prompts.ts +67 -0
- package/templates/rag.ts +188 -15
- package/templates/reranker.ts +95 -0
- package/dashboard/_next/static/chunks/1aa11153294820b1.js +0 -2
- package/dashboard/_next/static/chunks/3caf31dfdffb8e2e.js +0 -1
- package/dashboard/_next/static/chunks/49781d4788479f87.js +0 -4
- package/dashboard/_next/static/chunks/4f24965710e8e4cf.js +0 -1
- package/dashboard/_next/static/chunks/59607c439bb52384.js +0 -1
- package/dashboard/_next/static/chunks/8782022a9c17db71.js +0 -1
- package/dashboard/_next/static/chunks/8b72cfc40036c827.js +0 -1
- package/dashboard/_next/static/chunks/c332b20d9c29ae24.js +0 -1
- package/dashboard/_next/static/chunks/c4d90098b4abc498.js +0 -1
- /package/dashboard/_next/static/{2JoiCV-utsniJgNPfseeo → 7pe-AOGjyxk4S2bX5nhkB}/_buildManifest.js +0 -0
- /package/dashboard/_next/static/{2JoiCV-utsniJgNPfseeo → 7pe-AOGjyxk4S2bX5nhkB}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/_next/static/{2JoiCV-utsniJgNPfseeo → 7pe-AOGjyxk4S2bX5nhkB}/_ssgManifest.js +0 -0
package/bin/gencow.mjs
CHANGED
|
@@ -1185,16 +1185,16 @@ ${BOLD}Dev commands:${RESET}
|
|
|
1185
1185
|
${GREEN}mcp${RESET} Start MCP server for AI agent integration ${DIM}(stdio)${RESET}
|
|
1186
1186
|
|
|
1187
1187
|
${BOLD}BaaS commands (login required):${RESET}
|
|
1188
|
-
${GREEN}login${RESET} Login to Gencow Platform
|
|
1188
|
+
${GREEN}login${RESET} Login to Gencow Platform ${DIM}(browser → token)${RESET}
|
|
1189
1189
|
${GREEN}logout${RESET} Clear credentials
|
|
1190
|
-
${GREEN}
|
|
1191
|
-
${GREEN}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
${GREEN}
|
|
1195
|
-
${GREEN}
|
|
1196
|
-
${GREEN}
|
|
1197
|
-
${GREEN}
|
|
1190
|
+
${GREEN}whoami${RESET} Show current user info
|
|
1191
|
+
${GREEN}deploy${RESET} Bundle gencow/ and deploy to platform
|
|
1192
|
+
${DIM}--prod Deploy to production (confirmation required)${RESET}
|
|
1193
|
+
${DIM}--name, -n Specify app name${RESET}
|
|
1194
|
+
${GREEN}env list${RESET} List remote env vars ${DIM}(--prod for production)${RESET}
|
|
1195
|
+
${GREEN}env set K=V${RESET} Set remote env var
|
|
1196
|
+
${GREEN}env unset KEY${RESET} Remove remote env var
|
|
1197
|
+
${GREEN}env push${RESET} Push local .env to remote
|
|
1198
1198
|
|
|
1199
1199
|
${BOLD}Production:${RESET}
|
|
1200
1200
|
${GREEN}deploy${RESET} Build Docker image + start production stack
|
|
@@ -1211,8 +1211,488 @@ ${BOLD}Examples:${RESET}
|
|
|
1211
1211
|
`);
|
|
1212
1212
|
},
|
|
1213
1213
|
|
|
1214
|
-
// ── login
|
|
1215
|
-
|
|
1214
|
+
// ── login — Device Auth 패턴 ──────────────────────────
|
|
1215
|
+
async login() {
|
|
1216
|
+
log(`\n${BOLD}${CYAN}Gencow Login${RESET}\n`);
|
|
1217
|
+
|
|
1218
|
+
const platformUrl = process.env.GENCOW_PLATFORM_URL || "https://gencow.dev";
|
|
1219
|
+
info(`Platform: ${platformUrl}`);
|
|
1220
|
+
|
|
1221
|
+
// 1. auth-start → 인증 코드 생성
|
|
1222
|
+
info("인증 코드 요청 중...");
|
|
1223
|
+
let authData;
|
|
1224
|
+
try {
|
|
1225
|
+
const res = await fetch(`${platformUrl}/platform/cli/auth-start`, {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: { "Content-Type": "application/json" },
|
|
1228
|
+
});
|
|
1229
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1230
|
+
authData = await res.json();
|
|
1231
|
+
} catch (e) {
|
|
1232
|
+
error(`Platform 서버 연결 실패: ${e.message}`);
|
|
1233
|
+
error(`Platform URL: ${platformUrl}`);
|
|
1234
|
+
error(`서버가 실행 중인지 확인하세요.`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
log("");
|
|
1239
|
+
log(` ${BOLD}인증 코드: ${CYAN}${authData.code}${RESET}`);
|
|
1240
|
+
log("");
|
|
1241
|
+
info(`브라우저에서 로그인하세요:`);
|
|
1242
|
+
log(` ${CYAN}${authData.url}${RESET}`);
|
|
1243
|
+
log("");
|
|
1244
|
+
|
|
1245
|
+
// 2. 브라우저 오픈
|
|
1246
|
+
try {
|
|
1247
|
+
const { exec } = await import("child_process");
|
|
1248
|
+
const openCmd = process.platform === "darwin" ? "open" :
|
|
1249
|
+
process.platform === "win32" ? "start" : "xdg-open";
|
|
1250
|
+
exec(`${openCmd} "${authData.url}"`);
|
|
1251
|
+
} catch {
|
|
1252
|
+
warn("브라우저를 자동으로 열 수 없습니다. 위 URL을 직접 열어주세요.");
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// 3. 폴링 — 2초마다 인증 완료 확인
|
|
1256
|
+
info("로그인 대기 중...");
|
|
1257
|
+
const POLL_INTERVAL = 2000;
|
|
1258
|
+
const MAX_POLLS = 150; // 10분 내 5분 = 150회
|
|
1259
|
+
|
|
1260
|
+
for (let i = 0; i < MAX_POLLS; i++) {
|
|
1261
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
1262
|
+
|
|
1263
|
+
try {
|
|
1264
|
+
const res = await fetch(`${platformUrl}/platform/cli/auth-poll`, {
|
|
1265
|
+
method: "POST",
|
|
1266
|
+
headers: { "Content-Type": "application/json" },
|
|
1267
|
+
body: JSON.stringify({ code: authData.code }),
|
|
1268
|
+
});
|
|
1269
|
+
const data = await res.json();
|
|
1270
|
+
|
|
1271
|
+
if (data.status === "completed") {
|
|
1272
|
+
// 4. 토큰 저장
|
|
1273
|
+
saveCreds({
|
|
1274
|
+
apiKey: data.token,
|
|
1275
|
+
userId: data.userId,
|
|
1276
|
+
platformUrl,
|
|
1277
|
+
loginAt: new Date().toISOString(),
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
log("");
|
|
1281
|
+
success(`로그인 완료!`);
|
|
1282
|
+
info(`토큰 저장됨: ${DIM}~/.gencow/credentials.json${RESET}`);
|
|
1283
|
+
log("");
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (data.status === "expired") {
|
|
1288
|
+
error("인증 코드가 만료되었습니다. 다시 시도하세요: gencow login");
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// pending — 계속 폴링
|
|
1293
|
+
process.stdout.write(".");
|
|
1294
|
+
} catch {
|
|
1295
|
+
// 네트워크 오류 — 재시도
|
|
1296
|
+
process.stdout.write("x");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
error("\n타임아웃. 다시 시도하세요: gencow login");
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
},
|
|
1303
|
+
|
|
1304
|
+
// ── logout ────────────────────────────────────────────
|
|
1305
|
+
async logout() {
|
|
1306
|
+
try {
|
|
1307
|
+
unlinkSync(CREDS_PATH);
|
|
1308
|
+
success("로그아웃 완료. 자격 증명이 삭제되었습니다.");
|
|
1309
|
+
} catch {
|
|
1310
|
+
info("이미 로그아웃 상태입니다.");
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
|
|
1314
|
+
// ── whoami ────────────────────────────────────────────
|
|
1315
|
+
async whoami() {
|
|
1316
|
+
const creds = loadCreds();
|
|
1317
|
+
if (!creds?.apiKey) {
|
|
1318
|
+
error("로그인하지 않았습니다.");
|
|
1319
|
+
info("실행: gencow login");
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
try {
|
|
1324
|
+
const res = await fetch(`${creds.platformUrl}/platform/me`, {
|
|
1325
|
+
headers: { "Authorization": `Bearer ${creds.apiKey}` },
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
if (!res.ok) {
|
|
1329
|
+
error("토큰이 만료되었거나 유효하지 않습니다.");
|
|
1330
|
+
info("실행: gencow login");
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const data = await res.json();
|
|
1335
|
+
log(`\n${BOLD}${CYAN}Gencow CLI${RESET}\n`);
|
|
1336
|
+
info(`User ID: ${data.userId}`);
|
|
1337
|
+
info(`Platform: ${creds.platformUrl}`);
|
|
1338
|
+
info(`Token: ${creds.apiKey.slice(0, 10)}...${creds.apiKey.slice(-4)}`);
|
|
1339
|
+
info(`Expires: ${new Date(data.expiresAt).toLocaleString()}`);
|
|
1340
|
+
log("");
|
|
1341
|
+
} catch (e) {
|
|
1342
|
+
error(`서버 연결 실패: ${e.message}`);
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
|
|
1346
|
+
// ── deploy ────────────────────────────────────────────
|
|
1347
|
+
async deploy(...deployArgs) {
|
|
1348
|
+
const creds = requireCreds();
|
|
1349
|
+
|
|
1350
|
+
// gencow.json 또는 gencow.config.ts에서 앱 이름 확인
|
|
1351
|
+
let appName = null;
|
|
1352
|
+
let envTarget = "dev";
|
|
1353
|
+
|
|
1354
|
+
for (let i = 0; i < deployArgs.length; i++) {
|
|
1355
|
+
const a = deployArgs[i];
|
|
1356
|
+
if (a === "--prod") envTarget = "prod";
|
|
1357
|
+
else if (a === "--name" || a === "-n") appName = deployArgs[++i];
|
|
1358
|
+
else if (!a.startsWith("-")) appName = a;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// gencow.json에서 앱 이름 로드
|
|
1362
|
+
const gencowJsonPath = resolve(process.cwd(), "gencow.json");
|
|
1363
|
+
if (!appName && existsSync(gencowJsonPath)) {
|
|
1364
|
+
const gencowJson = JSON.parse(readFileSync(gencowJsonPath, "utf8"));
|
|
1365
|
+
appName = gencowJson.appName;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
if (!appName) {
|
|
1369
|
+
// gencow.config.ts에서 프로젝트 이름 추출 시도
|
|
1370
|
+
const configPath = resolve(process.cwd(), "gencow.config.ts");
|
|
1371
|
+
if (existsSync(configPath)) {
|
|
1372
|
+
const content = readFileSync(configPath, "utf8");
|
|
1373
|
+
const match = content.match(/name:\s*["']([^"']+)["']/);
|
|
1374
|
+
if (match) appName = match[1];
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (!appName) {
|
|
1379
|
+
// 디렉토리 이름을 앱 이름으로 사용
|
|
1380
|
+
appName = basename(process.cwd());
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
log(`\n${BOLD}${CYAN}Gencow Deploy${RESET}\n`);
|
|
1384
|
+
info(`앱: ${appName}`);
|
|
1385
|
+
info(`환경: ${envTarget}`);
|
|
1386
|
+
info(`포맷: tar.gz`);
|
|
1387
|
+
log("");
|
|
1388
|
+
|
|
1389
|
+
// 프로덕션 배포 확인
|
|
1390
|
+
if (envTarget === "prod") {
|
|
1391
|
+
const { createInterface } = await import("readline");
|
|
1392
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1393
|
+
const answer = await new Promise(resolve => {
|
|
1394
|
+
rl.question(` ${YELLOW}⚠${RESET} 프로덕션 배포를 진행하시겠습니까? (y/N) `, resolve);
|
|
1395
|
+
});
|
|
1396
|
+
rl.close();
|
|
1397
|
+
if (answer.toLowerCase() !== "y") {
|
|
1398
|
+
info("배포 취소됨.");
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// 1. tar.gz 패키징
|
|
1404
|
+
info("프로젝트 패키징 중...");
|
|
1405
|
+
const { execSync: exec } = await import("child_process");
|
|
1406
|
+
const tmpBundle = resolve(process.cwd(), ".gencow", "deploy-bundle.tar.gz");
|
|
1407
|
+
mkdirSync(dirname(tmpBundle), { recursive: true });
|
|
1408
|
+
|
|
1409
|
+
// gencow/ 폴더 + package.json + .env (있으면) 패키징
|
|
1410
|
+
const filesToPack = ["gencow/", "package.json"];
|
|
1411
|
+
if (existsSync(resolve(process.cwd(), "bun.lockb"))) filesToPack.push("bun.lockb");
|
|
1412
|
+
if (existsSync(resolve(process.cwd(), "package-lock.json"))) filesToPack.push("package-lock.json");
|
|
1413
|
+
if (existsSync(resolve(process.cwd(), "tsconfig.json"))) filesToPack.push("tsconfig.json");
|
|
1414
|
+
|
|
1415
|
+
// gencow/ 디렉토리 존재 확인
|
|
1416
|
+
if (!existsSync(resolve(process.cwd(), "gencow"))) {
|
|
1417
|
+
error("gencow/ 디렉토리가 없습니다. Gencow 프로젝트 루트에서 실행하세요.");
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
try {
|
|
1422
|
+
exec(`tar -czf "${tmpBundle}" ${filesToPack.join(" ")}`, { cwd: process.cwd() });
|
|
1423
|
+
} catch (e) {
|
|
1424
|
+
error(`패키징 실패: ${e.message}`);
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const bundleSize = statSync(tmpBundle).size;
|
|
1429
|
+
success(`번들 생성: ${(bundleSize / 1024).toFixed(1)} KB`);
|
|
1430
|
+
|
|
1431
|
+
// 2. 앱 생성 or 확인
|
|
1432
|
+
info("앱 상태 확인 중...");
|
|
1433
|
+
const checkRes = await platformFetch(creds, `/platform/apps/${appName}/deploy`, { method: "HEAD" }).catch(() => null);
|
|
1434
|
+
|
|
1435
|
+
// 앱이 없으면 먼저 생성 (RPC mutation 호출)
|
|
1436
|
+
// Note: deploy API는 앱이 이미 존재해야 함
|
|
1437
|
+
// 여기서는 일단 deploy 시도 — 404면 앱 생성 후 재시도
|
|
1438
|
+
|
|
1439
|
+
// 3. 업로드
|
|
1440
|
+
info("배포 중...");
|
|
1441
|
+
const bundleBuffer = readFileSync(tmpBundle);
|
|
1442
|
+
|
|
1443
|
+
const deployRes = await fetch(
|
|
1444
|
+
`${creds.platformUrl}/platform/apps/${appName}/deploy?env=${envTarget}`,
|
|
1445
|
+
{
|
|
1446
|
+
method: "POST",
|
|
1447
|
+
headers: {
|
|
1448
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
1449
|
+
"Content-Type": "application/octet-stream",
|
|
1450
|
+
},
|
|
1451
|
+
body: bundleBuffer,
|
|
1452
|
+
}
|
|
1453
|
+
);
|
|
1454
|
+
|
|
1455
|
+
// 임시 파일 정리
|
|
1456
|
+
try { unlinkSync(tmpBundle); } catch { }
|
|
1457
|
+
|
|
1458
|
+
if (!deployRes.ok) {
|
|
1459
|
+
const errData = await deployRes.json().catch(() => ({}));
|
|
1460
|
+
|
|
1461
|
+
// 앱이 없으면 생성 후 재시도
|
|
1462
|
+
if (deployRes.status === 404) {
|
|
1463
|
+
warn(`앱 "${appName}"이 아직 없습니다. 생성합니다...`);
|
|
1464
|
+
|
|
1465
|
+
const createRes = await fetch(`${creds.platformUrl}/api/mutation`, {
|
|
1466
|
+
method: "POST",
|
|
1467
|
+
headers: {
|
|
1468
|
+
"Content-Type": "application/json",
|
|
1469
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
1470
|
+
},
|
|
1471
|
+
body: JSON.stringify({ name: "apps.create", args: { name: appName } }),
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
if (!createRes.ok) {
|
|
1475
|
+
const createErr = await createRes.json().catch(() => ({}));
|
|
1476
|
+
error(`앱 생성 실패: ${createErr.error || createRes.statusText}`);
|
|
1477
|
+
process.exit(1);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
success(`앱 "${appName}" 생성 완료. 프로비저닝 대기 중...`);
|
|
1481
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
1482
|
+
|
|
1483
|
+
// 재배포
|
|
1484
|
+
info("재배포 시도...");
|
|
1485
|
+
// tar.gz를 다시 만들어야 함 (위에서 삭제했으므로)
|
|
1486
|
+
exec(`tar -czf "${tmpBundle}" ${filesToPack.join(" ")}`, { cwd: process.cwd() });
|
|
1487
|
+
const retryBuffer = readFileSync(tmpBundle);
|
|
1488
|
+
const retryRes = await fetch(
|
|
1489
|
+
`${creds.platformUrl}/platform/apps/${appName}/deploy?env=${envTarget}`,
|
|
1490
|
+
{
|
|
1491
|
+
method: "POST",
|
|
1492
|
+
headers: {
|
|
1493
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
1494
|
+
"Content-Type": "application/octet-stream",
|
|
1495
|
+
},
|
|
1496
|
+
body: retryBuffer,
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
try { unlinkSync(tmpBundle); } catch { }
|
|
1500
|
+
|
|
1501
|
+
if (!retryRes.ok) {
|
|
1502
|
+
const retryErr = await retryRes.json().catch(() => ({}));
|
|
1503
|
+
error(`배포 실패: ${retryErr.error || retryRes.statusText}`);
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const retryData = await retryRes.json();
|
|
1508
|
+
log("");
|
|
1509
|
+
success(`배포 완료!`);
|
|
1510
|
+
info(`URL: ${retryData.url}`);
|
|
1511
|
+
info(`Hash: ${retryData.bundleHash}`);
|
|
1512
|
+
log("");
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
error(`배포 실패: ${errData.error || deployRes.statusText}`);
|
|
1517
|
+
process.exit(1);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const deployData = await deployRes.json();
|
|
1521
|
+
|
|
1522
|
+
log("");
|
|
1523
|
+
success(`배포 완료!`);
|
|
1524
|
+
info(`URL: ${deployData.url}`);
|
|
1525
|
+
info(`Hash: ${deployData.bundleHash}`);
|
|
1526
|
+
if (deployData.deployId) info(`ID: ${deployData.deployId}`);
|
|
1527
|
+
log("");
|
|
1528
|
+
|
|
1529
|
+
// gencow.json 업데이트 (앱 이름 저장)
|
|
1530
|
+
if (!existsSync(gencowJsonPath)) {
|
|
1531
|
+
writeFileSync(gencowJsonPath, JSON.stringify({
|
|
1532
|
+
appName,
|
|
1533
|
+
platformUrl: creds.platformUrl,
|
|
1534
|
+
}, null, 2));
|
|
1535
|
+
info(`${DIM}gencow.json 생성됨${RESET}`);
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
|
|
1539
|
+
// ── env ───────────────────────────────────────────────
|
|
1540
|
+
async env(...envArgs) {
|
|
1541
|
+
const creds = requireCreds();
|
|
1542
|
+
const subCmd = envArgs[0] || "list";
|
|
1543
|
+
const restArgs = envArgs.slice(1);
|
|
1544
|
+
|
|
1545
|
+
// 앱 이름 결정
|
|
1546
|
+
let appName = null;
|
|
1547
|
+
let envTarget = "dev";
|
|
1548
|
+
|
|
1549
|
+
for (let i = 0; i < restArgs.length; i++) {
|
|
1550
|
+
if (restArgs[i] === "--app" || restArgs[i] === "-a") appName = restArgs[++i];
|
|
1551
|
+
if (restArgs[i] === "--prod") envTarget = "prod";
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (!appName) {
|
|
1555
|
+
const gencowJsonPath = resolve(process.cwd(), "gencow.json");
|
|
1556
|
+
if (existsSync(gencowJsonPath)) {
|
|
1557
|
+
const gencowJson = JSON.parse(readFileSync(gencowJsonPath, "utf8"));
|
|
1558
|
+
appName = gencowJson.appName;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (!appName) {
|
|
1563
|
+
appName = basename(process.cwd());
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
switch (subCmd) {
|
|
1567
|
+
case "list":
|
|
1568
|
+
case "ls": {
|
|
1569
|
+
const res = await fetch(
|
|
1570
|
+
`${creds.platformUrl}/platform/apps/${appName}/env?env=${envTarget}`,
|
|
1571
|
+
{ headers: { "Authorization": `Bearer ${creds.apiKey}` } }
|
|
1572
|
+
);
|
|
1573
|
+
if (!res.ok) {
|
|
1574
|
+
error(`환경변수 조회 실패: ${(await res.json().catch(() => ({}))).error || res.statusText}`);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const vars = await res.json();
|
|
1578
|
+
log(`\n${BOLD}${CYAN}환경변수${RESET} — ${appName} (${envTarget})\n`);
|
|
1579
|
+
if (vars.length === 0) {
|
|
1580
|
+
info("설정된 환경변수가 없습니다.");
|
|
1581
|
+
} else {
|
|
1582
|
+
for (const v of vars) {
|
|
1583
|
+
log(` ${v.key}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
log("");
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
case "set": {
|
|
1591
|
+
// gencow env set KEY=VALUE [KEY2=VALUE2...]
|
|
1592
|
+
const kvPairs = restArgs.filter(a => a.includes("=") && !a.startsWith("-"));
|
|
1593
|
+
if (kvPairs.length === 0) {
|
|
1594
|
+
error("사용법: gencow env set KEY=VALUE [KEY2=VALUE2...]");
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
for (const kv of kvPairs) {
|
|
1599
|
+
const [key, ...valueParts] = kv.split("=");
|
|
1600
|
+
const value = valueParts.join("=");
|
|
1601
|
+
|
|
1602
|
+
const res = await fetch(
|
|
1603
|
+
`${creds.platformUrl}/platform/apps/${appName}/env`,
|
|
1604
|
+
{
|
|
1605
|
+
method: "POST",
|
|
1606
|
+
headers: {
|
|
1607
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
1608
|
+
"Content-Type": "application/json",
|
|
1609
|
+
},
|
|
1610
|
+
body: JSON.stringify({ key, value, env: envTarget }),
|
|
1611
|
+
}
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
if (res.ok) {
|
|
1615
|
+
success(`${key} 설정 완료 (${envTarget})`);
|
|
1616
|
+
} else {
|
|
1617
|
+
error(`${key} 설정 실패`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
case "unset":
|
|
1624
|
+
case "remove": {
|
|
1625
|
+
const keys = restArgs.filter(a => !a.startsWith("-"));
|
|
1626
|
+
if (keys.length === 0) {
|
|
1627
|
+
error("사용법: gencow env unset KEY [KEY2...]");
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
for (const key of keys) {
|
|
1632
|
+
const res = await fetch(
|
|
1633
|
+
`${creds.platformUrl}/platform/apps/${appName}/env/${key}?env=${envTarget}`,
|
|
1634
|
+
{
|
|
1635
|
+
method: "DELETE",
|
|
1636
|
+
headers: { "Authorization": `Bearer ${creds.apiKey}` },
|
|
1637
|
+
}
|
|
1638
|
+
);
|
|
1639
|
+
|
|
1640
|
+
if (res.ok) {
|
|
1641
|
+
success(`${key} 삭제 완료`);
|
|
1642
|
+
} else {
|
|
1643
|
+
error(`${key} 삭제 실패`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
case "push": {
|
|
1650
|
+
// 로컬 .env 파일 → 리모트 일괄 push
|
|
1651
|
+
const envFile = resolve(process.cwd(), envTarget === "prod" ? ".env.production" : ".env");
|
|
1652
|
+
if (!existsSync(envFile)) {
|
|
1653
|
+
error(`${envFile} 파일을 찾을 수 없습니다.`);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const envContent = readFileSync(envFile, "utf8");
|
|
1658
|
+
const vars = {};
|
|
1659
|
+
for (const line of envContent.split("\n")) {
|
|
1660
|
+
const trimmed = line.trim();
|
|
1661
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1662
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
1663
|
+
if (key) vars[key.trim()] = valueParts.join("=").trim();
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const count = Object.keys(vars).length;
|
|
1667
|
+
info(`${count}개 환경변수를 ${appName} (${envTarget})에 push합니다...`);
|
|
1668
|
+
|
|
1669
|
+
const res = await fetch(
|
|
1670
|
+
`${creds.platformUrl}/platform/apps/${appName}/env/bulk`,
|
|
1671
|
+
{
|
|
1672
|
+
method: "PUT",
|
|
1673
|
+
headers: {
|
|
1674
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
1675
|
+
"Content-Type": "application/json",
|
|
1676
|
+
},
|
|
1677
|
+
body: JSON.stringify({ vars, env: envTarget }),
|
|
1678
|
+
}
|
|
1679
|
+
);
|
|
1680
|
+
|
|
1681
|
+
if (res.ok) {
|
|
1682
|
+
success(`${count}개 환경변수 push 완료`);
|
|
1683
|
+
} else {
|
|
1684
|
+
error(`push 실패: ${(await res.json().catch(() => ({}))).error}`);
|
|
1685
|
+
}
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
default:
|
|
1690
|
+
error(`알 수 없는 하위 명령: ${subCmd}`);
|
|
1691
|
+
info("사용법: gencow env [list|set|unset|push] ...");
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
|
|
1695
|
+
|
|
1216
1696
|
async mcp(...mcpArgs) {
|
|
1217
1697
|
let port = "5456";
|
|
1218
1698
|
for (let i = 0; i < mcpArgs.length; i++) {
|
|
@@ -1667,15 +2147,19 @@ ${BOLD}${CYAN}Gencow Dev Remote${RESET}
|
|
|
1667
2147
|
log(`\n${BOLD}${CYAN}gencow add${RESET} — Add components to your project\n`);
|
|
1668
2148
|
log(`${BOLD}Usage:${RESET} gencow add <component...>\n`);
|
|
1669
2149
|
log(`${BOLD}Available components:${RESET}`);
|
|
1670
|
-
log(` ${GREEN}AI${RESET}
|
|
1671
|
-
log(` ${GREEN}Tools${RESET}
|
|
1672
|
-
log(` ${GREEN}RAG${RESET}
|
|
1673
|
-
log(` ${GREEN}
|
|
1674
|
-
log(` ${GREEN}
|
|
2150
|
+
log(` ${GREEN}AI${RESET} Vercel AI SDK wrapper ${DIM}(chat, stream, embed, agent, retry)${RESET}`);
|
|
2151
|
+
log(` ${GREEN}Tools${RESET} AI Tool Calling with ctx integration`);
|
|
2152
|
+
log(` ${GREEN}RAG${RESET} Document ingestion + semantic search + Q&A`);
|
|
2153
|
+
log(` ${GREEN}Reranker${RESET} LLM-powered search result reranking`);
|
|
2154
|
+
log(` ${GREEN}Guardrails${RESET} Input/output safety filters`);
|
|
2155
|
+
log(` ${GREEN}Prompts${RESET} Reusable prompt templates`);
|
|
2156
|
+
log(` ${GREEN}Parsers${RESET} PDF/HTML/CSV file parsing`);
|
|
2157
|
+
log(` ${GREEN}Memory${RESET} Agent memory ${DIM}(episodic/semantic/procedural)${RESET}`);
|
|
2158
|
+
log(` ${GREEN}Analytics${RESET} LLM call tracking ${DIM}(coming soon)${RESET}`);
|
|
1675
2159
|
log(`\n${BOLD}Examples:${RESET}`);
|
|
1676
2160
|
log(` ${DIM}gencow add AI${RESET}`);
|
|
1677
|
-
log(` ${DIM}gencow add AI RAG
|
|
1678
|
-
log(` ${DIM}gencow add
|
|
2161
|
+
log(` ${DIM}gencow add AI RAG Reranker${RESET}`);
|
|
2162
|
+
log(` ${DIM}gencow add Guardrails Prompts${RESET}\n`);
|
|
1679
2163
|
return;
|
|
1680
2164
|
}
|
|
1681
2165
|
|
|
@@ -1750,6 +2234,63 @@ ${BOLD}${CYAN}Gencow Dev Remote${RESET}
|
|
|
1750
2234
|
notReady: true,
|
|
1751
2235
|
guide: [],
|
|
1752
2236
|
},
|
|
2237
|
+
reranker: {
|
|
2238
|
+
label: "Reranker",
|
|
2239
|
+
deps: [],
|
|
2240
|
+
files: [
|
|
2241
|
+
{ src: "reranker.ts", dest: "gencow/reranker.ts" },
|
|
2242
|
+
],
|
|
2243
|
+
env: {},
|
|
2244
|
+
requires: ["ai"],
|
|
2245
|
+
guide: [
|
|
2246
|
+
`gencow/reranker.ts 에서 reranker.rerank(), reranker.searchAndRerank() 사용 가능`,
|
|
2247
|
+
`const reranked = await reranker.rerank(query, searchResults, { topK: 5 })`,
|
|
2248
|
+
`RAG 파이프라인: await reranker.searchAndRerank(ctx, rag, query)`,
|
|
2249
|
+
],
|
|
2250
|
+
},
|
|
2251
|
+
guardrails: {
|
|
2252
|
+
label: "Guardrails",
|
|
2253
|
+
deps: [],
|
|
2254
|
+
files: [
|
|
2255
|
+
{ src: "guardrails.ts", dest: "gencow/guardrails.ts" },
|
|
2256
|
+
],
|
|
2257
|
+
env: {},
|
|
2258
|
+
requires: ["ai"],
|
|
2259
|
+
guide: [
|
|
2260
|
+
`gencow/guardrails.ts 에서 guardrails.validateInput(), guardrails.validateOutput() 사용 가능`,
|
|
2261
|
+
`PII 마스킹: guardrails.validateInput(text, { maskPII: true })`,
|
|
2262
|
+
`주제 차단: guardrails.validateInput(text, { blockTopics: ["정치"] })`,
|
|
2263
|
+
`한 번에 래핑: guardrails.wrap(fn, input, inputOpts, outputOpts)`,
|
|
2264
|
+
],
|
|
2265
|
+
},
|
|
2266
|
+
prompts: {
|
|
2267
|
+
label: "Prompt Templates",
|
|
2268
|
+
deps: [],
|
|
2269
|
+
files: [
|
|
2270
|
+
{ src: "prompts.ts", dest: "gencow/prompts.ts" },
|
|
2271
|
+
],
|
|
2272
|
+
env: {},
|
|
2273
|
+
requires: [],
|
|
2274
|
+
guide: [
|
|
2275
|
+
`gencow/prompts.ts 에서 definePrompt() 로 재사용 프롬프트 정의`,
|
|
2276
|
+
`내장 템플릿: ragQAPrompt, summarizePrompt, classifyPrompt`,
|
|
2277
|
+
`const filled = ragQAPrompt({ question: "...", context: "..." })`,
|
|
2278
|
+
],
|
|
2279
|
+
},
|
|
2280
|
+
parsers: {
|
|
2281
|
+
label: "File Parsers",
|
|
2282
|
+
deps: ["pdf-parse"],
|
|
2283
|
+
files: [
|
|
2284
|
+
{ src: "parsers.ts", dest: "gencow/parsers.ts" },
|
|
2285
|
+
],
|
|
2286
|
+
env: {},
|
|
2287
|
+
requires: [],
|
|
2288
|
+
guide: [
|
|
2289
|
+
`gencow/parsers.ts 에서 parsers.pdf(), parsers.html(), parsers.csv() 사용 가능`,
|
|
2290
|
+
`자동 감지: await parsers.auto("file.pdf", buffer)`,
|
|
2291
|
+
`RAG 연동: await rag.ingest(ctx, "file.pdf", await parsers.pdf(buffer))`,
|
|
2292
|
+
],
|
|
2293
|
+
},
|
|
1753
2294
|
};
|
|
1754
2295
|
|
|
1755
2296
|
log(`\n${BOLD}${CYAN}gencow add${RESET}\n`);
|
|
@@ -1897,7 +2438,13 @@ const COMPONENT_DOCS = {
|
|
|
1897
2438
|
| :--- | :--- | :--- |
|
|
1898
2439
|
| \`ai.chat()\` | \`function\` | 텍스트 생성 (비스트리밍) |
|
|
1899
2440
|
| \`ai.stream()\` | \`function\` | 스트리밍 응답 |
|
|
1900
|
-
| \`ai.embed()\` | \`function\` | 텍스트 임베딩 (RAG/Memory용)
|
|
2441
|
+
| \`ai.embed()\` | \`function\` | 텍스트 임베딩 (RAG/Memory용) |
|
|
2442
|
+
| \`ai.embedMany()\` | \`function\` | 배치 임베딩 (대용량 인제스트) |
|
|
2443
|
+
| \`ai.generateObject()\` | \`function\` | 구조화 JSON 출력 (Zod 스키마) |
|
|
2444
|
+
| \`ai.agent()\` | \`function\` | Agent Loop (multi-turn tool use) |
|
|
2445
|
+
| \`ai.withRetry()\` | \`function\` | 자동 재시도 + 폴백 미들웨어 |
|
|
2446
|
+
| \`ai.estimateTokens()\` | \`function\` | 토큰 수 추정 (간이) |
|
|
2447
|
+
| \`ai.trimMessages()\` | \`function\` | 메시지 토큰 예산 자르기 |`,
|
|
1901
2448
|
usage: `\`\`\`typescript
|
|
1902
2449
|
import { ai } from "@/gencow/ai";
|
|
1903
2450
|
|
|
@@ -1907,37 +2454,62 @@ const reply = await ai.chat({
|
|
|
1907
2454
|
messages: [{ role: "user", content: "안녕?" }],
|
|
1908
2455
|
});
|
|
1909
2456
|
|
|
1910
|
-
//
|
|
1911
|
-
const
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
}
|
|
2457
|
+
// Agent Loop — 도구 자동 반복 호출
|
|
2458
|
+
const result = await ai.agent({
|
|
2459
|
+
system: "고객 지원 에이전트입니다.",
|
|
2460
|
+
messages: [{ role: "user", content: "주문 ORD-123 환불해줘" }],
|
|
2461
|
+
tools: defineTools(ctx, { ... }),
|
|
2462
|
+
maxSteps: 5,
|
|
2463
|
+
});
|
|
2464
|
+
|
|
2465
|
+
// 재시도 + 폴백
|
|
2466
|
+
const safe = await ai.withRetry(
|
|
2467
|
+
() => ai.chat({ messages: [...] }),
|
|
2468
|
+
{ maxRetries: 3 }
|
|
2469
|
+
);
|
|
1915
2470
|
\`\`\``,
|
|
1916
2471
|
vibePrompt: `AI 엔진 사용법:
|
|
1917
2472
|
import { ai } from "@/gencow/ai";
|
|
1918
2473
|
ai.chat({ messages }) // 텍스트 생성
|
|
1919
2474
|
ai.stream({ messages }) // 스트리밍
|
|
1920
|
-
ai.embed("텍스트") //
|
|
2475
|
+
ai.embed("텍스트") // 임베딩
|
|
2476
|
+
ai.embedMany(["a","b"]) // 배치 임베딩
|
|
2477
|
+
ai.agent({ messages, tools, maxSteps }) // Agent Loop
|
|
2478
|
+
ai.withRetry(fn, { maxRetries: 3 }) // 재시도+폴백
|
|
2479
|
+
ai.estimateTokens(text) // 토큰 추정
|
|
2480
|
+
ai.trimMessages(msgs, { maxTokens: 4000 }) // 토큰 예산`,
|
|
1921
2481
|
},
|
|
1922
2482
|
"rag.ts": {
|
|
1923
2483
|
title: "🔍 RAG Engine",
|
|
1924
2484
|
table: `| Function | Type | Description |
|
|
1925
2485
|
| :--- | :--- | :--- |
|
|
1926
|
-
| \`rag.ingest()\` | \`function\` | 문서 청킹 + 임베딩 저장 |
|
|
1927
|
-
| \`rag.search()\` | \`function\` | 시맨틱 검색 (코사인
|
|
2486
|
+
| \`rag.ingest()\` | \`function\` | 문서 청킹 + 임베딩 저장 (스마트 청킹, 배치 임베딩) |
|
|
2487
|
+
| \`rag.search()\` | \`function\` | 시맨틱 검색 (코사인 유사도, 소스 필터) |
|
|
2488
|
+
| \`rag.ask()\` | \`function\` | RAG Q&A (검색→컨텍스트→AI 답변) |
|
|
2489
|
+
| \`rag.delete()\` | \`function\` | 소스별 문서 삭제 |`,
|
|
1928
2490
|
usage: `\`\`\`typescript
|
|
1929
2491
|
import { rag } from "@/gencow/rag";
|
|
1930
2492
|
|
|
1931
|
-
// mutation 안에서 문서 인제스트
|
|
1932
|
-
await rag.ingest(ctx, "manual.pdf", documentText);
|
|
2493
|
+
// mutation 안에서 문서 인제스트 (스마트 청킹 자동 적용)
|
|
2494
|
+
await rag.ingest(ctx, "manual.pdf", documentText, { strategy: "markdown" });
|
|
1933
2495
|
|
|
1934
|
-
// query 안에서 시맨틱 검색
|
|
1935
|
-
const results = await rag.search(ctx, "환불 정책이 뭔가요?"
|
|
2496
|
+
// query 안에서 시맨틱 검색 (소스 필터 가능)
|
|
2497
|
+
const results = await rag.search(ctx, "환불 정책이 뭔가요?", {
|
|
2498
|
+
filter: { source: "manual.pdf" },
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
// Q&A — 검색+답변 한 번에
|
|
2502
|
+
const { answer, sources } = await rag.ask(ctx, "환불 정책이 뭔가요?");
|
|
2503
|
+
|
|
2504
|
+
// 문서 삭제
|
|
2505
|
+
await rag.delete(ctx, "old-manual.pdf");
|
|
1936
2506
|
\`\`\``,
|
|
1937
2507
|
vibePrompt: `RAG 엔진 사용법:
|
|
1938
2508
|
import { rag } from "@/gencow/rag";
|
|
1939
|
-
rag.ingest(ctx, source, text)
|
|
1940
|
-
rag.search(ctx, query)
|
|
2509
|
+
rag.ingest(ctx, source, text) // 문서 인제스트 (스마트 청킹)
|
|
2510
|
+
rag.search(ctx, query, { filter }) // 시맨틱 검색
|
|
2511
|
+
rag.ask(ctx, question) // Q&A (검색+답변)
|
|
2512
|
+
rag.delete(ctx, source) // 문서 삭제`,
|
|
1941
2513
|
},
|
|
1942
2514
|
"tools.ts": {
|
|
1943
2515
|
title: "🔧 Tool Calling",
|
|
@@ -1998,6 +2570,110 @@ const reply = await ai.chat({
|
|
|
1998
2570
|
vibePrompt: `Analytics:
|
|
1999
2571
|
ai.chat()/ai.stream() 호출 시 자동 계측됩니다.`,
|
|
2000
2572
|
},
|
|
2573
|
+
"reranker.ts": {
|
|
2574
|
+
title: "🎯 Reranker",
|
|
2575
|
+
table: `| Function | Type | Description |
|
|
2576
|
+
| :--- | :--- | :--- |
|
|
2577
|
+
| \`reranker.rerank()\` | \`function\` | LLM 기반 검색 결과 재정렬 |
|
|
2578
|
+
| \`reranker.searchAndRerank()\` | \`function\` | RAG search + rerank 통합 |`,
|
|
2579
|
+
usage: `\`\`\`typescript
|
|
2580
|
+
import { reranker } from "@/gencow/reranker";
|
|
2581
|
+
import { rag } from "@/gencow/rag";
|
|
2582
|
+
|
|
2583
|
+
// 벡터 검색 후 LLM 재정렬
|
|
2584
|
+
const results = await rag.search(ctx, query, { limit: 20 });
|
|
2585
|
+
const reranked = await reranker.rerank(query, results, { topK: 5 });
|
|
2586
|
+
|
|
2587
|
+
// 한 번에 (search + rerank)
|
|
2588
|
+
const best = await reranker.searchAndRerank(ctx, rag, query);
|
|
2589
|
+
\`\`\``,
|
|
2590
|
+
vibePrompt: `Reranker 사용법:
|
|
2591
|
+
import { reranker } from "@/gencow/reranker";
|
|
2592
|
+
reranker.rerank(query, docs, { topK: 5 }) // LLM 재정렬
|
|
2593
|
+
reranker.searchAndRerank(ctx, rag, query) // 통합 파이프라인`,
|
|
2594
|
+
},
|
|
2595
|
+
"guardrails.ts": {
|
|
2596
|
+
title: "🛡️ Guardrails",
|
|
2597
|
+
table: `| Function | Type | Description |
|
|
2598
|
+
| :--- | :--- | :--- |
|
|
2599
|
+
| \`guardrails.validateInput()\` | \`function\` | 입력 검증 (PII/주제/길이) |
|
|
2600
|
+
| \`guardrails.validateOutput()\` | \`function\` | 출력 검증 (길이/패턴/커스텀) |
|
|
2601
|
+
| \`guardrails.wrap()\` | \`function\` | 입력→AI→출력 한 번에 래핑 |`,
|
|
2602
|
+
usage: `\`\`\`typescript
|
|
2603
|
+
import { guardrails } from "@/gencow/guardrails";
|
|
2604
|
+
|
|
2605
|
+
// PII 마스킹 + 주제 차단
|
|
2606
|
+
const safe = await guardrails.validateInput(userMsg, {
|
|
2607
|
+
maskPII: true,
|
|
2608
|
+
blockTopics: ["정치", "종교"],
|
|
2609
|
+
});
|
|
2610
|
+
if (!safe.allowed) return safe.blocked;
|
|
2611
|
+
|
|
2612
|
+
// 래핑 유틸리티
|
|
2613
|
+
const result = await guardrails.wrap(
|
|
2614
|
+
(sanitized) => ai.chat({ messages: [{ role: "user", content: sanitized }] }),
|
|
2615
|
+
userMsg,
|
|
2616
|
+
{ maskPII: true },
|
|
2617
|
+
{ maxLength: 2000 }
|
|
2618
|
+
);
|
|
2619
|
+
\`\`\``,
|
|
2620
|
+
vibePrompt: `Guardrails 사용법:
|
|
2621
|
+
import { guardrails } from "@/gencow/guardrails";
|
|
2622
|
+
guardrails.validateInput(text, { maskPII: true }) // PII 마스킹
|
|
2623
|
+
guardrails.validateInput(text, { blockTopics }) // 주제 차단
|
|
2624
|
+
guardrails.wrap(fn, input, inputOpts, outputOpts) // 한 번에 래핑`,
|
|
2625
|
+
},
|
|
2626
|
+
"prompts.ts": {
|
|
2627
|
+
title: "📝 Prompt Templates",
|
|
2628
|
+
table: `| Function | Type | Description |
|
|
2629
|
+
| :--- | :--- | :--- |
|
|
2630
|
+
| \`definePrompt()\` | \`function\` | 재사용 프롬프트 팩토리 |
|
|
2631
|
+
| \`ragQAPrompt\` | \`template\` | RAG Q&A 내장 프롬프트 |
|
|
2632
|
+
| \`summarizePrompt\` | \`template\` | 요약 내장 프롬프트 |
|
|
2633
|
+
| \`classifyPrompt\` | \`template\` | 분류 내장 프롬프트 |`,
|
|
2634
|
+
usage: `\`\`\`typescript
|
|
2635
|
+
import { definePrompt, ragQAPrompt } from "@/gencow/prompts";
|
|
2636
|
+
|
|
2637
|
+
// 내장 템플릿 사용
|
|
2638
|
+
const filled = ragQAPrompt({ question: "환불 정책?", context: docs });
|
|
2639
|
+
|
|
2640
|
+
// 커스텀 프롬프트 정의
|
|
2641
|
+
const myPrompt = definePrompt({
|
|
2642
|
+
template: "{{role}}로서 {{task}}를 수행하세요.",
|
|
2643
|
+
defaults: { role: "도움이 되는 AI" },
|
|
2644
|
+
});
|
|
2645
|
+
const prompt = myPrompt({ role: "전문가", task: "코드 리뷰" });
|
|
2646
|
+
\`\`\``,
|
|
2647
|
+
vibePrompt: `Prompt Templates 사용법:
|
|
2648
|
+
import { definePrompt, ragQAPrompt } from "@/gencow/prompts";
|
|
2649
|
+
definePrompt({ template, defaults }) // 재사용 프롬프트 정의
|
|
2650
|
+
ragQAPrompt({ question, context }) // RAG Q&A 프롬프트`,
|
|
2651
|
+
},
|
|
2652
|
+
"parsers.ts": {
|
|
2653
|
+
title: "📄 File Parsers",
|
|
2654
|
+
table: `| Function | Type | Description |
|
|
2655
|
+
| :--- | :--- | :--- |
|
|
2656
|
+
| \`parsers.pdf()\` | \`function\` | PDF → 텍스트 추출 |
|
|
2657
|
+
| \`parsers.html()\` | \`function\` | HTML → 텍스트 (태그 제거) |
|
|
2658
|
+
| \`parsers.csv()\` | \`function\` | CSV → 행별 텍스트 |
|
|
2659
|
+
| \`parsers.auto()\` | \`function\` | 확장자 기반 자동 파싱 |`,
|
|
2660
|
+
usage: `\`\`\`typescript
|
|
2661
|
+
import { parsers } from "@/gencow/parsers";
|
|
2662
|
+
|
|
2663
|
+
// PDF 파싱 후 RAG 인제스트
|
|
2664
|
+
const text = await parsers.pdf(fileBuffer);
|
|
2665
|
+
await rag.ingest(ctx, "manual.pdf", text);
|
|
2666
|
+
|
|
2667
|
+
// 자동 감지
|
|
2668
|
+
const content = await parsers.auto("report.html", htmlString);
|
|
2669
|
+
\`\`\``,
|
|
2670
|
+
vibePrompt: `File Parsers 사용법:
|
|
2671
|
+
import { parsers } from "@/gencow/parsers";
|
|
2672
|
+
parsers.pdf(buffer) // PDF → 텍스트
|
|
2673
|
+
parsers.html(htmlString) // HTML → 텍스트
|
|
2674
|
+
parsers.csv(csvText) // CSV → ["header: value", ...]
|
|
2675
|
+
parsers.auto(filename, buf) // 확장자 자동 감지`,
|
|
2676
|
+
},
|
|
2001
2677
|
};
|
|
2002
2678
|
|
|
2003
2679
|
async function updateReadme(config) {
|