pai-zero 0.9.2 → 0.10.1

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/dist/bin/pai.js CHANGED
@@ -501,6 +501,51 @@ async function interactiveInterview(analysis, _cwd, projectName) {
501
501
  if (database === "supabase") extraTools.push("supabase");
502
502
  extraTools.push("gstack", "harness");
503
503
  }
504
+ let mcp;
505
+ console.log("");
506
+ const { mcpDev } = await inquirer.prompt([{
507
+ type: "confirm",
508
+ name: "mcpDev",
509
+ message: "MCP(Model Context Protocol) \uC11C\uBC84\uB97C \uAC1C\uBC1C\uD558\uC2DC\uB098\uC694?",
510
+ default: false
511
+ }]);
512
+ if (mcpDev) {
513
+ console.log("");
514
+ const hasSupabase = extraTools.includes("supabase");
515
+ const typeChoices = [
516
+ { name: `Tools \uC11C\uBC84 (\uAC1C\uC778\uC6A9) ${colors.dim("\u2500 AI\uAC00 \uD638\uCD9C\uD560 \uAE30\uB2A5 \uC81C\uACF5")}`, value: "tools" }
517
+ ];
518
+ if (hasSupabase) {
519
+ typeChoices.push({
520
+ name: `Tools \uC11C\uBC84 (\uACF5\uAC1C \uBC30\uD3EC) ${colors.dim("\u2500 API \uD0A4 + \uAD00\uB9AC\uD654\uBA74 + \uC0AC\uC6A9\uB7C9 \uCD94\uC801")}`,
521
+ value: "tools-public"
522
+ });
523
+ }
524
+ typeChoices.push(
525
+ { name: `Resources \uC11C\uBC84 ${colors.dim("\u2500 AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8 \uC81C\uACF5")}`, value: "resources" },
526
+ { name: `Prompts \uC11C\uBC84 ${colors.dim("\u2500 \uC7AC\uC0AC\uC6A9 \uD504\uB86C\uD504\uD2B8 \uD15C\uD50C\uB9BF")}`, value: "prompts" },
527
+ { name: `All-in-one ${colors.dim("\u2500 \uBAA8\uB450 \uD3EC\uD568")}`, value: "all" }
528
+ );
529
+ const { mcpType } = await inquirer.prompt([{
530
+ type: "list",
531
+ name: "mcpType",
532
+ message: "MCP \uC11C\uBC84 \uC720\uD615:",
533
+ choices: typeChoices
534
+ }]);
535
+ const { mcpName } = await inquirer.prompt([{
536
+ type: "input",
537
+ name: "mcpName",
538
+ message: "MCP \uC11C\uBC84 \uC774\uB984:",
539
+ default: `${projectName}-mcp`,
540
+ validate: (v) => /^[a-z0-9][a-z0-9-]*$/.test(v.trim()) ? true : "\uC601\uBB38 \uC18C\uBB38\uC790, \uC22B\uC790, \uD558\uC774\uD508(-)\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
541
+ }]);
542
+ mcp = {
543
+ enabled: true,
544
+ type: mcpType,
545
+ name: mcpName.trim()
546
+ };
547
+ extraTools.push("mcp");
548
+ }
504
549
  step(3, 3, "\uC124\uCE58 \uD655\uC778");
505
550
  console.log("");
506
551
  console.log(colors.dim(" \uC124\uCE58\uB420 \uD56D\uBAA9"));
@@ -516,6 +561,9 @@ async function interactiveInterview(analysis, _cwd, projectName) {
516
561
  if (extraTools.includes("supabase")) console.log(` ${colors.accent("+")} Supabase \uB370\uC774\uD130\uBCA0\uC774\uC2A4`);
517
562
  if (extraTools.includes("gstack")) console.log(` ${colors.accent("+")} gstack \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654`);
518
563
  if (extraTools.includes("harness")) console.log(` ${colors.accent("+")} Harness \uD488\uC9C8 \uAC80\uC99D`);
564
+ if (extraTools.includes("mcp") && mcp) {
565
+ console.log(` ${colors.accent("+")} MCP \uC11C\uBC84 (${mcp.name}, ${mcp.type})`);
566
+ }
519
567
  }
