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 +1030 -211
- package/dist/bin/pai.js.map +1 -1
- package/dist/cli/index.js +1030 -211
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
925
|
-
import
|
|
1733
|
+
import { join as join4 } from "path";
|
|
1734
|
+
import fs5 from "fs-extra";
|
|
926
1735
|
async function provisionGitHub(ctx) {
|
|
927
|
-
const gitDir =
|
|
928
|
-
if (!await
|
|
929
|
-
await
|
|
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) =>
|
|
939
|
-
const handoffPath =
|
|
940
|
-
if (!await
|
|
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
|
|
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 =
|
|
972
|
-
if (!await
|
|
973
|
-
await
|
|
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 =
|
|
1018
|
-
await
|
|
1019
|
-
const contribSkillPath =
|
|
1020
|
-
if (!await
|
|
1021
|
-
await
|
|
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 =
|
|
1047
|
-
if (!await
|
|
1048
|
-
await
|
|
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 =
|
|
1094
|
-
if (!await
|
|
1095
|
-
await
|
|
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 =
|
|
1104
|
-
await
|
|
1105
|
-
const configToml =
|
|
1106
|
-
if (!await
|
|
1107
|
-
await
|
|
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
|
|
1114
|
-
const openspecPath =
|
|
1115
|
-
if (!await
|
|
1116
|
-
await
|
|
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
|
|
1144
|
-
const omcPath =
|
|
1145
|
-
if (!await
|
|
1146
|
-
await
|
|
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
|
|
1167
|
-
await
|
|
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
|
|
1176
|
-
const robocoPath =
|
|
1177
|
-
if (await
|
|
1178
|
-
await
|
|
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
|
|
1187
|
-
await
|
|
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(
|
|
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
|
|
1240
|
-
import
|
|
2053
|
+
import { join as join5 } from "path";
|
|
2054
|
+
import fs6 from "fs-extra";
|
|
1241
2055
|
async function writeCommandFiles(cmdDir, entries, options) {
|
|
1242
|
-
await
|
|
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 =
|
|
2061
|
+
const filePath = join5(cmdDir, filename);
|
|
1248
2062
|
try {
|
|
1249
|
-
if (options.skipIfExists && await
|
|
2063
|
+
if (options.skipIfExists && await fs6.pathExists(filePath)) {
|
|
1250
2064
|
skipped.push(filename);
|
|
1251
2065
|
continue;
|
|
1252
2066
|
}
|
|
1253
|
-
await
|
|
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 =
|
|
1264
|
-
await
|
|
1265
|
-
const skillPath =
|
|
1266
|
-
if (!await
|
|
1267
|
-
await
|
|
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 =
|
|
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 =
|
|
1279
|
-
await
|
|
2092
|
+
const skillDir = join5(cwd, ".claude", "skills", "pai");
|
|
2093
|
+
await fs6.ensureDir(skillDir);
|
|
1280
2094
|
try {
|
|
1281
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
1913
|
-
return
|
|
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
|
|
1920
|
-
await
|
|
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
|
|
2761
|
+
import { join as join6 } from "path";
|
|
1948
2762
|
import { homedir } from "os";
|
|
1949
|
-
import
|
|
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 =
|
|
1969
|
-
const hasGlobalConfig = await
|
|
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
|
|
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
|
|
3005
|
+
if (await fs9.pathExists(paiConfigPath)) {
|
|
2190
3006
|
result.hasPaiConfig = true;
|
|
2191
3007
|
try {
|
|
2192
|
-
const config = await
|
|
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
|
|
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
|
|
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
|
|
2342
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3706
|
+
import { join as join8 } from "path";
|
|
2891
3707
|
import { homedir as homedir2 } from "os";
|
|
2892
|
-
import
|
|
3708
|
+
import fs11 from "fs-extra";
|
|
2893
3709
|
async function requestCdAfter(targetDir) {
|
|
2894
|
-
await
|
|
2895
|
-
await
|
|
3710
|
+
await fs11.ensureDir(PAI_DIR);
|
|
3711
|
+
await fs11.writeFile(CD_FILE, targetDir);
|
|
2896
3712
|
}
|
|
2897
3713
|
async function installShellHelper() {
|
|
2898
|
-
await
|
|
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
|
|
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
|
|
2909
|
-
const content = await
|
|
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
|
|
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
|
|
3735
|
+
await fs11.writeFile(rcFile, `${sourceLine}
|
|
2920
3736
|
`);
|
|
2921
3737
|
return false;
|
|
2922
3738
|
}
|
|
2923
3739
|
async function installPowerShellHelper() {
|
|
2924
|
-
await
|
|
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
|
|
2928
|
-
if (await
|
|
2929
|
-
const content = await
|
|
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
|
|
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
|
|
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 =
|
|
2949
|
-
CD_FILE =
|
|
2950
|
-
HELPER_FILE_SH =
|
|
2951
|
-
HELPER_FILE_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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
3080
|
-
import
|
|
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 =
|
|
3101
|
-
const reportPath =
|
|
3102
|
-
await
|
|
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
|
|
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
|
|
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
|
|
3239
|
-
import
|
|
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 =
|
|
3298
|
-
await
|
|
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
|
|
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 =
|
|
3347
|
-
if (await
|
|
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
|
|
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
|
|
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 =
|
|
3473
|
-
await
|
|
4291
|
+
const reportDir = join11(projectDir, "docs", "p-reports");
|
|
4292
|
+
await fs13.ensureDir(reportDir);
|
|
3474
4293
|
const detailedReport = buildDetailedReport3(evalResult, projectName);
|
|
3475
|
-
await
|
|
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
|
|
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
|
|
4343
|
+
const rcContent = await fs13.readFile(shellRc, "utf8").catch(() => "");
|
|
3525
4344
|
if (!rcContent.includes("claude-yolo")) {
|
|
3526
|
-
await
|
|
3527
|
-
await
|
|
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 =
|
|
3759
|
-
await
|
|
3760
|
-
await
|
|
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
|
|
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
|
|
3828
|
-
import
|
|
4646
|
+
import { join as join12 } from "path";
|
|
4647
|
+
import fs14 from "fs-extra";
|
|
3829
4648
|
async function initOpenSpec(cwd, projectName) {
|
|
3830
|
-
const docsDir =
|
|
3831
|
-
await
|
|
3832
|
-
const openspecPath =
|
|
3833
|
-
if (await
|
|
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
|
|
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
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
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
|
|
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
|
|
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
|
|
3934
|
-
import
|
|
4752
|
+
import { join as join13 } from "path";
|
|
4753
|
+
import fs15 from "fs-extra";
|
|
3935
4754
|
async function initOMC(cwd, projectName) {
|
|
3936
|
-
const paiDir =
|
|
3937
|
-
await
|
|
3938
|
-
const omcPath =
|
|
3939
|
-
if (await
|
|
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
|
|
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
|
|
4026
|
-
import
|
|
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 =
|
|
4848
|
+
const gstackPath = join14(cwd, ".pai", "gstack.json");
|
|
4030
4849
|
let runner = "npm test";
|
|
4031
|
-
if (await
|
|
4850
|
+
if (await fs16.pathExists(gstackPath)) {
|
|
4032
4851
|
try {
|
|
4033
|
-
const config = await
|
|
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 =
|
|
4041
|
-
if (await
|
|
4859
|
+
const pkgPath = join14(cwd, "package.json");
|
|
4860
|
+
if (await fs16.pathExists(pkgPath)) {
|
|
4042
4861
|
try {
|
|
4043
|
-
const pkg4 = await
|
|
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
|
|
4086
|
-
import
|
|
4904
|
+
import { join as join15 } from "path";
|
|
4905
|
+
import fs17 from "fs-extra";
|
|
4087
4906
|
async function runHarnessCheck(cwd) {
|
|
4088
|
-
const harnessPath =
|
|
4089
|
-
if (!await
|
|
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
|
|
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
|
|
4103
|
-
const srcExists = await
|
|
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
|
|
4112
|
-
const testDir2 = await
|
|
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
|
|
4203
|
-
import
|
|
5021
|
+
import { join as join16 } from "path";
|
|
5022
|
+
import fs18 from "fs-extra";
|
|
4204
5023
|
async function autoInstallHarness(cwd) {
|
|
4205
|
-
const harnessPath =
|
|
4206
|
-
if (await
|
|
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
|
|
4209
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4745
|
-
import
|
|
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 =
|
|
4815
|
-
await
|
|
5633
|
+
const reportDir = join17(cwd, ".pai");
|
|
5634
|
+
await fs20.ensureDir(reportDir);
|
|
4816
5635
|
const report = buildReport(callSites, cwd);
|
|
4817
|
-
const reportPath =
|
|
4818
|
-
await
|
|
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
|
|
5793
|
+
import { join as join18 } from "path";
|
|
4975
5794
|
import { homedir as homedir3, platform as osPlatform } from "os";
|
|
4976
|
-
import
|
|
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
|
|
5024
|
-
await
|
|
5025
|
-
await
|
|
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 =
|
|
5056
|
-
await
|
|
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
|
|
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 =
|
|
5115
|
-
await
|
|
5116
|
-
const psScriptPath =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5221
|
-
const config = await
|
|
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
|
|
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
|
|
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 =
|
|
5391
|
-
CONFIG_FILE2 =
|
|
5392
|
-
MESSAGES_FILE =
|
|
5393
|
-
SCRIPT_FILE =
|
|
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 =
|
|
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.
|