520
568
  if (authMethods.length > 0) {
521
569
  console.log("");
@@ -541,6 +589,7 @@ async function interactiveInterview(analysis, _cwd, projectName) {
541
589
  authMethods,
542
590
  customAuth,
543
591
  extraTools,
592
+ mcp,
544
593
  setupDomains: {
545
594
  claudeEnv: true,
546
595
  processDocs: true,
@@ -915,18 +964,778 @@ var init_platform = __esm({
915
964
  }
916
965
  });
917
966
 
967
+ // src/stages/environment/provisioners/mcp.ts
968
+ var mcp_exports = {};
969
+ __export(mcp_exports, {
970
+ provisionMcp: () => provisionMcp
971
+ });
972
+ import { join as join3 } from "path";
973
+ import fs4 from "fs-extra";
974
+ async function provisionMcp(ctx) {
975
+ const mcp = ctx.mcp;
976
+ if (!mcp?.enabled) return;
977
+ const mcpDir = join3(ctx.cwd, "mcp-server");
978
+ await fs4.ensureDir(mcpDir);
979
+ await fs4.ensureDir(join3(mcpDir, "src"));
980
+ await fs4.ensureDir(join3(mcpDir, "tests"));
981
+ const isPublicDeps = mcp.type === "tools-public";
982
+ const pkgJson = {
983
+ name: mcp.name,
984
+ version: "0.1.0",
985
+ description: `MCP server for ${ctx.projectName}`,
986
+ type: "module",
987
+ main: "dist/index.js",
988
+ bin: { [mcp.name]: "./dist/index.js" },
989
+ scripts: {
990
+ build: "tsc",
991
+ dev: "tsc --watch",
992
+ test: "vitest run",
993
+ start: "node dist/index.js"
994
+ },
995
+ dependencies: {
996
+ "@modelcontextprotocol/sdk": "^1.0.0",
997
+ ...isPublicDeps ? { "@supabase/supabase-js": "^2.48.0" } : {}
998
+ },
999
+ devDependencies: {
1000
+ typescript: "^5.7.0",
1001
+ "@types/node": "^20.17.0",
1002
+ vitest: "^3.0.0"
1003
+ }
1004
+ };
1005
+ const pkgPath = join3(mcpDir, "package.json");
1006
+ if (!await fs4.pathExists(pkgPath)) {
1007
+ await fs4.writeJson(pkgPath, pkgJson, { spaces: 2 });
1008
+ }
1009
+ const tsconfig = {
1010
+ compilerOptions: {
1011
+ target: "ES2022",
1012
+ module: "ESNext",
1013
+ moduleResolution: "bundler",
1014
+ outDir: "./dist",
1015
+ rootDir: "./src",
1016
+ strict: true,
1017
+ esModuleInterop: true,
1018
+ skipLibCheck: true,
1019
+ resolveJsonModule: true,
1020
+ declaration: true
1021
+ },
1022
+ include: ["src/**/*"],
1023
+ exclude: ["node_modules", "dist", "tests"]
1024
+ };
1025
+ const tsPath = join3(mcpDir, "tsconfig.json");
1026
+ if (!await fs4.pathExists(tsPath)) {
1027
+ await fs4.writeJson(tsPath, tsconfig, { spaces: 2 });
1028
+ }
1029
+ const indexPath = join3(mcpDir, "src", "index.ts");
1030
+ if (!await fs4.pathExists(indexPath)) {
1031
+ await fs4.writeFile(indexPath, buildIndexTs(mcp), "utf8");
1032
+ }
1033
+ const isTools = mcp.type === "tools" || mcp.type === "tools-public" || mcp.type === "all";
1034
+ const isPublic = mcp.type === "tools-public";
1035
+ if (isTools) {
1036
+ const toolsPath = join3(mcpDir, "src", "tools.ts");
1037
+ if (!await fs4.pathExists(toolsPath)) {
1038
+ await fs4.writeFile(toolsPath, isPublic ? TOOLS_PUBLIC_TEMPLATE : TOOLS_TEMPLATE, "utf8");
1039
+ }
1040
+ }
1041
+ if (isPublic) {
1042
+ const authPath = join3(mcpDir, "src", "auth.ts");
1043
+ if (!await fs4.pathExists(authPath)) {
1044
+ await fs4.writeFile(authPath, AUTH_MIDDLEWARE_TEMPLATE, "utf8");
1045
+ }
1046
+ const rateLimitPath = join3(mcpDir, "src", "rate-limit.ts");
1047
+ if (!await fs4.pathExists(rateLimitPath)) {
1048
+ await fs4.writeFile(rateLimitPath, RATE_LIMIT_TEMPLATE, "utf8");
1049
+ }
1050
+ const migDir = join3(ctx.cwd, "supabase", "migrations");
1051
+ await fs4.ensureDir(migDir);
1052
+ const migPath = join3(migDir, "20260101_mcp_keys_and_usage.sql");
1053
+ if (!await fs4.pathExists(migPath)) {
1054
+ await fs4.writeFile(migPath, SUPABASE_MIGRATION, "utf8");
1055
+ }
1056
+ const dashDir = join3(ctx.cwd, "src", "app", "dashboard");
1057
+ await fs4.ensureDir(join3(dashDir, "keys"));
1058
+ await fs4.ensureDir(join3(dashDir, "usage"));
1059
+ const dashLayoutPath = join3(dashDir, "layout.tsx");
1060
+ if (!await fs4.pathExists(dashLayoutPath)) {
1061
+ await fs4.writeFile(dashLayoutPath, DASHBOARD_LAYOUT, "utf8");
1062
+ }
1063
+ const dashPagePath = join3(dashDir, "page.tsx");
1064
+ if (!await fs4.pathExists(dashPagePath)) {
1065
+ await fs4.writeFile(dashPagePath, DASHBOARD_PAGE, "utf8");
1066
+ }
1067
+ const keysPagePath = join3(dashDir, "keys", "page.tsx");
1068
+ if (!await fs4.pathExists(keysPagePath)) {
1069
+ await fs4.writeFile(keysPagePath, KEYS_PAGE, "utf8");
1070
+ }
1071
+ const usagePagePath = join3(dashDir, "usage", "page.tsx");
1072
+ if (!await fs4.pathExists(usagePagePath)) {
1073
+ await fs4.writeFile(usagePagePath, USAGE_PAGE, "utf8");
1074
+ }
1075
+ const apiKeysDir = join3(ctx.cwd, "src", "app", "api", "keys");
1076
+ await fs4.ensureDir(apiKeysDir);
1077
+ const keysRoutePath = join3(apiKeysDir, "route.ts");
1078
+ if (!await fs4.pathExists(keysRoutePath)) {
1079
+ await fs4.writeFile(keysRoutePath, KEYS_API_ROUTE, "utf8");
1080
+ }
1081
+ }
1082
+ if (mcp.type === "resources" || mcp.type === "all") {
1083
+ const resPath = join3(mcpDir, "src", "resources.ts");
1084
+ if (!await fs4.pathExists(resPath)) {
1085
+ await fs4.writeFile(resPath, RESOURCES_TEMPLATE, "utf8");
1086
+ }
1087
+ }
1088
+ if (mcp.type === "prompts" || mcp.type === "all") {
1089
+ const promptsPath = join3(mcpDir, "src", "prompts.ts");
1090
+ if (!await fs4.pathExists(promptsPath)) {
1091
+ await fs4.writeFile(promptsPath, PROMPTS_TEMPLATE, "utf8");
1092
+ }
1093
+ }
1094
+ const testPath = join3(mcpDir, "tests", "server.test.ts");
1095
+ if (!await fs4.pathExists(testPath)) {
1096
+ await fs4.writeFile(testPath, TEST_TEMPLATE, "utf8");
1097
+ }
1098
+ const readmePath = join3(mcpDir, "README.md");
1099
+ if (!await fs4.pathExists(readmePath)) {
1100
+ await fs4.writeFile(readmePath, buildReadme(mcp), "utf8");
1101
+ }
1102
+ const mcpJsonPath = join3(ctx.cwd, ".mcp.json");
1103
+ const mcpJson = await fs4.pathExists(mcpJsonPath) ? await fs4.readJson(mcpJsonPath) : { mcpServers: {} };
1104
+ mcpJson.mcpServers[mcp.name] = {
1105
+ command: "node",
1106
+ args: ["./mcp-server/dist/index.js"]
1107
+ };
1108
+ await fs4.writeJson(mcpJsonPath, mcpJson, { spaces: 2 });
1109
+ const gitignorePath = join3(mcpDir, ".gitignore");
1110
+ if (!await fs4.pathExists(gitignorePath)) {
1111
+ await fs4.writeFile(gitignorePath, "node_modules/\ndist/\n*.log\n", "utf8");
1112
+ }
1113
+ }
1114
+ function buildIndexTs(mcp) {
1115
+ const imports = [];
1116
+ const registrations = [];
1117
+ if (mcp.type === "tools" || mcp.type === "all") {
1118
+ imports.push("import { registerTools } from './tools.js';");
1119
+ registrations.push(" registerTools(server);");
1120
+ }
1121
+ if (mcp.type === "resources" || mcp.type === "all") {
1122
+ imports.push("import { registerResources } from './resources.js';");
1123
+ registrations.push(" registerResources(server);");
1124
+ }
1125
+ if (mcp.type === "prompts" || mcp.type === "all") {
1126
+ imports.push("import { registerPrompts } from './prompts.js';");
1127
+ registrations.push(" registerPrompts(server);");
1128
+ }
1129
+ return `#!/usr/bin/env node
1130
+ /**
1131
+ * ${mcp.name} \u2014 MCP Server
1132
+ * Generated by PAI
1133
+ */
1134
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1135
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
1136
+ ${imports.join("\n")}
1137
+
1138
+ async function main() {
1139
+ const server = new Server(
1140
+ {
1141
+ name: '${mcp.name}',
1142
+ version: '0.1.0',
1143
+ },
1144
+ {
1145
+ capabilities: {
1146
+ ${mcp.type === "tools" || mcp.type === "all" ? " tools: {}," : ""}
1147
+ ${mcp.type === "resources" || mcp.type === "all" ? " resources: {}," : ""}
1148
+ ${mcp.type === "prompts" || mcp.type === "all" ? " prompts: {}," : ""}
1149
+ },
1150
+ },
1151
+ );
1152
+
1153
+ ${registrations.join("\n")}
1154
+
1155
+ const transport = new StdioServerTransport();
1156
+ await server.connect(transport);
1157
+ console.error('${mcp.name} MCP server running on stdio');
1158
+ }
1159
+
1160
+ main().catch((err) => {
1161
+ console.error('Fatal error:', err);
1162
+ process.exit(1);
1163
+ });
1164
+ `;
1165
+ }
1166
+ function buildReadme(mcp) {
1167
+ const isPublic = mcp.type === "tools-public";
1168
+ return `# ${mcp.name} \u2014 MCP Server
1169
+
1170
+ PAI\uAC00 \uC0DD\uC131\uD55C MCP(Model Context Protocol) \uC11C\uBC84\uC785\uB2C8\uB2E4.
1171
+
1172
+ ## \uD0C0\uC785: ${mcp.type}
1173
+
1174
+ ${mcp.type === "tools" || mcp.type === "all" ? "- **Tools**: AI\uAC00 \uD638\uCD9C\uD560 \uAE30\uB2A5 \uC81C\uACF5 (src/tools.ts)" : ""}
1175
+ ${isPublic ? "- **Tools (\uACF5\uAC1C \uBC30\uD3EC)**: API \uD0A4 \uAC80\uC99D + \uB808\uC774\uD2B8 \uB9AC\uBC0B + \uC0AC\uC6A9\uB7C9 \uCD94\uC801 (src/tools.ts, auth.ts, rate-limit.ts)" : ""}
1176
+ ${mcp.type === "resources" || mcp.type === "all" ? "- **Resources**: AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8 \uC81C\uACF5 (src/resources.ts)" : ""}
1177
+ ${mcp.type === "prompts" || mcp.type === "all" ? "- **Prompts**: \uC7AC\uC0AC\uC6A9 \uD504\uB86C\uD504\uD2B8 \uD15C\uD50C\uB9BF (src/prompts.ts)" : ""}
1178
+
1179
+ ${isPublic ? `
1180
+ ## \uACF5\uAC1C \uBC30\uD3EC \uC124\uC815
1181
+
1182
+ 1. **Supabase \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC2E4\uD589**
1183
+ \\\`\\\`\\\`bash
1184
+ supabase db push
1185
+ \\\`\\\`\\\`
1186
+ (\\\`supabase/migrations/\\\` \uCC38\uACE0)
1187
+
1188
+ 2. **\uD658\uACBD \uBCC0\uC218 \uC124\uC815 (.env.local)**
1189
+ \\\`\\\`\\\`
1190
+ NEXT_PUBLIC_SUPABASE_URL=...
1191
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=...
1192
+ SUPABASE_SERVICE_ROLE_KEY=...
1193
+ \\\`\\\`\\\`
1194
+
1195
+ 3. **\uB300\uC2DC\uBCF4\uB4DC \uC811\uC18D**
1196
+ \\\`\\\`\\\`bash
1197
+ npm run dev
1198
+ # \u2192 http://localhost:3000/dashboard
1199
+ \\\`\\\`\\\`
1200
+ - API \uD0A4 \uBC1C\uAE09 \uBC0F \uAD00\uB9AC
1201
+ - \uC0AC\uC6A9\uB7C9 \uD655\uC778
1202
+ ` : ""}
1203
+
1204
+ ## \uAC1C\uBC1C
1205
+
1206
+ \`\`\`bash
1207
+ cd mcp-server
1208
+ npm install
1209
+ npm run build
1210
+ npm test
1211
+ \`\`\`
1212
+
1213
+ ## Claude Code \uC5F0\uACB0
1214
+
1215
+ \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`.mcp.json\` \uD30C\uC77C\uC5D0 \uC774\uBBF8 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.
1216
+ Claude Code\uB97C \uC7AC\uC2DC\uC791\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C MCP \uC11C\uBC84\uAC00 \uB85C\uB4DC\uB429\uB2C8\uB2E4.
1217
+
1218
+ \uC218\uB3D9 \uB4F1\uB85D:
1219
+ \`\`\`bash
1220
+ claude mcp add ${mcp.name} -- node ./mcp-server/dist/index.js
1221
+ \`\`\`
1222
+
1223
+ ## \uB2E4\uC74C \uB2E8\uACC4
1224
+
1225
+ 1. \`npm install\` \u2014 \uC758\uC874\uC131 \uC124\uCE58
1226
+ 2. \`npm run build\` \u2014 TypeScript \uCEF4\uD30C\uC77C
1227
+ 3. \`src/\` \uB514\uB809\uD1A0\uB9AC\uC758 \uD15C\uD50C\uB9BF\uC744 \uC2E4\uC81C \uB85C\uC9C1\uC73C\uB85C \uC218\uC815
1228
+ 4. Claude Code \uC7AC\uC2DC\uC791 \u2192 MCP \uC11C\uBC84 \uC0AC\uC6A9
1229
+
1230
+ ## \uCC38\uACE0 \uBB38\uC11C
1231
+
1232
+ - [MCP \uACF5\uC2DD \uBB38\uC11C](https://modelcontextprotocol.io/)
1233
+ - [SDK \uBB38\uC11C](https://github.com/modelcontextprotocol/sdk)
1234
+ `;
1235
+ }
1236
+ var TOOLS_PUBLIC_TEMPLATE, AUTH_MIDDLEWARE_TEMPLATE, RATE_LIMIT_TEMPLATE, SUPABASE_MIGRATION, DASHBOARD_LAYOUT, DASHBOARD_PAGE, KEYS_PAGE, USAGE_PAGE, KEYS_API_ROUTE, TOOLS_TEMPLATE, RESOURCES_TEMPLATE, PROMPTS_TEMPLATE, TEST_TEMPLATE;
1237
+ var init_mcp = __esm({
1238
+ "src/stages/environment/provisioners/mcp.ts"() {
1239
+ "use strict";
1240
+ TOOLS_PUBLIC_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1241
+ import {
1242
+ CallToolRequestSchema,
1243
+ ListToolsRequestSchema,
1244
+ } from '@modelcontextprotocol/sdk/types.js';
1245
+ import { verifyApiKey } from './auth.js';
1246
+ import { checkRateLimit, logUsage } from './rate-limit.js';
1247
+
1248
+ export function registerTools(server: Server): void {
1249
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1250
+ tools: [
1251
+ {
1252
+ name: 'hello',
1253
+ description: '\uC778\uC99D\uB41C \uB3C4\uAD6C \uD638\uCD9C \u2014 API \uD0A4 \uD544\uC694',
1254
+ inputSchema: {
1255
+ type: 'object',
1256
+ properties: {
1257
+ apiKey: { type: 'string', description: 'API \uD0A4' },
1258
+ name: { type: 'string', description: '\uC778\uC0AC\uD560 \uB300\uC0C1' },
1259
+ },
1260
+ required: ['apiKey', 'name'],
1261
+ },
1262
+ },
1263
+ ],
1264
+ }));
1265
+
1266
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1267
+ const { name, arguments: args } = request.params;
1268
+ const { apiKey, ...params } = (args as { apiKey?: string; [k: string]: unknown }) ?? {};
1269
+
1270
+ if (!apiKey) throw new Error('API key required');
1271
+ const user = await verifyApiKey(apiKey);
1272
+ if (!user) throw new Error('Invalid API key');
1273
+
1274
+ const allowed = await checkRateLimit(user.id);
1275
+ if (!allowed) throw new Error('Rate limit exceeded');
1276
+
1277
+ if (name === 'hello') {
1278
+ const target = (params as { name?: string })?.name ?? 'world';
1279
+ const result = { content: [{ type: 'text' as const, text: \`Hello, \${target}!\` }] };
1280
+ await logUsage(user.id, name);
1281
+ return result;
1282
+ }
1283
+
1284
+ throw new Error(\`Unknown tool: \${name}\`);
1285
+ });
1286
+ }
1287
+ `;
1288
+ AUTH_MIDDLEWARE_TEMPLATE = `import { createClient } from '@supabase/supabase-js';
1289
+
1290
+ const supabase = createClient(
1291
+ process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
1292
+ process.env.SUPABASE_SERVICE_ROLE_KEY ?? '',
1293
+ );
1294
+
1295
+ export interface AuthUser {
1296
+ id: string;
1297
+ email: string;
1298
+ }
1299
+
1300
+ /**
1301
+ * API \uD0A4 \uAC80\uC99D \u2014 Supabase api_keys \uD14C\uC774\uBE14\uC5D0\uC11C \uC870\uD68C
1302
+ */
1303
+ export async function verifyApiKey(apiKey: string): Promise<AuthUser | null> {
1304
+ const { data, error } = await supabase
1305
+ .from('api_keys')
1306
+ .select('user_id, revoked_at, users(id, email)')
1307
+ .eq('key_hash', await hashKey(apiKey))
1308
+ .single();
1309
+
1310
+ if (error || !data || data.revoked_at) return null;
1311
+
1312
+ // \uB9C8\uC9C0\uB9C9 \uC0AC\uC6A9 \uC2DC\uAC04 \uC5C5\uB370\uC774\uD2B8
1313
+ await supabase
1314
+ .from('api_keys')
1315
+ .update({ last_used_at: new Date().toISOString() })
1316
+ .eq('key_hash', await hashKey(apiKey));
1317
+
1318
+ const users = (data as unknown as { users: { id: string; email: string } }).users;
1319
+ return { id: users.id, email: users.email };
1320
+ }
1321
+
1322
+ async function hashKey(key: string): Promise<string> {
1323
+ const crypto = await import('node:crypto');
1324
+ return crypto.createHash('sha256').update(key).digest('hex');
1325
+ }
1326
+
1327
+ export async function generateApiKey(): Promise<{ key: string; hash: string }> {
1328
+ const crypto = await import('node:crypto');
1329
+ const key = \`mcp_\${crypto.randomBytes(32).toString('hex')}\`;
1330
+ const hash = crypto.createHash('sha256').update(key).digest('hex');
1331
+ return { key, hash };
1332
+ }
1333
+ `;
1334
+ RATE_LIMIT_TEMPLATE = `import { createClient } from '@supabase/supabase-js';
1335
+
1336
+ const supabase = createClient(
1337
+ process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
1338
+ process.env.SUPABASE_SERVICE_ROLE_KEY ?? '',
1339
+ );
1340
+
1341
+ const RATE_LIMIT_PER_HOUR = 100;
1342
+
1343
+ /**
1344
+ * Rate limit \uCCB4\uD06C \u2014 \uCD5C\uADFC 1\uC2DC\uAC04 \uB0B4 \uD638\uCD9C \uC218 \uD655\uC778
1345
+ */
1346
+ export async function checkRateLimit(userId: string): Promise<boolean> {
1347
+ const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
1348
+ const { count } = await supabase
1349
+ .from('usage_logs')
1350
+ .select('*', { count: 'exact', head: true })
1351
+ .eq('user_id', userId)
1352
+ .gte('created_at', oneHourAgo);
1353
+
1354
+ return (count ?? 0) < RATE_LIMIT_PER_HOUR;
1355
+ }
1356
+
1357
+ /**
1358
+ * \uC0AC\uC6A9\uB7C9 \uAE30\uB85D \u2014 usage_logs \uD14C\uC774\uBE14\uC5D0 \uC800\uC7A5
1359
+ */
1360
+ export async function logUsage(userId: string, toolName: string): Promise<void> {
1361
+ await supabase.from('usage_logs').insert({
1362
+ user_id: userId,
1363
+ tool_name: toolName,
1364
+ created_at: new Date().toISOString(),
1365
+ });
1366
+ }
1367
+ `;
1368
+ SUPABASE_MIGRATION = `-- MCP Tools \uACF5\uAC1C \uBC30\uD3EC \u2014 API \uD0A4 \uAD00\uB9AC + \uC0AC\uC6A9\uB7C9 \uCD94\uC801
1369
+ -- Generated by PAI
1370
+
1371
+ -- \uC0AC\uC6A9\uC790 \uD14C\uC774\uBE14 (Supabase Auth\uC640 \uC5F0\uB3D9)
1372
+ create table if not exists public.users (
1373
+ id uuid primary key references auth.users(id) on delete cascade,
1374
+ email text not null unique,
1375
+ created_at timestamptz default now()
1376
+ );
1377
+
1378
+ -- API \uD0A4 \uD14C\uC774\uBE14
1379
+ create table if not exists public.api_keys (
1380
+ id uuid primary key default gen_random_uuid(),
1381
+ user_id uuid not null references public.users(id) on delete cascade,
1382
+ key_hash text not null unique,
1383
+ name text not null,
1384
+ created_at timestamptz default now(),
1385
+ last_used_at timestamptz,
1386
+ revoked_at timestamptz
1387
+ );
1388
+
1389
+ create index if not exists api_keys_user_id_idx on public.api_keys(user_id);
1390
+ create index if not exists api_keys_key_hash_idx on public.api_keys(key_hash);
1391
+
1392
+ -- \uC0AC\uC6A9\uB7C9 \uB85C\uADF8 \uD14C\uC774\uBE14
1393
+ create table if not exists public.usage_logs (
1394
+ id bigserial primary key,
1395
+ user_id uuid not null references public.users(id) on delete cascade,
1396
+ tool_name text not null,
1397
+ created_at timestamptz default now()
1398
+ );
1399
+
1400
+ create index if not exists usage_logs_user_id_idx on public.usage_logs(user_id);
1401
+ create index if not exists usage_logs_created_at_idx on public.usage_logs(created_at);
1402
+
1403
+ -- RLS (Row Level Security)
1404
+ alter table public.users enable row level security;
1405
+ alter table public.api_keys enable row level security;
1406
+ alter table public.usage_logs enable row level security;
1407
+
1408
+ create policy "Users can view own data" on public.users
1409
+ for select using (auth.uid() = id);
1410
+
1411
+ create policy "Users can manage own api keys" on public.api_keys
1412
+ for all using (auth.uid() = user_id);
1413
+
1414
+ create policy "Users can view own usage" on public.usage_logs
1415
+ for select using (auth.uid() = user_id);
1416
+ `;
1417
+ DASHBOARD_LAYOUT = `import Link from 'next/link';
1418
+ import type { ReactNode } from 'react';
1419
+
1420
+ export default function DashboardLayout({ children }: { children: ReactNode }) {
1421
+ return (
1422
+ <div style={{ display: 'flex', minHeight: '100vh' }}>
1423
+ <aside style={{ width: 240, borderRight: '1px solid #eee', padding: 24 }}>
1424
+ <h2 style={{ fontSize: 18, marginBottom: 16 }}>MCP \uB300\uC2DC\uBCF4\uB4DC</h2>
1425
+ <nav>
1426
+ <Link href="/dashboard" style={{ display: 'block', padding: '8px 0' }}>\uAC1C\uC694</Link>
1427
+ <Link href="/dashboard/keys" style={{ display: 'block', padding: '8px 0' }}>API \uD0A4</Link>
1428
+ <Link href="/dashboard/usage" style={{ display: 'block', padding: '8px 0' }}>\uC0AC\uC6A9\uB7C9</Link>
1429
+ </nav>
1430
+ </aside>
1431
+ <main style={{ flex: 1, padding: 24 }}>{children}</main>
1432
+ </div>
1433
+ );
1434
+ }
1435
+ `;
1436
+ DASHBOARD_PAGE = `export default function DashboardPage() {
1437
+ return (
1438
+ <div>
1439
+ <h1>MCP \uB300\uC2DC\uBCF4\uB4DC</h1>
1440
+ <p>API \uD0A4\uB97C \uBC1C\uAE09\uD558\uACE0 \uC0AC\uC6A9\uB7C9\uC744 \uAD00\uB9AC\uD558\uC138\uC694.</p>
1441
+ <ul>
1442
+ <li><a href="/dashboard/keys">API \uD0A4 \uBC1C\uAE09</a></li>
1443
+ <li><a href="/dashboard/usage">\uC0AC\uC6A9\uB7C9 \uD655\uC778</a></li>
1444
+ </ul>
1445
+ </div>
1446
+ );
1447
+ }
1448
+ `;
1449
+ KEYS_PAGE = `'use client';
1450
+ import { useEffect, useState } from 'react';
1451
+
1452
+ interface ApiKey {
1453
+ id: string;
1454
+ name: string;
1455
+ created_at: string;
1456
+ last_used_at?: string;
1457
+ }
1458
+
1459
+ export default function KeysPage() {
1460
+ const [keys, setKeys] = useState<ApiKey[]>([]);
1461
+ const [newKeyName, setNewKeyName] = useState('');
1462
+ const [newKey, setNewKey] = useState<string | null>(null);
1463
+
1464
+ useEffect(() => {
1465
+ fetch('/api/keys').then(r => r.json()).then(setKeys);
1466
+ }, []);
1467
+
1468
+ async function create() {
1469
+ const res = await fetch('/api/keys', {
1470
+ method: 'POST',
1471
+ headers: { 'Content-Type': 'application/json' },
1472
+ body: JSON.stringify({ name: newKeyName }),
1473
+ });
1474
+ const data = await res.json();
1475
+ setNewKey(data.key);
1476
+ setKeys([...keys, data.apiKey]);
1477
+ setNewKeyName('');
1478
+ }
1479
+
1480
+ async function revoke(id: string) {
1481
+ await fetch(\`/api/keys/\${id}\`, { method: 'DELETE' });
1482
+ setKeys(keys.filter(k => k.id !== id));
1483
+ }
1484
+
1485
+ return (
1486
+ <div>
1487
+ <h1>API \uD0A4 \uAD00\uB9AC</h1>
1488
+ <div style={{ marginBottom: 24 }}>
1489
+ <input
1490
+ placeholder="\uD0A4 \uC774\uB984"
1491
+ value={newKeyName}
1492
+ onChange={e => setNewKeyName(e.target.value)}
1493
+ />
1494
+ <button onClick={create}>\uC0C8 \uD0A4 \uBC1C\uAE09</button>
1495
+ </div>
1496
+ {newKey && (
1497
+ <div style={{ padding: 16, background: '#fffbe6', marginBottom: 24 }}>
1498
+ <strong>\uC0C8 API \uD0A4 (\uD55C \uBC88\uB9CC \uD45C\uC2DC):</strong>
1499
+ <pre>{newKey}</pre>
1500
+ </div>
1501
+ )}
1502
+ <table style={{ width: '100%' }}>
1503
+ <thead>
1504
+ <tr><th>\uC774\uB984</th><th>\uC0DD\uC131\uC77C</th><th>\uB9C8\uC9C0\uB9C9 \uC0AC\uC6A9</th><th></th></tr>
1505
+ </thead>
1506
+ <tbody>
1507
+ {keys.map(k => (
1508
+ <tr key={k.id}>
1509
+ <td>{k.name}</td>
1510
+ <td>{new Date(k.created_at).toLocaleDateString()}</td>
1511
+ <td>{k.last_used_at ? new Date(k.last_used_at).toLocaleString() : '\u2014'}</td>
1512
+ <td><button onClick={() => revoke(k.id)}>\uC0AD\uC81C</button></td>
1513
+ </tr>
1514
+ ))}
1515
+ </tbody>
1516
+ </table>
1517
+ </div>
1518
+ );
1519
+ }
1520
+ `;
1521
+ USAGE_PAGE = `'use client';
1522
+ import { useEffect, useState } from 'react';
1523
+
1524
+ interface UsageLog {
1525
+ tool_name: string;
1526
+ count: number;
1527
+ }
1528
+
1529
+ export default function UsagePage() {
1530
+ const [usage, setUsage] = useState<UsageLog[]>([]);
1531
+ const [total, setTotal] = useState(0);
1532
+
1533
+ useEffect(() => {
1534
+ fetch('/api/usage').then(r => r.json()).then(data => {
1535
+ setUsage(data.byTool ?? []);
1536
+ setTotal(data.total ?? 0);
1537
+ });
1538
+ }, []);
1539
+
1540
+ return (
1541
+ <div>
1542
+ <h1>\uC0AC\uC6A9\uB7C9</h1>
1543
+ <p>\uCD5C\uADFC 30\uC77C \uCD1D \uD638\uCD9C: <strong>{total}</strong></p>
1544
+ <table style={{ width: '100%' }}>
1545
+ <thead>
1546
+ <tr><th>\uB3C4\uAD6C</th><th>\uD638\uCD9C \uC218</th></tr>
1547
+ </thead>
1548
+ <tbody>
1549
+ {usage.map(u => (
1550
+ <tr key={u.tool_name}>
1551
+ <td>{u.tool_name}</td>
1552
+ <td>{u.count}</td>
1553
+ </tr>
1554
+ ))}
1555
+ </tbody>
1556
+ </table>
1557
+ </div>
1558
+ );
1559
+ }
1560
+ `;
1561
+ KEYS_API_ROUTE = `import { NextResponse } from 'next/server';
1562
+ import { createClient } from '@supabase/supabase-js';
1563
+ import crypto from 'node:crypto';
1564
+
1565
+ const supabase = createClient(
1566
+ process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
1567
+ process.env.SUPABASE_SERVICE_ROLE_KEY ?? '',
1568
+ );
1569
+
1570
+ export async function GET() {
1571
+ // TODO: \uD604\uC7AC \uB85C\uADF8\uC778 \uC0AC\uC6A9\uC790\uC758 \uD0A4\uB9CC \uC870\uD68C (auth \uBBF8\uB4E4\uC6E8\uC5B4 \uC5F0\uB3D9 \uD544\uC694)
1572
+ const { data, error } = await supabase
1573
+ .from('api_keys')
1574
+ .select('id, name, created_at, last_used_at')
1575
+ .is('revoked_at', null);
1576
+ if (error) return NextResponse.json({ error: error.message }, { status: 500 });
1577
+ return NextResponse.json(data);
1578
+ }
1579
+
1580
+ export async function POST(request: Request) {
1581
+ const { name } = await request.json();
1582
+ const key = \`mcp_\${crypto.randomBytes(32).toString('hex')}\`;
1583
+ const key_hash = crypto.createHash('sha256').update(key).digest('hex');
1584
+
1585
+ // TODO: \uD604\uC7AC \uB85C\uADF8\uC778 \uC0AC\uC6A9\uC790 ID \uC5F0\uB3D9
1586
+ const user_id = 'TODO-REPLACE-WITH-AUTH-USER-ID';
1587
+
1588
+ const { data, error } = await supabase
1589
+ .from('api_keys')
1590
+ .insert({ user_id, name, key_hash })
1591
+ .select('id, name, created_at')
1592
+ .single();
1593
+ if (error) return NextResponse.json({ error: error.message }, { status: 500 });
1594
+
1595
+ return NextResponse.json({ apiKey: data, key });
1596
+ }
1597
+ `;
1598
+ TOOLS_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1599
+ import {
1600
+ CallToolRequestSchema,
1601
+ ListToolsRequestSchema,
1602
+ } from '@modelcontextprotocol/sdk/types.js';
1603
+
1604
+ export function registerTools(server: Server): void {
1605
+ // Tool \uBAA9\uB85D \uC815\uC758
1606
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1607
+ tools: [
1608
+ {
1609
+ name: 'hello',
1610
+ description: '\uD14C\uC2A4\uD2B8\uC6A9 \uC778\uC0AC \uB3C4\uAD6C \u2014 \uC774 \uD15C\uD50C\uB9BF\uC744 \uC218\uC815\uD574\uC11C \uC2E4\uC81C \uB3C4\uAD6C\uB97C \uAD6C\uD604\uD558\uC138\uC694',
1611
+ inputSchema: {
1612
+ type: 'object',
1613
+ properties: {
1614
+ name: { type: 'string', description: '\uC778\uC0AC\uD560 \uB300\uC0C1' },
1615
+ },
1616
+ required: ['name'],
1617
+ },
1618
+ },
1619
+ ],
1620
+ }));
1621
+
1622
+ // Tool \uD638\uCD9C \uCC98\uB9AC
1623
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1624
+ const { name, arguments: args } = request.params;
1625
+
1626
+ if (name === 'hello') {
1627
+ const target = (args as { name?: string })?.name ?? 'world';
1628
+ return {
1629
+ content: [{ type: 'text', text: \`Hello, \${target}!\` }],
1630
+ };
1631
+ }
1632
+
1633
+ throw new Error(\`Unknown tool: \${name}\`);
1634
+ });
1635
+ }
1636
+ `;
1637
+ RESOURCES_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1638
+ import {
1639
+ ListResourcesRequestSchema,
1640
+ ReadResourceRequestSchema,
1641
+ } from '@modelcontextprotocol/sdk/types.js';
1642
+
1643
+ export function registerResources(server: Server): void {
1644
+ // Resource \uBAA9\uB85D \uC815\uC758
1645
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1646
+ resources: [
1647
+ {
1648
+ uri: 'example://sample',
1649
+ name: 'Sample Resource',
1650
+ description: '\uC608\uC81C \uB9AC\uC18C\uC2A4 \u2014 AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8',
1651
+ mimeType: 'text/plain',
1652
+ },
1653
+ ],
1654
+ }));
1655
+
1656
+ // Resource \uC77D\uAE30
1657
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1658
+ const { uri } = request.params;
1659
+
1660
+ if (uri === 'example://sample') {
1661
+ return {
1662
+ contents: [{
1663
+ uri,
1664
+ mimeType: 'text/plain',
1665
+ text: 'This is a sample resource content.',
1666
+ }],
1667
+ };
1668
+ }
1669
+
1670
+ throw new Error(\`Unknown resource: \${uri}\`);
1671
+ });
1672
+ }
1673
+ `;
1674
+ PROMPTS_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1675
+ import {
1676
+ GetPromptRequestSchema,
1677
+ ListPromptsRequestSchema,
1678
+ } from '@modelcontextprotocol/sdk/types.js';
1679
+
1680
+ export function registerPrompts(server: Server): void {
1681
+ // Prompt \uBAA9\uB85D \uC815\uC758
1682
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1683
+ prompts: [
1684
+ {
1685
+ name: 'review-code',
1686
+ description: '\uCF54\uB4DC \uB9AC\uBDF0 \uC694\uCCAD \uD504\uB86C\uD504\uD2B8',
1687
+ arguments: [
1688
+ { name: 'code', description: '\uB9AC\uBDF0\uD560 \uCF54\uB4DC', required: true },
1689
+ ],
1690
+ },
1691
+ ],
1692
+ }));
1693
+
1694
+ // Prompt \uAC00\uC838\uC624\uAE30
1695
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1696
+ const { name, arguments: args } = request.params;
1697
+
1698
+ if (name === 'review-code') {
1699
+ const code = (args as { code?: string })?.code ?? '';
1700
+ return {
1701
+ messages: [{
1702
+ role: 'user',
1703
+ content: {
1704
+ type: 'text',
1705
+ text: \`\uB2E4\uC74C \uCF54\uB4DC\uB97C \uB9AC\uBDF0\uD574\uC8FC\uC138\uC694:\\n\\n\${code}\`,
1706
+ },
1707
+ }],
1708
+ };
1709
+ }
1710
+
1711
+ throw new Error(\`Unknown prompt: \${name}\`);
1712
+ });
1713
+ }
1714
+ `;
1715
+ TEST_TEMPLATE = `import { describe, it, expect } from 'vitest';
1716
+
1717
+ describe('MCP Server', () => {
1718
+ it('should be tested', () => {
1719
+ // TODO: MCP \uC11C\uBC84 \uD14C\uC2A4\uD2B8 \uC791\uC131
1720
+ expect(true).toBe(true);
1721
+ });
1722
+ });
1723
+ `;
1724
+ }
1725
+ });
1726
+
918
1727
  // src/stages/environment/provisioners/registry.ts
919
1728
  var registry_exports = {};
920
1729
  __export(registry_exports, {
921
1730
  PROVISIONERS: () => PROVISIONERS,
922
1731
  runProvisioners: () => runProvisioners
923
1732
  });
924
- import { join as join3 } from "path";
925
- import fs4 from "fs-extra";
1733
+ import { join as join4 } from "path";
1734
+ import fs5 from "fs-extra";
926
1735
  async function provisionGitHub(ctx) {
927
- const gitDir = join3(ctx.cwd, ".git");
928
- if (!await fs4.pathExists(gitDir)) {
929
- await fs4.ensureDir(join3(ctx.cwd, ".github", "workflows"));
1736
+ const gitDir = join4(ctx.cwd, ".git");
1737
+ if (!await fs5.pathExists(gitDir)) {
1738
+ await fs5.ensureDir(join4(ctx.cwd, ".github", "workflows"));
930
1739
  try {
931
1740
  const { execa } = await import("execa");
932
1741
  await execa("git", ["init"], { cwd: ctx.cwd });
@@ -935,11 +1744,11 @@ async function provisionGitHub(ctx) {
935
1744
  }
936
1745
  }
937
1746
  const dirs = ["src", "docs", "tests", "public", ".pai"];
938
- await Promise.all(dirs.map((d) => fs4.ensureDir(join3(ctx.cwd, d))));
939
- const handoffPath = join3(ctx.cwd, "handoff.md");
940
- if (!await fs4.pathExists(handoffPath)) {
1747
+ await Promise.all(dirs.map((d) => fs5.ensureDir(join4(ctx.cwd, d))));
1748
+ const handoffPath = join4(ctx.cwd, "handoff.md");
1749
+ if (!await fs5.pathExists(handoffPath)) {
941
1750
  const now = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
942
- await fs4.writeFile(handoffPath, [
1751
+ await fs5.writeFile(handoffPath, [
943
1752
  `# Handoff \u2014 ${ctx.projectName}`,
944
1753
  "",
945
1754
  `> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${now}`,
@@ -968,9 +1777,9 @@ async function provisionGitHub(ctx) {
968
1777
  "- \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uBD80\uC871\uD558\uBA74 AI\uAC00 \uC790\uB3D9\uC73C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4"
969
1778
  ].join("\n") + "\n");
970
1779
  }
971
- const contribPath = join3(ctx.cwd, "CONTRIBUTING.md");
972
- if (!await fs4.pathExists(contribPath)) {
973
- await fs4.writeFile(contribPath, [
1780
+ const contribPath = join4(ctx.cwd, "CONTRIBUTING.md");
1781
+ if (!await fs5.pathExists(contribPath)) {
1782
+ await fs5.writeFile(contribPath, [
974
1783
  `# Contributing to ${ctx.projectName}`,
975
1784
  "",
976
1785
  "\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uAE30\uC5EC\uD574 \uC8FC\uC154\uC11C \uAC10\uC0AC\uD569\uB2C8\uB2E4.",
@@ -1014,11 +1823,11 @@ async function provisionGitHub(ctx) {
1014
1823
  ""
1015
1824
  ].join("\n") + "\n");
1016
1825
  }
1017
- const contribSkillDir = join3(ctx.cwd, ".claude", "skills");
1018
- await fs4.ensureDir(contribSkillDir);
1019
- const contribSkillPath = join3(contribSkillDir, "contributing.md");
1020
- if (!await fs4.pathExists(contribSkillPath)) {
1021
- await fs4.writeFile(contribSkillPath, [
1826
+ const contribSkillDir = join4(ctx.cwd, ".claude", "skills");
1827
+ await fs5.ensureDir(contribSkillDir);
1828
+ const contribSkillPath = join4(contribSkillDir, "contributing.md");
1829
+ if (!await fs5.pathExists(contribSkillPath)) {
1830
+ await fs5.writeFile(contribSkillPath, [
1022
1831
  "---",
1023
1832
  "name: contributing",
1024
1833
  'description: "\uAE30\uC5EC\uC790 \uAC00\uC774\uB4DC \u2014 \uBE0C\uB79C\uCE58 \uADDC\uCE59, \uCEE4\uBC0B \uBA54\uC2DC\uC9C0, PR \uD504\uB85C\uC138\uC2A4 \uC790\uB3D9 \uC801\uC6A9"',
@@ -1043,9 +1852,9 @@ async function provisionGitHub(ctx) {
1043
1852
  ""
1044
1853
  ].join("\n") + "\n");
1045
1854
  }
1046
- const gitignorePath = join3(ctx.cwd, ".gitignore");
1047
- if (!await fs4.pathExists(gitignorePath)) {
1048
- await fs4.writeFile(gitignorePath, [
1855
+ const gitignorePath = join4(ctx.cwd, ".gitignore");
1856
+ if (!await fs5.pathExists(gitignorePath)) {
1857
+ await fs5.writeFile(gitignorePath, [
1049
1858
  "# Dependencies",
1050
1859
  "node_modules/",
1051
1860
  "",
@@ -1090,9 +1899,9 @@ async function provisionGitHub(ctx) {
1090
1899
  }
1091
1900
  }
1092
1901
  async function provisionVercel(ctx) {
1093
- const vercelJson = join3(ctx.cwd, "vercel.json");
1094
- if (!await fs4.pathExists(vercelJson)) {
1095
- await fs4.writeJson(vercelJson, {
1902
+ const vercelJson = join4(ctx.cwd, "vercel.json");
1903
+ if (!await fs5.pathExists(vercelJson)) {
1904
+ await fs5.writeJson(vercelJson, {
1096
1905
  version: 2,
1097
1906
  name: ctx.projectName,
1098
1907
  builds: [{ src: "src/**", use: "@vercel/static" }]
@@ -1100,20 +1909,20 @@ async function provisionVercel(ctx) {
1100
1909
  }
1101
1910
  }
1102
1911
  async function provisionSupabase(ctx) {
1103
- const supabaseDir = join3(ctx.cwd, "supabase");
1104
- await fs4.ensureDir(supabaseDir);
1105
- const configToml = join3(supabaseDir, "config.toml");
1106
- if (!await fs4.pathExists(configToml)) {
1107
- await fs4.writeFile(configToml, '[api]\nschemas = ["public"]\n[auth]\nsite_url = "http://localhost:3000"\n');
1912
+ const supabaseDir = join4(ctx.cwd, "supabase");
1913
+ await fs5.ensureDir(supabaseDir);
1914
+ const configToml = join4(supabaseDir, "config.toml");
1915
+ if (!await fs5.pathExists(configToml)) {
1916
+ await fs5.writeFile(configToml, '[api]\nschemas = ["public"]\n[auth]\nsite_url = "http://localhost:3000"\n');
1108
1917
  }
1109
1918
  ctx.envEntries["NEXT_PUBLIC_SUPABASE_URL"] = "YOUR_SUPABASE_URL";
1110
1919
  ctx.envEntries["NEXT_PUBLIC_SUPABASE_ANON_KEY"] = "YOUR_SUPABASE_ANON_KEY";
1111
1920
  }
1112
1921
  async function provisionOpenSpec(ctx) {
1113
- await fs4.ensureDir(join3(ctx.cwd, "docs"));
1114
- const openspecPath = join3(ctx.cwd, "docs", "openspec.md");
1115
- if (!await fs4.pathExists(openspecPath)) {
1116
- await fs4.writeFile(openspecPath, [
1922
+ await fs5.ensureDir(join4(ctx.cwd, "docs"));
1923
+ const openspecPath = join4(ctx.cwd, "docs", "openspec.md");
1924
+ if (!await fs5.pathExists(openspecPath)) {
1925
+ await fs5.writeFile(openspecPath, [
1117
1926
  `# OpenSpec \u2014 ${ctx.projectName}`,
1118
1927
  "",
1119
1928
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -1140,10 +1949,10 @@ async function provisionOpenSpec(ctx) {
1140
1949
  }
1141
1950
  }
1142
1951
  async function provisionOMC(ctx) {
1143
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1144
- const omcPath = join3(ctx.cwd, ".pai", "omc.md");
1145
- if (!await fs4.pathExists(omcPath)) {
1146
- await fs4.writeFile(omcPath, [
1952
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1953
+ const omcPath = join4(ctx.cwd, ".pai", "omc.md");
1954
+ if (!await fs5.pathExists(omcPath)) {
1955
+ await fs5.writeFile(omcPath, [
1147
1956
  `# OMC \u2014 Object Model Context (${ctx.projectName})`,
1148
1957
  "",
1149
1958
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -1163,8 +1972,8 @@ async function provisionOMC(ctx) {
1163
1972
  }
1164
1973
  }
1165
1974
  async function provisionGstack(ctx) {
1166
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1167
- await fs4.writeJson(join3(ctx.cwd, ".pai", "gstack.json"), {
1975
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1976
+ await fs5.writeJson(join4(ctx.cwd, ".pai", "gstack.json"), {
1168
1977
  version: "1.0",
1169
1978
  testRunner: "jest",
1170
1979
  coverageThreshold: { global: { lines: 80 } },
@@ -1172,10 +1981,10 @@ async function provisionGstack(ctx) {
1172
1981
  }, { spaces: 2 });
1173
1982
  }
1174
1983
  async function provisionRoboco(ctx) {
1175
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1176
- const robocoPath = join3(ctx.cwd, ".pai", "roboco.json");
1177
- if (await fs4.pathExists(robocoPath)) return;
1178
- await fs4.writeJson(robocoPath, {
1984
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1985
+ const robocoPath = join4(ctx.cwd, ".pai", "roboco.json");
1986
+ if (await fs5.pathExists(robocoPath)) return;
1987
+ await fs5.writeJson(robocoPath, {
1179
1988
  version: "1.0",
1180
1989
  checks: ["github", "vercel", "supabase", "openspec", "omc"],
1181
1990
  reportOutput: "AI_READINESS_REPORT.md",
@@ -1183,14 +1992,18 @@ async function provisionRoboco(ctx) {
1183
1992
  }, { spaces: 2 });
1184
1993
  }
1185
1994
  async function provisionHarness(ctx) {
1186
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1187
- await fs4.writeJson(join3(ctx.cwd, ".pai", "harness.json"), {
1995
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1996
+ await fs5.writeJson(join4(ctx.cwd, ".pai", "harness.json"), {
1188
1997
  version: "1.0",
1189
1998
  specFile: "docs/openspec.md",
1190
1999
  checkOn: ["pre-commit", "ci"],
1191
2000
  rules: ["spec-implementation-match", "api-contract-test"]
1192
2001
  }, { spaces: 2 });
1193
2002
  }
2003
+ async function provisionMcpWrapper(ctx) {
2004
+ const { provisionMcp: provisionMcp2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
2005
+ await provisionMcp2(ctx);
2006
+ }
1194
2007
  async function runProvisioners(keys, ctx) {
1195
2008
  const results = [];
1196
2009
  for (const key of keys) {
@@ -1208,7 +2021,7 @@ async function runProvisioners(keys, ctx) {
1208
2021
  }
1209
2022
  }
1210
2023
  if (Object.keys(ctx.envEntries).length > 0) {
1211
- writeEnvFile(join3(ctx.cwd, ".env.local"), ctx.envEntries);
2024
+ writeEnvFile(join4(ctx.cwd, ".env.local"), ctx.envEntries);
1212
2025
  }
1213
2026
  return results;
1214
2027
  }
@@ -1225,7 +2038,8 @@ var init_registry = __esm({
1225
2038
  omc: provisionOMC,
1226
2039
  gstack: provisionGstack,
1227
2040
  roboco: provisionRoboco,
1228
- harness: provisionHarness
2041
+ harness: provisionHarness,
2042
+ mcp: provisionMcpWrapper
1229
2043
  };
1230
2044
  }
1231
2045
  });
@@ -1236,21 +2050,21 @@ __export(claude_commands_exports, {
1236
2050
  provisionClaudeCommands: () => provisionClaudeCommands,
1237
2051
  upgradeClaudeCommands: () => upgradeClaudeCommands
1238
2052
  });
1239
- import { join as join4 } from "path";
1240
- import fs5 from "fs-extra";
2053
+ import { join as join5 } from "path";
2054
+ import fs6 from "fs-extra";
1241
2055
  async function writeCommandFiles(cmdDir, entries, options) {
1242
- await fs5.ensureDir(cmdDir);
2056
+ await fs6.ensureDir(cmdDir);
1243
2057
  const written = [];
1244
2058
  const skipped = [];
1245
2059
  const errors = [];
1246
2060
  for (const [filename, content] of Object.entries(entries)) {
1247
- const filePath = join4(cmdDir, filename);
2061
+ const filePath = join5(cmdDir, filename);
1248
2062
  try {
1249
- if (options.skipIfExists && await fs5.pathExists(filePath)) {
2063
+ if (options.skipIfExists && await fs6.pathExists(filePath)) {
1250
2064
  skipped.push(filename);
1251
2065
  continue;
1252
2066
  }
1253
- await fs5.writeFile(filePath, content);
2067
+ await fs6.writeFile(filePath, content);
1254
2068
  written.push(filename);
1255
2069
  } catch (err) {
1256
2070
  const msg = err instanceof Error ? err.message : String(err);
@@ -1260,13 +2074,13 @@ async function writeCommandFiles(cmdDir, entries, options) {
1260
2074
  return { written, skipped, errors };
1261
2075
  }
1262
2076
  async function provisionClaudeCommands(cwd) {
1263
- const skillDir = join4(cwd, ".claude", "skills", "pai");
1264
- await fs5.ensureDir(skillDir);
1265
- const skillPath = join4(skillDir, "SKILL.md");
1266
- if (!await fs5.pathExists(skillPath)) {
1267
- await fs5.writeFile(skillPath, SKILL_CONTENT);
2077
+ const skillDir = join5(cwd, ".claude", "skills", "pai");
2078
+ await fs6.ensureDir(skillDir);
2079
+ const skillPath = join5(skillDir, "SKILL.md");
2080
+ if (!await fs6.pathExists(skillPath)) {
2081
+ await fs6.writeFile(skillPath, SKILL_CONTENT);
1268
2082
  }
1269
- const cmdDir = join4(cwd, ".claude", "commands", "pai");
2083
+ const cmdDir = join5(cwd, ".claude", "commands", "pai");
1270
2084
  await writeCommandFiles(cmdDir, COMMANDS, { skipIfExists: true });
1271
2085
  }
1272
2086
  async function upgradeClaudeCommands(cwd) {
@@ -1275,16 +2089,16 @@ async function upgradeClaudeCommands(cwd) {
1275
2089
  commandsWritten: [],
1276
2090
  commandErrors: []
1277
2091
  };
1278
- const skillDir = join4(cwd, ".claude", "skills", "pai");
1279
- await fs5.ensureDir(skillDir);
2092
+ const skillDir = join5(cwd, ".claude", "skills", "pai");
2093
+ await fs6.ensureDir(skillDir);
1280
2094
  try {
1281
- await fs5.writeFile(join4(skillDir, "SKILL.md"), SKILL_CONTENT);
2095
+ await fs6.writeFile(join5(skillDir, "SKILL.md"), SKILL_CONTENT);
1282
2096
  result.skillUpdated = true;
1283
2097
  } catch (err) {
1284
2098
  const msg = err instanceof Error ? err.message : String(err);
1285
2099
  result.commandErrors.push({ file: "SKILL.md", error: msg });
1286
2100
  }
1287
- const cmdDir = join4(cwd, ".claude", "commands", "pai");
2101
+ const cmdDir = join5(cwd, ".claude", "commands", "pai");
1288
2102
  const { written, errors } = await writeCommandFiles(cmdDir, COMMANDS, { skipIfExists: false });
1289
2103
  result.commandsWritten = written;
1290
2104
  result.commandErrors.push(...errors);
@@ -1906,18 +2720,18 @@ npx pai-zero wakeup # \uC9C0\uAE08 \uB79C\uB364 \uBA54\uC2DC\uC9
1906
2720
  // src/core/config.ts
1907
2721
  import path from "path";
1908
2722
  import { createRequire } from "module";
1909
- import fs6 from "fs-extra";
2723
+ import fs7 from "fs-extra";
1910
2724
  async function loadConfig(cwd) {
1911
2725
  const configPath = path.join(cwd, CONFIG_DIR, CONFIG_FILE);
1912
- if (await fs6.pathExists(configPath)) {
1913
- return fs6.readJson(configPath);
2726
+ if (await fs7.pathExists(configPath)) {
2727
+ return fs7.readJson(configPath);
1914
2728
  }
1915
2729
  return null;
1916
2730
  }
1917
2731
  async function saveConfig(cwd, config) {
1918
2732
  const configDir = path.join(cwd, CONFIG_DIR);
1919
- await fs6.ensureDir(configDir);
1920
- await fs6.writeJson(path.join(configDir, CONFIG_FILE), config, { spaces: 2 });
2733
+ await fs7.ensureDir(configDir);
2734
+ await fs7.writeJson(path.join(configDir, CONFIG_FILE), config, { spaces: 2 });
1921
2735
  }
1922
2736
  function createDefaultConfig(projectName, mode) {
1923
2737
  return {
@@ -1944,9 +2758,9 @@ var doctor_exports = {};
1944
2758
  __export(doctor_exports, {
1945
2759
  runDoctor: () => runDoctor
1946
2760
  });
1947
- import { join as join5 } from "path";
2761
+ import { join as join6 } from "path";
1948
2762
  import { homedir } from "os";
1949
- import fs7 from "fs-extra";
2763
+ import fs8 from "fs-extra";
1950
2764
  async function runDoctor() {
1951
2765
  section("PAI Doctor \u2014 \uD658\uACBD \uC9C4\uB2E8");
1952
2766
  const checks = [];
@@ -1965,8 +2779,8 @@ async function runDoctor() {
1965
2779
  detail: claudeCheck.detail,
1966
2780
  fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
1967
2781
  });
1968
- const globalConfigPath = join5(homedir(), ".pai", "config.json");
1969
- const hasGlobalConfig = await fs7.pathExists(globalConfigPath);
2782
+ const globalConfigPath = join6(homedir(), ".pai", "config.json");
2783
+ const hasGlobalConfig = await fs8.pathExists(globalConfigPath);
1970
2784
  checks.push({
1971
2785
  label: "\uAE00\uB85C\uBC8C \uC124\uC815",
1972
2786
  ok: true,
@@ -2088,7 +2902,8 @@ var init_environment = __esm({
2088
2902
  envEntries: {
2089
2903
  PAI_PROJECT_NAME: interview.projectName,
2090
2904
  PAI_MODE: interview.mode
2091
- }
2905
+ },
2906
+ mcp: interview.mcp
2092
2907
  };
2093
2908
  if (interview.authMethods.includes("custom")) {
2094
2909
  provCtx.envEntries["OAUTH_CLIENT_ID"] = interview.customAuth?.clientId || "YOUR_CLIENT_ID_HERE";
@@ -2108,6 +2923,7 @@ var init_environment = __esm({
2108
2923
  ...interview.extraTools.includes("supabase") ? ["supabase/config.toml"] : [],
2109
2924
  ...interview.extraTools.includes("gstack") ? [".pai/gstack.json"] : [],
2110
2925
  ...interview.extraTools.includes("harness") ? [".pai/harness.json"] : [],
2926
+ ...interview.extraTools.includes("mcp") ? ["mcp-server/", ".mcp.json"] : [],
2111
2927
  ".env.local"
2112
2928
  ];
2113
2929
  console.log("");
@@ -2175,7 +2991,7 @@ __export(detector_exports, {
2175
2991
  scanProjectState: () => scanProjectState
2176
2992
  });
2177
2993
  import path2 from "path";
2178
- import fs8 from "fs-extra";
2994
+ import fs9 from "fs-extra";
2179
2995
  async function scanProjectState(cwd) {
2180
2996
  const result = {
2181
2997
  isNewProject: true,
@@ -2186,10 +3002,10 @@ async function scanProjectState(cwd) {
2186
3002
  details: {}
2187
3003
  };
2188
3004
  const paiConfigPath = path2.join(cwd, ".pai", "config.json");
2189
- if (await fs8.pathExists(paiConfigPath)) {
3005
+ if (await fs9.pathExists(paiConfigPath)) {
2190
3006
  result.hasPaiConfig = true;
2191
3007
  try {
2192
- const config = await fs8.readJson(paiConfigPath);
3008
+ const config = await fs9.readJson(paiConfigPath);
2193
3009
  result.projectMode = config.mode ?? null;
2194
3010
  } catch {
2195
3011
  }
@@ -2197,7 +3013,7 @@ async function scanProjectState(cwd) {
2197
3013
  for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
2198
3014
  const installed = await Promise.any(
2199
3015
  signatures.map(async (sig) => {
2200
- if (await fs8.pathExists(path2.join(cwd, sig))) return true;
3016
+ if (await fs9.pathExists(path2.join(cwd, sig))) return true;
2201
3017
  throw new Error("not found");
2202
3018
  })
2203
3019
  ).catch(() => false);
@@ -2208,7 +3024,7 @@ async function scanProjectState(cwd) {
2208
3024
  result.missingPlugins.push(key);
2209
3025
  }
2210
3026
  }
2211
- const hasAnyContent = result.installedPlugins.length > 0 || await fs8.pathExists(path2.join(cwd, "package.json")) || await fs8.pathExists(path2.join(cwd, "src")) || await fs8.pathExists(path2.join(cwd, "README.md"));
3027
+ const hasAnyContent = result.installedPlugins.length > 0 || await fs9.pathExists(path2.join(cwd, "package.json")) || await fs9.pathExists(path2.join(cwd, "src")) || await fs9.pathExists(path2.join(cwd, "README.md"));
2212
3028
  result.isNewProject = !hasAnyContent;
2213
3029
  return result;
2214
3030
  }
@@ -2338,8 +3154,8 @@ var analyzer_exports2 = {};
2338
3154
  __export(analyzer_exports2, {
2339
3155
  analyzeRepository: () => analyzeRepository
2340
3156
  });
2341
- import { join as join6 } from "path";
2342
- import fs9 from "fs-extra";
3157
+ import { join as join7 } from "path";
3158
+ import fs10 from "fs-extra";
2343
3159
  async function analyzeRepository(repoPath) {
2344
3160
  try {
2345
3161
  return await aiAnalysis(repoPath);
@@ -2400,14 +3216,14 @@ async function checkTestCoverage(repoPath) {
2400
3216
  ".nycrc"
2401
3217
  ];
2402
3218
  for (const f of testConfigs) {
2403
- const found = await fs9.pathExists(join6(repoPath, f));
3219
+ const found = await fs10.pathExists(join7(repoPath, f));
2404
3220
  findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2405
3221
  if (found) score += 20;
2406
3222
  }
2407
3223
  const testDirs = ["tests", "test", "__tests__", "spec"];
2408
3224
  let hasTestDir = false;
2409
3225
  for (const d of testDirs) {
2410
- if (await fs9.pathExists(join6(repoPath, d))) {
3226
+ if (await fs10.pathExists(join7(repoPath, d))) {
2411
3227
  findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
2412
3228
  hasTestDir = true;
2413
3229
  score += 30;
@@ -2431,7 +3247,7 @@ async function checkCiCd(repoPath) {
2431
3247
  { path: ".circleci", label: "CircleCI" }
2432
3248
  ];
2433
3249
  for (const { path: path3, label } of ciConfigs) {
2434
- const found = await fs9.pathExists(join6(repoPath, path3));
3250
+ const found = await fs10.pathExists(join7(repoPath, path3));
2435
3251
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2436
3252
  if (found) score += 40;
2437
3253
  }
@@ -2450,7 +3266,7 @@ async function checkHooks(repoPath) {
2450
3266
  { path: ".claude/settings.json", label: "Claude Code settings" }
2451
3267
  ];
2452
3268
  for (const { path: path3, label } of hookConfigs) {
2453
- const found = await fs9.pathExists(join6(repoPath, path3));
3269
+ const found = await fs10.pathExists(join7(repoPath, path3));
2454
3270
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2455
3271
  if (found) score += 20;
2456
3272
  }
@@ -2468,7 +3284,7 @@ async function checkRepoStructure(repoPath) {
2468
3284
  { path: ".gitignore", label: ".gitignore" }
2469
3285
  ];
2470
3286
  for (const { path: path3, label } of structureChecks) {
2471
- const found = await fs9.pathExists(join6(repoPath, path3));
3287
+ const found = await fs10.pathExists(join7(repoPath, path3));
2472
3288
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2473
3289
  if (found) score += 25;
2474
3290
  }
@@ -2485,7 +3301,7 @@ async function checkDocumentation(repoPath) {
2485
3301
  { path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
2486
3302
  ];
2487
3303
  for (const { path: path3, label, points } of docChecks) {
2488
- const found = await fs9.pathExists(join6(repoPath, path3));
3304
+ const found = await fs10.pathExists(join7(repoPath, path3));
2489
3305
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2490
3306
  if (found) score += points;
2491
3307
  }
@@ -2504,7 +3320,7 @@ async function checkHarnessEngineering(repoPath) {
2504
3320
  { path: ".pai/config.json", label: "PAI config", points: 10 }
2505
3321
  ];
2506
3322
  for (const { path: path3, label, points } of harnessChecks) {
2507
- const found = await fs9.pathExists(join6(repoPath, path3));
3323
+ const found = await fs10.pathExists(join7(repoPath, path3));
2508
3324
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2509
3325
  if (found) score += points;
2510
3326
  }
@@ -2887,56 +3703,56 @@ __export(shell_cd_exports, {
2887
3703
  installShellHelper: () => installShellHelper,
2888
3704
  requestCdAfter: () => requestCdAfter
2889
3705
  });
2890
- import { join as join7 } from "path";
3706
+ import { join as join8 } from "path";
2891
3707
  import { homedir as homedir2 } from "os";
2892
- import fs10 from "fs-extra";
3708
+ import fs11 from "fs-extra";
2893
3709
  async function requestCdAfter(targetDir) {
2894
- await fs10.ensureDir(PAI_DIR);
2895
- await fs10.writeFile(CD_FILE, targetDir);
3710
+ await fs11.ensureDir(PAI_DIR);
3711
+ await fs11.writeFile(CD_FILE, targetDir);
2896
3712
  }
2897
3713
  async function installShellHelper() {
2898
- await fs10.ensureDir(PAI_DIR);
3714
+ await fs11.ensureDir(PAI_DIR);
2899
3715
  if (isWindows) {
2900
3716
  return installPowerShellHelper();
2901
3717
  }
2902
3718
  return installBashHelper();
2903
3719
  }
2904
3720
  async function installBashHelper() {
2905
- await fs10.writeFile(HELPER_FILE_SH, BASH_HELPER);
3721
+ await fs11.writeFile(HELPER_FILE_SH, BASH_HELPER);
2906
3722
  const rcFile = getShellRcPath();
2907
3723
  const sourceLine = 'source "$HOME/.pai/shell-helper.sh"';
2908
- if (await fs10.pathExists(rcFile)) {
2909
- const content = await fs10.readFile(rcFile, "utf8");
3724
+ if (await fs11.pathExists(rcFile)) {
3725
+ const content = await fs11.readFile(rcFile, "utf8");
2910
3726
  if (content.includes("shell-helper.sh")) {
2911
3727
  return true;
2912
3728
  }
2913
- await fs10.appendFile(rcFile, `
3729
+ await fs11.appendFile(rcFile, `
2914
3730
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
2915
3731
  ${sourceLine}
2916
3732
  `);
2917
3733
  return false;
2918
3734
  }
2919
- await fs10.writeFile(rcFile, `${sourceLine}
3735
+ await fs11.writeFile(rcFile, `${sourceLine}
2920
3736
  `);
2921
3737
  return false;
2922
3738
  }
2923
3739
  async function installPowerShellHelper() {
2924
- await fs10.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
3740
+ await fs11.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
2925
3741
  const rcFile = getShellRcPath();
2926
3742
  const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
2927
- await fs10.ensureDir(join7(rcFile, ".."));
2928
- if (await fs10.pathExists(rcFile)) {
2929
- const content = await fs10.readFile(rcFile, "utf8");
3743
+ await fs11.ensureDir(join8(rcFile, ".."));
3744
+ if (await fs11.pathExists(rcFile)) {
3745
+ const content = await fs11.readFile(rcFile, "utf8");
2930
3746
  if (content.includes("shell-helper.ps1")) {
2931
3747
  return true;
2932
3748
  }
2933
- await fs10.appendFile(rcFile, `
3749
+ await fs11.appendFile(rcFile, `
2934
3750
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
2935
3751
  ${sourceLine}
2936
3752
  `);
2937
3753
  return false;
2938
3754
  }
2939
- await fs10.writeFile(rcFile, `${sourceLine}
3755
+ await fs11.writeFile(rcFile, `${sourceLine}
2940
3756
  `);
2941
3757
  return false;
2942
3758
  }
@@ -2945,10 +3761,10 @@ var init_shell_cd = __esm({
2945
3761
  "src/utils/shell-cd.ts"() {
2946
3762
  "use strict";
2947
3763
  init_platform();
2948
- PAI_DIR = join7(homedir2(), ".pai");
2949
- CD_FILE = join7(PAI_DIR, ".cd-after");
2950
- HELPER_FILE_SH = join7(PAI_DIR, "shell-helper.sh");
2951
- HELPER_FILE_PS1 = join7(PAI_DIR, "shell-helper.ps1");
3764
+ PAI_DIR = join8(homedir2(), ".pai");
3765
+ CD_FILE = join8(PAI_DIR, ".cd-after");
3766
+ HELPER_FILE_SH = join8(PAI_DIR, "shell-helper.sh");
3767
+ HELPER_FILE_PS1 = join8(PAI_DIR, "shell-helper.ps1");
2952
3768
  BASH_HELPER = `# PAI shell helper \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9 \uC9C0\uC6D0
2953
3769
  pai() {
2954
3770
  local cd_target="$HOME/.pai/.cd-after"
@@ -2990,12 +3806,12 @@ function pai {
2990
3806
 
2991
3807
  // src/stages/evaluation/cache.ts
2992
3808
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2993
- import { join as join8 } from "path";
3809
+ import { join as join9 } from "path";
2994
3810
  import { createHash } from "crypto";
2995
3811
  function computeRepoHash(repoPath) {
2996
3812
  const hash = createHash("sha256");
2997
3813
  for (const file of FILES_TO_HASH) {
2998
- const fullPath = join8(repoPath, file);
3814
+ const fullPath = join9(repoPath, file);
2999
3815
  try {
3000
3816
  const content = readFileSync(fullPath);
3001
3817
  hash.update(`${file}:${content.length}`);
@@ -3006,7 +3822,7 @@ function computeRepoHash(repoPath) {
3006
3822
  return hash.digest("hex").slice(0, 16);
3007
3823
  }
3008
3824
  function getCachePath(repoPath) {
3009
- return join8(repoPath, CACHE_DIR, CACHE_FILE);
3825
+ return join9(repoPath, CACHE_DIR, CACHE_FILE);
3010
3826
  }
3011
3827
  function loadCache(repoPath) {
3012
3828
  try {
@@ -3017,7 +3833,7 @@ function loadCache(repoPath) {
3017
3833
  }
3018
3834
  }
3019
3835
  function saveCache(repoPath, store) {
3020
- const cacheDir = join8(repoPath, CACHE_DIR, "cache");
3836
+ const cacheDir = join9(repoPath, CACHE_DIR, "cache");
3021
3837
  if (!existsSync(cacheDir)) {
3022
3838
  mkdirSync(cacheDir, { recursive: true });
3023
3839
  }
@@ -3076,8 +3892,8 @@ var evaluate_cmd_exports = {};
3076
3892
  __export(evaluate_cmd_exports, {
3077
3893
  evaluateCommand: () => evaluateCommand
3078
3894
  });
3079
- import { join as join9, basename } from "path";
3080
- import fs11 from "fs-extra";
3895
+ import { join as join10, basename } from "path";
3896
+ import fs12 from "fs-extra";
3081
3897
  async function evaluateCommand(cwd, options) {
3082
3898
  const useCache = options.cache !== false;
3083
3899
  let llmOutput = useCache ? getCachedResult(cwd) : null;
@@ -3097,17 +3913,17 @@ async function evaluateCommand(cwd, options) {
3097
3913
  const config = await loadConfig(cwd);
3098
3914
  const projectName = config?.projectName ?? basename(cwd);
3099
3915
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3100
- const reportDir = join9(cwd, "docs", "p-reports");
3101
- const reportPath = join9(reportDir, `${today}.md`);
3102
- await fs11.ensureDir(reportDir);
3916
+ const reportDir = join10(cwd, "docs", "p-reports");
3917
+ const reportPath = join10(reportDir, `${today}.md`);
3918
+ await fs12.ensureDir(reportDir);
3103
3919
  const detailedReport = buildDetailedReport(result, projectName);
3104
- await fs11.writeFile(reportPath, detailedReport, "utf8");
3920
+ await fs12.writeFile(reportPath, detailedReport, "utf8");
3105
3921
  console.log("");
3106
3922
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
3107
3923
  console.log("");
3108
3924
  console.log(detailedReport);
3109
3925
  if (options.output) {
3110
- await fs11.writeFile(options.output, detailedReport, "utf8");
3926
+ await fs12.writeFile(options.output, detailedReport, "utf8");
3111
3927
  success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
3112
3928
  }
3113
3929
  if (options.failUnder && result.totalScore < options.failUnder) {
@@ -3235,8 +4051,8 @@ var init_cmd_exports = {};
3235
4051
  __export(init_cmd_exports, {
3236
4052
  initCommand: () => initCommand
3237
4053
  });
3238
- import { join as join10, basename as basename2 } from "path";
3239
- import fs12 from "fs-extra";
4054
+ import { join as join11, basename as basename2 } from "path";
4055
+ import fs13 from "fs-extra";
3240
4056
  async function initCommand(cwd, nameArg) {
3241
4057
  printBanner();
3242
4058
  const { isWindows: isWindows2, diagnoseWindowsEnv: diagnoseWindowsEnv2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
@@ -3294,11 +4110,11 @@ async function initCommand(cwd, nameArg) {
3294
4110
  const evalResult = computeResult2(llmOutput);
3295
4111
  printReport2(evalResult);
3296
4112
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3297
- const reportDir = join10(cwd, "docs", "p-reports");
3298
- await fs12.ensureDir(reportDir);
4113
+ const reportDir = join11(cwd, "docs", "p-reports");
4114
+ await fs13.ensureDir(reportDir);
3299
4115
  const legacyName = basename2(cwd);
3300
4116
  const detailedReport = buildDetailedReport3(evalResult, legacyName);
3301
- await fs12.writeFile(join10(reportDir, `${today}.md`), detailedReport, "utf8");
4117
+ await fs13.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
3302
4118
  console.log("");
3303
4119
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
3304
4120
  } catch {
@@ -3343,8 +4159,8 @@ async function initCommand(cwd, nameArg) {
3343
4159
  }]);
3344
4160
  projectName = answer.name.trim();
3345
4161
  }
3346
- const projectDir = join10(cwd, projectName);
3347
- if (await fs12.pathExists(projectDir)) {
4162
+ const projectDir = join11(cwd, projectName);
4163
+ if (await fs13.pathExists(projectDir)) {
3348
4164
  const existingConfig = await loadConfig(projectDir);
3349
4165
  if (existingConfig) {
3350
4166
  console.log("");
@@ -3366,7 +4182,7 @@ async function initCommand(cwd, nameArg) {
3366
4182
  return;
3367
4183
  }
3368
4184
  } else {
3369
- await fs12.ensureDir(projectDir);
4185
+ await fs13.ensureDir(projectDir);
3370
4186
  success(`${projectName}/ \uD3F4\uB354 \uC0DD\uC131`);
3371
4187
  }
3372
4188
  await setupInDirectory(projectDir, projectName);
@@ -3433,6 +4249,9 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3433
4249
  if (extraTools.includes("supabase")) {
3434
4250
  console.log(` ${chalk8.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0 (PostgreSQL \uAE30\uBC18)`);
3435
4251
  }
4252
+ if (extraTools.includes("mcp")) {
4253
+ console.log(` ${chalk8.cyan("MCP \uC11C\uBC84")} AI \uB3C4\uAD6C \u2014 Claude\uAC00 \uD638\uCD9C\uD560 \uCEE4\uC2A4\uD140 \uAE30\uB2A5/\uB370\uC774\uD130`);
4254
+ }
3436
4255
  console.log("");
3437
4256
  console.log(colors.accent(" \uC124\uCE58 \uD655\uC778"));
3438
4257
  console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
@@ -3444,7 +4263,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3444
4263
  { label: "\uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC", path: ".claude/skills/pai/SKILL.md" }
3445
4264
  ];
3446
4265
  for (const check of checks) {
3447
- const exists = await fs12.pathExists(join10(projectDir, check.path));
4266
+ const exists = await fs13.pathExists(join11(projectDir, check.path));
3448
4267
  console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
3449
4268
  }
3450
4269
  console.log("");
@@ -3469,10 +4288,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3469
4288
  printReport2(evalResult);
3470
4289
  await sleep2(500);
3471
4290
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3472
- const reportDir = join10(projectDir, "docs", "p-reports");
3473
- await fs12.ensureDir(reportDir);
4291
+ const reportDir = join11(projectDir, "docs", "p-reports");
4292
+ await fs13.ensureDir(reportDir);
3474
4293
  const detailedReport = buildDetailedReport3(evalResult, projectName);
3475
- await fs12.writeFile(join10(reportDir, `${today}.md`), detailedReport, "utf8");
4294
+ await fs13.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
3476
4295
  await sleep2(500);
3477
4296
  console.log("");
3478
4297
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
@@ -3500,7 +4319,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3500
4319
  const shellRc = getShellRcPath2();
3501
4320
  let hasYoloAliasSet = false;
3502
4321
  try {
3503
- const rcContent = await fs12.readFile(shellRc, "utf8");
4322
+ const rcContent = await fs13.readFile(shellRc, "utf8");
3504
4323
  hasYoloAliasSet = checkYolo(rcContent);
3505
4324
  } catch {
3506
4325
  }
@@ -3521,10 +4340,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3521
4340
  const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
3522
4341
  const aliasLine = getYoloAliasLine2();
3523
4342
  try {
3524
- const rcContent = await fs12.readFile(shellRc, "utf8").catch(() => "");
4343
+ const rcContent = await fs13.readFile(shellRc, "utf8").catch(() => "");
3525
4344
  if (!rcContent.includes("claude-yolo")) {
3526
- await fs12.ensureDir(join10(shellRc, ".."));
3527
- await fs12.appendFile(shellRc, `
4345
+ await fs13.ensureDir(join11(shellRc, ".."));
4346
+ await fs13.appendFile(shellRc, `
3528
4347
  # PAI \u2014 claude-YOLO mode
3529
4348
  ${aliasLine}
3530
4349
  `);
@@ -3755,9 +4574,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
3755
4574
  const evalResult = computeResult2(llmOutput);
3756
4575
  printReport2(evalResult);
3757
4576
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3758
- const reportDir = join10(projectDir, "docs", "p-reports");
3759
- await fs12.ensureDir(reportDir);
3760
- await fs12.writeFile(join10(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
4577
+ const reportDir = join11(projectDir, "docs", "p-reports");
4578
+ await fs13.ensureDir(reportDir);
4579
+ await fs13.writeFile(join11(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
3761
4580
  console.log("");
3762
4581
  hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
3763
4582
  } catch {
@@ -3807,7 +4626,7 @@ async function detectLegacyProject(cwd) {
3807
4626
  ".gitignore"
3808
4627
  ];
3809
4628
  for (const signal of signals) {
3810
- if (await fs12.pathExists(join10(cwd, signal))) return true;
4629
+ if (await fs13.pathExists(join11(cwd, signal))) return true;
3811
4630
  }
3812
4631
  return false;
3813
4632
  }
@@ -3824,17 +4643,17 @@ var init_init_cmd = __esm({
3824
4643
  });
3825
4644
 
3826
4645
  // src/stages/design/openspec.ts
3827
- import { join as join11 } from "path";
3828
- import fs13 from "fs-extra";
4646
+ import { join as join12 } from "path";
4647
+ import fs14 from "fs-extra";
3829
4648
  async function initOpenSpec(cwd, projectName) {
3830
- const docsDir = join11(cwd, "docs");
3831
- await fs13.ensureDir(docsDir);
3832
- const openspecPath = join11(docsDir, "openspec.md");
3833
- if (await fs13.pathExists(openspecPath)) {
4649
+ const docsDir = join12(cwd, "docs");
4650
+ await fs14.ensureDir(docsDir);
4651
+ const openspecPath = join12(docsDir, "openspec.md");
4652
+ if (await fs14.pathExists(openspecPath)) {
3834
4653
  info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
3835
4654
  return;
3836
4655
  }
3837
- await fs13.writeFile(openspecPath, [
4656
+ await fs14.writeFile(openspecPath, [
3838
4657
  `# OpenSpec \u2014 ${projectName}`,
3839
4658
  "",
3840
4659
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -3862,13 +4681,13 @@ async function initOpenSpec(cwd, projectName) {
3862
4681
  }
3863
4682
  async function validateOpenSpec(cwd) {
3864
4683
  const candidates = [
3865
- join11(cwd, "docs", "openspec.md"),
3866
- join11(cwd, "openspec.md"),
3867
- join11(cwd, ".pai", "openspec.md")
4684
+ join12(cwd, "docs", "openspec.md"),
4685
+ join12(cwd, "openspec.md"),
4686
+ join12(cwd, ".pai", "openspec.md")
3868
4687
  ];
3869
4688
  let specPath = null;
3870
4689
  for (const p of candidates) {
3871
- if (await fs13.pathExists(p)) {
4690
+ if (await fs14.pathExists(p)) {
3872
4691
  specPath = p;
3873
4692
  break;
3874
4693
  }
@@ -3882,7 +4701,7 @@ async function validateOpenSpec(cwd) {
3882
4701
  warnings: ["openspec.md \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai design init` \uC744 \uC2E4\uD589\uD558\uC138\uC694."]
3883
4702
  };
3884
4703
  }
3885
- const content = await fs13.readFile(specPath, "utf8");
4704
+ const content = await fs14.readFile(specPath, "utf8");
3886
4705
  const missing = [];
3887
4706
  let filled = 0;
3888
4707
  for (const section2 of REQUIRED_SECTIONS) {
@@ -3930,17 +4749,17 @@ var init_openspec = __esm({
3930
4749
  });
3931
4750
 
3932
4751
  // src/stages/design/omc.ts
3933
- import { join as join12 } from "path";
3934
- import fs14 from "fs-extra";
4752
+ import { join as join13 } from "path";
4753
+ import fs15 from "fs-extra";
3935
4754
  async function initOMC(cwd, projectName) {
3936
- const paiDir = join12(cwd, ".pai");
3937
- await fs14.ensureDir(paiDir);
3938
- const omcPath = join12(paiDir, "omc.md");
3939
- if (await fs14.pathExists(omcPath)) {
4755
+ const paiDir = join13(cwd, ".pai");
4756
+ await fs15.ensureDir(paiDir);
4757
+ const omcPath = join13(paiDir, "omc.md");
4758
+ if (await fs15.pathExists(omcPath)) {
3940
4759
  info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
3941
4760
  return;
3942
4761
  }
3943
- await fs14.writeFile(omcPath, [
4762
+ await fs15.writeFile(omcPath, [
3944
4763
  `# OMC \u2014 Object Model Context (${projectName})`,
3945
4764
  "",
3946
4765
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -4022,25 +4841,25 @@ var init_design_cmd = __esm({
4022
4841
  });
4023
4842
 
4024
4843
  // src/stages/validation/runner.ts
4025
- import { join as join13 } from "path";
4026
- import fs15 from "fs-extra";
4844
+ import { join as join14 } from "path";
4845
+ import fs16 from "fs-extra";
4027
4846
  async function runTests(cwd) {
4028
4847
  const start = Date.now();
4029
- const gstackPath = join13(cwd, ".pai", "gstack.json");
4848
+ const gstackPath = join14(cwd, ".pai", "gstack.json");
4030
4849
  let runner = "npm test";
4031
- if (await fs15.pathExists(gstackPath)) {
4850
+ if (await fs16.pathExists(gstackPath)) {
4032
4851
  try {
4033
- const config = await fs15.readJson(gstackPath);
4852
+ const config = await fs16.readJson(gstackPath);
4034
4853
  if (config.testRunner === "vitest") runner = "npx vitest run";
4035
4854
  else if (config.testRunner === "jest") runner = "npx jest";
4036
4855
  else if (config.testRunner === "mocha") runner = "npx mocha";
4037
4856
  } catch {
4038
4857
  }
4039
4858
  }
4040
- const pkgPath = join13(cwd, "package.json");
4041
- if (await fs15.pathExists(pkgPath)) {
4859
+ const pkgPath = join14(cwd, "package.json");
4860
+ if (await fs16.pathExists(pkgPath)) {
4042
4861
  try {
4043
- const pkg4 = await fs15.readJson(pkgPath);
4862
+ const pkg4 = await fs16.readJson(pkgPath);
4044
4863
  if (!pkg4.scripts?.test || pkg4.scripts.test.includes("no test specified")) {
4045
4864
  return {
4046
4865
  runner,
@@ -4082,16 +4901,16 @@ var init_runner = __esm({
4082
4901
  });
4083
4902
 
4084
4903
  // src/stages/validation/harness.ts
4085
- import { join as join14 } from "path";
4086
- import fs16 from "fs-extra";
4904
+ import { join as join15 } from "path";
4905
+ import fs17 from "fs-extra";
4087
4906
  async function runHarnessCheck(cwd) {
4088
- const harnessPath = join14(cwd, ".pai", "harness.json");
4089
- if (!await fs16.pathExists(harnessPath)) {
4907
+ const harnessPath = join15(cwd, ".pai", "harness.json");
4908
+ if (!await fs17.pathExists(harnessPath)) {
4090
4909
  return { enabled: false, specFile: null, rules: [], checks: [] };
4091
4910
  }
4092
4911
  let config;
4093
4912
  try {
4094
- config = await fs16.readJson(harnessPath);
4913
+ config = await fs17.readJson(harnessPath);
4095
4914
  } catch {
4096
4915
  return { enabled: false, specFile: null, rules: [], checks: [] };
4097
4916
  }
@@ -4099,8 +4918,8 @@ async function runHarnessCheck(cwd) {
4099
4918
  const rules = config.rules ?? [];
4100
4919
  const checks = [];
4101
4920
  if (rules.includes("spec-implementation-match")) {
4102
- const specExists = await fs16.pathExists(join14(cwd, specFile));
4103
- const srcExists = await fs16.pathExists(join14(cwd, "src"));
4921
+ const specExists = await fs17.pathExists(join15(cwd, specFile));
4922
+ const srcExists = await fs17.pathExists(join15(cwd, "src"));
4104
4923
  checks.push({
4105
4924
  rule: "spec-implementation-match",
4106
4925
  passed: specExists && srcExists,
@@ -4108,8 +4927,8 @@ async function runHarnessCheck(cwd) {
4108
4927
  });
4109
4928
  }
4110
4929
  if (rules.includes("api-contract-test")) {
4111
- const testDir = await fs16.pathExists(join14(cwd, "tests"));
4112
- const testDir2 = await fs16.pathExists(join14(cwd, "test"));
4930
+ const testDir = await fs17.pathExists(join15(cwd, "tests"));
4931
+ const testDir2 = await fs17.pathExists(join15(cwd, "test"));
4113
4932
  checks.push({
4114
4933
  rule: "api-contract-test",
4115
4934
  passed: testDir || testDir2,
@@ -4199,14 +5018,14 @@ var init_context = __esm({
4199
5018
  });
4200
5019
 
4201
5020
  // src/stages/design/index.ts
4202
- import { join as join15 } from "path";
4203
- import fs17 from "fs-extra";
5021
+ import { join as join16 } from "path";
5022
+ import fs18 from "fs-extra";
4204
5023
  async function autoInstallHarness(cwd) {
4205
- const harnessPath = join15(cwd, ".pai", "harness.json");
4206
- if (await fs17.pathExists(harnessPath)) return;
5024
+ const harnessPath = join16(cwd, ".pai", "harness.json");
5025
+ if (await fs18.pathExists(harnessPath)) return;
4207
5026
  await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
4208
- await fs17.ensureDir(join15(cwd, ".pai"));
4209
- await fs17.writeJson(harnessPath, {
5027
+ await fs18.ensureDir(join16(cwd, ".pai"));
5028
+ await fs18.writeJson(harnessPath, {
4210
5029
  version: "1.0",
4211
5030
  specFile: "docs/openspec.md",
4212
5031
  checkOn: ["pre-commit", "ci"],
@@ -4551,7 +5370,7 @@ __export(remove_cmd_exports, {
4551
5370
  removeCommand: () => removeCommand
4552
5371
  });
4553
5372
  import { basename as basename4, dirname } from "path";
4554
- import fs18 from "fs-extra";
5373
+ import fs19 from "fs-extra";
4555
5374
  async function removeCommand(cwd, options) {
4556
5375
  section("\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C");
4557
5376
  const config = await loadConfig(cwd);
@@ -4569,7 +5388,7 @@ async function removeCommand(cwd, options) {
4569
5388
  console.log(colors.err(` ${folderName}/ \uD3F4\uB354 \uC804\uCCB4\uAC00 \uC0AD\uC81C\uB429\uB2C8\uB2E4.`));
4570
5389
  hint("\uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4571
5390
  console.log("");
4572
- const items = await fs18.readdir(cwd);
5391
+ const items = await fs19.readdir(cwd);
4573
5392
  const fileCount = items.filter((i) => !i.startsWith(".")).length;
4574
5393
  const hiddenCount = items.filter((i) => i.startsWith(".")).length;
4575
5394
  info(`\uD30C\uC77C/\uD3F4\uB354 ${fileCount}\uAC1C, \uC228\uAE40 \uD56D\uBAA9 ${hiddenCount}\uAC1C`);
@@ -4588,7 +5407,7 @@ async function removeCommand(cwd, options) {
4588
5407
  }
4589
5408
  process.chdir(parentDir);
4590
5409
  try {
4591
- await fs18.remove(cwd);
5410
+ await fs19.remove(cwd);
4592
5411
  console.log("");
4593
5412
  success(`${folderName}/ \uD504\uB85C\uC81D\uD2B8\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
4594
5413
  try {
@@ -4741,8 +5560,8 @@ var savetoken_cmd_exports = {};
4741
5560
  __export(savetoken_cmd_exports, {
4742
5561
  savetokenCommand: () => savetokenCommand
4743
5562
  });
4744
- import { join as join16, relative } from "path";
4745
- import fs19 from "fs-extra";
5563
+ import { join as join17, relative } from "path";
5564
+ import fs20 from "fs-extra";
4746
5565
  import chalk6 from "chalk";
4747
5566
  async function savetokenCommand(cwd) {
4748
5567
  const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
@@ -4811,11 +5630,11 @@ async function savetokenCommand(cwd) {
4811
5630
  console.log(` \u2192 ${colors.dim("\uB8F0 \uAE30\uBC18 \uB85C\uC9C1, \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC73C\uB85C \uAC80\uD1A0 \uD6C4 \uB300\uCCB4")}`);
4812
5631
  console.log(` ${chalk6.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
4813
5632
  console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
4814
- const reportDir = join16(cwd, ".pai");
4815
- await fs19.ensureDir(reportDir);
5633
+ const reportDir = join17(cwd, ".pai");
5634
+ await fs20.ensureDir(reportDir);
4816
5635
  const report = buildReport(callSites, cwd);
4817
- const reportPath = join16(reportDir, "savetoken-report.md");
4818
- await fs19.writeFile(reportPath, report, "utf8");
5636
+ const reportPath = join17(reportDir, "savetoken-report.md");
5637
+ await fs20.writeFile(reportPath, report, "utf8");
4819
5638
  console.log("");
4820
5639
  success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
4821
5640
  console.log("");
@@ -4971,9 +5790,9 @@ var wakeup_cmd_exports = {};
4971
5790
  __export(wakeup_cmd_exports, {
4972
5791
  wakeupCommand: () => wakeupCommand
4973
5792
  });
4974
- import { join as join17 } from "path";
5793
+ import { join as join18 } from "path";
4975
5794
  import { homedir as homedir3, platform as osPlatform } from "os";
4976
- import fs20 from "fs-extra";
5795
+ import fs21 from "fs-extra";
4977
5796
  import chalk7 from "chalk";
4978
5797
  async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
4979
5798
  if (timeOrAction === "off") {
@@ -5020,9 +5839,9 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
5020
5839
  projectDir,
5021
5840
  launchMode
5022
5841
  };
5023
- await fs20.ensureDir(PAI_DIR2);
5024
- await fs20.writeJson(CONFIG_FILE2, config, { spaces: 2 });
5025
- await fs20.writeJson(MESSAGES_FILE, MESSAGES);
5842
+ await fs21.ensureDir(PAI_DIR2);
5843
+ await fs21.writeJson(CONFIG_FILE2, config, { spaces: 2 });
5844
+ await fs21.writeJson(MESSAGES_FILE, MESSAGES);
5026
5845
  await createWakeupScript(config);
5027
5846
  if (osPlatform() === "darwin") {
5028
5847
  await setupMacOS(config);
@@ -5052,8 +5871,8 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
5052
5871
  async function setupMacOS(config) {
5053
5872
  const { execa } = await import("execa");
5054
5873
  const [hour, minute] = config.time.split(":").map(Number);
5055
- const plistDir = join17(homedir3(), "Library", "LaunchAgents");
5056
- await fs20.ensureDir(plistDir);
5874
+ const plistDir = join18(homedir3(), "Library", "LaunchAgents");
5875
+ await fs21.ensureDir(plistDir);
5057
5876
  const weekdays = scheduleToWeekdays(config.schedule);
5058
5877
  let calendarEntries;
5059
5878
  if (weekdays.length === 7) {
@@ -5089,7 +5908,7 @@ ${calendarEntries}
5089
5908
  <string>${PAI_DIR2}/wakeup.log</string>
5090
5909
  </dict>
5091
5910
  </plist>`;
5092
- await fs20.writeFile(PLIST_PATH, plist);
5911
+ await fs21.writeFile(PLIST_PATH, plist);
5093
5912
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
5094
5913
  });
5095
5914
  await execa("launchctl", ["load", PLIST_PATH]);
@@ -5111,9 +5930,9 @@ ${calendarEntries}
5111
5930
  async function setupWindows(config) {
5112
5931
  const { execa } = await import("execa");
5113
5932
  const [hour, minute] = config.time.split(":").map(Number);
5114
- const psScriptDir = join17(homedir3(), ".pai");
5115
- await fs20.ensureDir(psScriptDir);
5116
- const psScriptPath = join17(psScriptDir, "wakeup.ps1");
5933
+ const psScriptDir = join18(homedir3(), ".pai");
5934
+ await fs21.ensureDir(psScriptDir);
5935
+ const psScriptPath = join18(psScriptDir, "wakeup.ps1");
5117
5936
  const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
5118
5937
  const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
5119
5938
  $paiDir = "$env:USERPROFILE\\.pai"
@@ -5142,7 +5961,7 @@ $notifier.Show([Windows.UI.Notifications.ToastNotification]::new($xml))
5142
5961
  # Open PowerShell with Claude Code
5143
5962
  Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Content '$todayFile'; Write-Host ''; Set-Location '${config.projectDir}'; ${claudeCmd}"
5144
5963
  `;
5145
- await fs20.writeFile(psScriptPath, psScript, "utf8");
5964
+ await fs21.writeFile(psScriptPath, psScript, "utf8");
5146
5965
  const daysMap = {
5147
5966
  "\uD3C9\uC77C": "MON,TUE,WED,THU,FRI",
5148
5967
  "\uB9E4\uC77C": "MON,TUE,WED,THU,FRI,SAT,SUN",
@@ -5190,7 +6009,7 @@ async function disableWakeup() {
5190
6009
  if (osPlatform() === "darwin") {
5191
6010
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
5192
6011
  });
5193
- await fs20.remove(PLIST_PATH).catch(() => {
6012
+ await fs21.remove(PLIST_PATH).catch(() => {
5194
6013
  });
5195
6014
  success("launchd \uC2A4\uCF00\uC904 \uC81C\uAC70");
5196
6015
  console.log("");
@@ -5211,14 +6030,14 @@ async function disableWakeup() {
5211
6030
  } else {
5212
6031
  await removeCronEntry();
5213
6032
  }
5214
- await fs20.remove(CONFIG_FILE2).catch(() => {
6033
+ await fs21.remove(CONFIG_FILE2).catch(() => {
5215
6034
  });
5216
6035
  console.log("");
5217
6036
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD574\uC81C \uC644\uB8CC");
5218
6037
  }
5219
6038
  async function showStatus() {
5220
- if (await fs20.pathExists(CONFIG_FILE2)) {
5221
- const config = await fs20.readJson(CONFIG_FILE2);
6039
+ if (await fs21.pathExists(CONFIG_FILE2)) {
6040
+ const config = await fs21.readJson(CONFIG_FILE2);
5222
6041
  console.log("");
5223
6042
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD65C\uC131\uD654");
5224
6043
  console.log(` \uC2DC\uAC04 ${chalk7.white(config.time)}`);
@@ -5226,7 +6045,7 @@ async function showStatus() {
5226
6045
  console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk7.white(config.projectDir)}`);
5227
6046
  console.log(` \uBAA8\uB4DC ${chalk7.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
5228
6047
  if (osPlatform() === "darwin") {
5229
- const plistExists = await fs20.pathExists(PLIST_PATH);
6048
+ const plistExists = await fs21.pathExists(PLIST_PATH);
5230
6049
  console.log(` launchd ${plistExists ? chalk7.green("\uD65C\uC131") : chalk7.red("\uBE44\uD65C\uC131")}`);
5231
6050
  }
5232
6051
  console.log("");
@@ -5379,7 +6198,7 @@ fi
5379
6198
 
5380
6199
  echo "[$(date)] PAI Wakeup completed" >> "$LOG_FILE"
5381
6200
  `;
5382
- await fs20.writeFile(SCRIPT_FILE, script, { mode: 493 });
6201
+ await fs21.writeFile(SCRIPT_FILE, script, { mode: 493 });
5383
6202
  }
5384
6203
  var PAI_DIR2, CONFIG_FILE2, MESSAGES_FILE, SCRIPT_FILE, PLIST_NAME, PLIST_PATH, CRON_MARKER, MESSAGES;
5385
6204
  var init_wakeup_cmd = __esm({
@@ -5387,12 +6206,12 @@ var init_wakeup_cmd = __esm({
5387
6206
  "use strict";
5388
6207
  init_ui();
5389
6208
  init_logger();
5390
- PAI_DIR2 = join17(homedir3(), ".pai");
5391
- CONFIG_FILE2 = join17(PAI_DIR2, "wakeup-config.json");
5392
- MESSAGES_FILE = join17(PAI_DIR2, "wakeup-messages.json");
5393
- SCRIPT_FILE = join17(PAI_DIR2, "wakeup.sh");
6209
+ PAI_DIR2 = join18(homedir3(), ".pai");
6210
+ CONFIG_FILE2 = join18(PAI_DIR2, "wakeup-config.json");
6211
+ MESSAGES_FILE = join18(PAI_DIR2, "wakeup-messages.json");
6212
+ SCRIPT_FILE = join18(PAI_DIR2, "wakeup.sh");
5394
6213
  PLIST_NAME = "com.pai.wakeup";
5395
- PLIST_PATH = join17(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
6214
+ PLIST_PATH = join18(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
5396
6215
  CRON_MARKER = "# PAI-WAKEUP";
5397
6216
  MESSAGES = [
5398
6217
  `Here's to the crazy ones. The misfits. The rebels. The troublemakers.