@byh3071/vhk 0.5.3 โ 0.7.0
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/README.md +37 -4
- package/dist/chunk-NJDRNI3S.js +316 -0
- package/dist/index.js +482 -43
- package/dist/mcp/index.js +10 -0
- package/package.json +65 -62
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: vhk-readme
|
|
3
|
-
date: 2026-05-
|
|
4
|
-
tags: [vhk, cli, readme, v0.
|
|
3
|
+
date: 2026-05-24
|
|
4
|
+
tags: [vhk, cli, readme, v0.6.0]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ๐ง VHK โ Vibe Harness Kit
|
|
8
8
|
|
|
9
|
-
> AI ์ฝ๋ฉ ์์ด์ ํธ๋ฅผ ๋ถ๋ฆฌ๋ ์ฌ๋์ ์ํ **ํ๊ตญ์ด ํ์ฌ์ดํด CLI** (v0.
|
|
9
|
+
> AI ์ฝ๋ฉ ์์ด์ ํธ๋ฅผ ๋ถ๋ฆฌ๋ ์ฌ๋์ ์ํ **ํ๊ตญ์ด ํ์ฌ์ดํด CLI** (v0.6.0)
|
|
10
10
|
>
|
|
11
11
|
> ๐ฝ๏ธ **VHK๋ VHK๋ก ๋ถํธ์คํธ๋ฉ๋จ** โ ์ด ๋ ํฌ์ `docs/`, `CLAUDE.md`, `.cursorrules`๋ `vhk init`์ด ๋ง๋ค์์ต๋๋ค.
|
|
12
12
|
|
|
@@ -83,12 +83,18 @@ vhk ๊ธฐํ ๋๋ฌ๊ณ ๋ฐ๋ก ์์
|
|
|
83
83
|
| `vhk sync` | `๊ท์น`, `๋ง์ถ๊ธฐ` | RULES.md โ `.cursorrules` + CLAUDE.md |
|
|
84
84
|
| `vhk check` | `์ ๊ฒ`, `๋ฆฐํธ` | RULES.md ๊ท์น ์๋ฐ ๊ฒ์ฌ |
|
|
85
85
|
| `vhk secure` | `๋ณด์` | ์ํฌ๋ฆฟยทํค ์ ์ถ ์ค์บ (`scan` / `์ค์บ` ๋์ผ). **CRITICAL/HIGH ๋ฐ๊ฒฌ ์ exit code 1** (CI์ฉ) |
|
|
86
|
-
| `vhk ship` |
|
|
86
|
+
| `vhk ship` | `์ถํ` | ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์คํธ + ํ๊ณ + ๋น๋ ๋ก๊ทธ |
|
|
87
87
|
| `vhk doctor` | `ํ๊ฒฝ`, `์ง๋จ` | Node / npm / pnpm / Git ํ๊ฒฝ ์ ๊ฒ |
|
|
88
88
|
| `vhk save` | `์ ์ฅ`, `์ปค๋ฐ` | git add ยท commit ยท push ํ ๋ฒ์ |
|
|
89
89
|
| `vhk undo` | `๋๋๋ฆฌ๊ธฐ`, `์ทจ์` | ์ต๊ทผ ์ปค๋ฐ soft reset (๋ณ๊ฒฝ์ staged ์ ์ง) |
|
|
90
90
|
| `vhk diff` | `๋ณ๊ฒฝ`, `์ฐจ์ด` | staged / unstaged / ์ ํ์ผ ์์ฝ (์ค ์ ํฉ๊ณ๋ trackedยทHEAD ๊ธฐ์ค) |
|
|
91
91
|
| `vhk status` | `์ํ`, `ํํฉ` | ๋ธ๋์นยท๋ณ๊ฒฝยท์ปค๋ฐยท์๊ฒฉยท๋ฒ์ ๋์๋ณด๋ |
|
|
92
|
+
| `vhk mcp` | โ | MCP ์๋ฒ ์์ (Cursor ๋ฑ MCP ํด๋ผ์ด์ธํธ์ฉ, stdio) |
|
|
93
|
+
| `vhk mcp-init` | `mcp์ค์ ` | Cursor `.cursor/mcp.json` ์๋ ์์ฑ |
|
|
94
|
+
| `vhk deploy` | `๋ฐฐํฌ` | ํ๋ก๋์
๋ฐฐํฌ (Vercel / Netlify / Cloudflare ์๋ ๊ฐ์ง) |
|
|
95
|
+
| `vhk env` | `ํ๊ฒฝ๋ณ์` | `.env` โ `.env.example` ๋๊ธฐํ + `.gitignore`์ `.env` ์๋ ์ถ๊ฐ |
|
|
96
|
+
| `vhk env-check` | `ํ๊ฒฝ๋ณ์์ ๊ฒ` | `.env.example` ๊ธฐ์ค ๋๋ฝ ํ๊ฒฝ๋ณ์ ๊ฒ์ฌ |
|
|
97
|
+
| `vhk publish` | `์ถ์` | npm ๋ฐฐํฌ ์๋ํ (๋ฒ์ ๋ฒํ โ ๋น๋ โ ํ
์คํธ โ publish โ git tag) |
|
|
92
98
|
|
|
93
99
|
### init ์ต์
|
|
94
100
|
|
|
@@ -105,6 +111,33 @@ vhk ๊ธฐํ ๋๋ฌ๊ณ ๋ฐ๋ก ์์
|
|
|
105
111
|
|------|------|
|
|
106
112
|
| `--since YYYY-MM-DD` | ๋ถ์ ์์์ผ (๊ธฐ๋ณธ: ์ค๋) |
|
|
107
113
|
|
|
114
|
+
## Cursor์ MCP๋ก ์ฐ๋ํ๊ธฐ
|
|
115
|
+
|
|
116
|
+
`v0.6.0`๋ถํฐ vhk๋ [Model Context Protocol](https://modelcontextprotocol.io) ์๋ฒ๋ฅผ ๋ด์ฅํฉ๋๋ค. Cursor ์ฑํ
์์ ์์ฐ์ด๋ก vhk ๋๊ตฌ๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค.
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
vhk mcp-init # .cursor/mcp.json ์๋ ์์ฑ
|
|
120
|
+
# โ Cursor ์ฌ์์ ํ ์ฑํ
์์ ์์ฐ์ด๋ก vhk ๋๊ตฌ ํธ์ถ ๊ฐ๋ฅ
|
|
121
|
+
# ์: "์ํ ์๋ ค์ค" โ Cursor๊ฐ vhk status ๋๊ตฌ ํธ์ถ
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
๋
ธ์ถ๋๋ MCP ๋๊ตฌ 8๊ฐ: `save`, `undo`, `status`, `diff`, `ship`, `doctor`, `check`, `recap`.
|
|
125
|
+
|
|
126
|
+
MCP ์๋ฒ๋ฅผ ์๋์ผ๋ก ๋์ฐ๋ ค๋ฉด:
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
vhk mcp # stdio ์๋ฒ ์์ (Cursor๊ฐ ์๋์ผ๋ก ํธ์ถ)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## v0.6.0 ํ์ด๋ผ์ดํธ
|
|
133
|
+
|
|
134
|
+
| ๊ธฐ๋ฅ | ์ค๋ช
|
|
|
135
|
+
|------|------|
|
|
136
|
+
| **MCP ์๋ฒ** | `vhk mcp` โ 8๊ฐ ๋๊ตฌ(save/undo/status/diff/ship/doctor/check/recap)๋ฅผ stdio๋ก ๋
ธ์ถ. Cursor ๋ฑ MCP ํด๋ผ์ด์ธํธ์์ ์์ฐ์ด๋ก ํธ์ถ |
|
|
137
|
+
| **mcp-init** | `vhk mcp-init` โ Cursor `.cursor/mcp.json` ์๋ ์์ฑ. ์ฌ์์ ํ ๋ฒ์ผ๋ก ์ฐ๋ ์๋ฃ |
|
|
138
|
+
| **์์ฐ์ด ๋ผ์ฐํ
ํ์ฅ** | `vhk mcp์ค์ ` โ `vhk mcp-init` ๋ณ์นญ |
|
|
139
|
+
| **๋ณด์** | MCP save ๋๊ตฌ์ shell injection ์ฐจ๋จ โ ๋ชจ๋ git ํธ์ถ์ `execFileSync` ์ฌ์ฉ |
|
|
140
|
+
|
|
108
141
|
## v0.5.3 ํ์ด๋ผ์ดํธ
|
|
109
142
|
|
|
110
143
|
| ๊ธฐ๋ฅ | ์ค๋ช
|
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
// src/mcp/server.ts
|
|
29
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
30
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { existsSync, readFileSync } from "fs";
|
|
33
|
+
|
|
34
|
+
// src/lib/exec.ts
|
|
35
|
+
import { execFileSync } from "child_process";
|
|
36
|
+
var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
37
|
+
function platformCmd(cmd) {
|
|
38
|
+
if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
|
|
39
|
+
return `${cmd}.cmd`;
|
|
40
|
+
}
|
|
41
|
+
return cmd;
|
|
42
|
+
}
|
|
43
|
+
function safeExecFile(cmd, args) {
|
|
44
|
+
try {
|
|
45
|
+
const out = execFileSync(platformCmd(cmd), args, {
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
48
|
+
}).toString();
|
|
49
|
+
return { ok: true, out: out.trim() };
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
52
|
+
return { ok: false, err: msg };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function safeExecFileStream(cmd, args) {
|
|
56
|
+
try {
|
|
57
|
+
execFileSync(platformCmd(cmd), args, {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
stdio: "inherit"
|
|
60
|
+
});
|
|
61
|
+
return { ok: true };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
return { ok: false, err: msg };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/mcp/server.ts
|
|
69
|
+
var SERVER_VERSION = "0.7.0";
|
|
70
|
+
function isGitRepo() {
|
|
71
|
+
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
72
|
+
}
|
|
73
|
+
function createVhkMcpServer() {
|
|
74
|
+
const server = new McpServer({
|
|
75
|
+
name: "vhk",
|
|
76
|
+
version: SERVER_VERSION
|
|
77
|
+
});
|
|
78
|
+
server.registerTool(
|
|
79
|
+
"save",
|
|
80
|
+
{
|
|
81
|
+
description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
async ({ message }) => {
|
|
87
|
+
if (!isGitRepo()) {
|
|
88
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
89
|
+
}
|
|
90
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
91
|
+
if (!status.ok) {
|
|
92
|
+
return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
|
|
93
|
+
}
|
|
94
|
+
if (!status.out) {
|
|
95
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
96
|
+
}
|
|
97
|
+
const files = status.out.split("\n");
|
|
98
|
+
const now = /* @__PURE__ */ new Date();
|
|
99
|
+
const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
100
|
+
const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
|
|
101
|
+
const add = safeExecFile("git", ["add", "."]);
|
|
102
|
+
if (!add.ok) {
|
|
103
|
+
return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
|
|
104
|
+
}
|
|
105
|
+
const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
|
|
106
|
+
if (!commit.ok) {
|
|
107
|
+
return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
|
|
108
|
+
}
|
|
109
|
+
const push = safeExecFile("git", ["push"]);
|
|
110
|
+
const pushResult = push.ok ? "+ \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC" : "(\uC6D0\uACA9 \uC800\uC7A5\uC18C \uC5C6\uAC70\uB098 push \uC2E4\uD328 \u2192 \uC2A4\uD0B5)";
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
|
|
116
|
+
\uCEE4\uBC0B: ${commitMsg}`
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
|
|
123
|
+
if (!isGitRepo()) {
|
|
124
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
125
|
+
}
|
|
126
|
+
const last = safeExecFile("git", ["log", "--oneline", "-1"]);
|
|
127
|
+
if (!last.ok || !last.out) {
|
|
128
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
129
|
+
}
|
|
130
|
+
const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
|
|
131
|
+
if (!reset.ok) {
|
|
132
|
+
return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
|
|
139
|
+
\uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
|
|
140
|
+
\u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
server.registerTool("status", { description: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC (\uBE0C\uB79C\uCE58/\uBCC0\uACBD\uC0AC\uD56D/\uCD5C\uADFC \uCEE4\uBC0B)" }, async () => {
|
|
146
|
+
const lines = [];
|
|
147
|
+
if (existsSync("package.json")) {
|
|
148
|
+
try {
|
|
149
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
150
|
+
lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!isGitRepo()) {
|
|
155
|
+
lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
156
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
157
|
+
}
|
|
158
|
+
const branch = safeExecFile("git", ["branch", "--show-current"]);
|
|
159
|
+
if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
|
|
160
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
161
|
+
if (status.ok) {
|
|
162
|
+
if (!status.out) {
|
|
163
|
+
lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
|
|
164
|
+
} else {
|
|
165
|
+
const fileLines = status.out.split("\n");
|
|
166
|
+
const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
|
|
167
|
+
const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
|
|
168
|
+
const untracked = fileLines.filter((l) => l.startsWith("??")).length;
|
|
169
|
+
const parts = [];
|
|
170
|
+
if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
|
|
171
|
+
if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
|
|
172
|
+
if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
|
|
173
|
+
lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const log = safeExecFile("git", ["log", "--oneline", "-3"]);
|
|
177
|
+
if (log.ok && log.out) {
|
|
178
|
+
lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
|
|
179
|
+
log.out.split("\n").forEach((l) => lines.push(` ${l}`));
|
|
180
|
+
}
|
|
181
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
182
|
+
});
|
|
183
|
+
server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
|
|
184
|
+
if (!isGitRepo()) {
|
|
185
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
186
|
+
}
|
|
187
|
+
const unstaged = safeExecFile("git", ["diff", "--stat"]);
|
|
188
|
+
const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
|
|
189
|
+
const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
190
|
+
const unstagedOut = unstaged.ok ? unstaged.out : "";
|
|
191
|
+
const stagedOut = staged.ok ? staged.out : "";
|
|
192
|
+
const untrackedOut = untracked.ok ? untracked.out : "";
|
|
193
|
+
if (!unstagedOut && !stagedOut && !untrackedOut) {
|
|
194
|
+
return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
|
|
195
|
+
}
|
|
196
|
+
const lines = [];
|
|
197
|
+
if (stagedOut) {
|
|
198
|
+
lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
|
|
199
|
+
lines.push(stagedOut);
|
|
200
|
+
}
|
|
201
|
+
if (unstagedOut) {
|
|
202
|
+
lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
|
|
203
|
+
lines.push(unstagedOut);
|
|
204
|
+
}
|
|
205
|
+
if (untrackedOut) {
|
|
206
|
+
const files = untrackedOut.split("\n");
|
|
207
|
+
lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
|
|
208
|
+
files.forEach((f) => lines.push(` + ${f}`));
|
|
209
|
+
}
|
|
210
|
+
const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
|
|
211
|
+
if (numstat.ok && numstat.out) {
|
|
212
|
+
let totalAdd = 0;
|
|
213
|
+
let totalDel = 0;
|
|
214
|
+
let fileCount = 0;
|
|
215
|
+
numstat.out.split("\n").forEach((line) => {
|
|
216
|
+
const [add, del] = line.split(" ");
|
|
217
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
218
|
+
totalDel += parseInt(del, 10) || 0;
|
|
219
|
+
fileCount += 1;
|
|
220
|
+
});
|
|
221
|
+
lines.push(`
|
|
222
|
+
\u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
|
|
223
|
+
}
|
|
224
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
225
|
+
});
|
|
226
|
+
server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
|
|
227
|
+
const checks = [];
|
|
228
|
+
const build = safeExecFile("pnpm", ["build"]);
|
|
229
|
+
checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
|
|
230
|
+
const test = safeExecFile("pnpm", ["test", "--run"]);
|
|
231
|
+
checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
232
|
+
if (existsSync("package.json")) {
|
|
233
|
+
try {
|
|
234
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
235
|
+
checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (isGitRepo()) {
|
|
240
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
241
|
+
if (status.ok) {
|
|
242
|
+
if (status.out) {
|
|
243
|
+
checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
|
|
244
|
+
} else {
|
|
245
|
+
checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
|
|
250
|
+
});
|
|
251
|
+
server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
|
|
252
|
+
const checks = [];
|
|
253
|
+
const node = safeExecFile("node", ["--version"]);
|
|
254
|
+
checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
|
|
255
|
+
const git = safeExecFile("git", ["--version"]);
|
|
256
|
+
checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
|
|
257
|
+
const pnpm = safeExecFile("pnpm", ["--version"]);
|
|
258
|
+
checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
|
|
259
|
+
const npm = safeExecFile("npm", ["--version"]);
|
|
260
|
+
checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
|
|
261
|
+
const tsc = safeExecFile("npx", ["tsc", "--version"]);
|
|
262
|
+
checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
|
|
263
|
+
return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
|
|
264
|
+
});
|
|
265
|
+
server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
|
|
266
|
+
const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
|
|
267
|
+
const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
|
|
268
|
+
const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
|
|
269
|
+
required.forEach((f) => {
|
|
270
|
+
lines.push(` ${existsSync(f) ? "\u2705" : "\u274C"} ${f}`);
|
|
271
|
+
});
|
|
272
|
+
lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
|
|
273
|
+
recommended.forEach((f) => {
|
|
274
|
+
lines.push(` ${existsSync(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
|
|
275
|
+
});
|
|
276
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
277
|
+
});
|
|
278
|
+
server.registerTool(
|
|
279
|
+
"recap",
|
|
280
|
+
{
|
|
281
|
+
description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
async ({ count }) => {
|
|
287
|
+
if (!isGitRepo()) {
|
|
288
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
289
|
+
}
|
|
290
|
+
const n = count && count > 0 ? Math.floor(count) : 10;
|
|
291
|
+
const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
|
|
292
|
+
if (!log.ok) {
|
|
293
|
+
return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
|
|
294
|
+
}
|
|
295
|
+
if (!log.out) {
|
|
296
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
297
|
+
}
|
|
298
|
+
return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
|
|
299
|
+
${log.out}` }] };
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
return server;
|
|
303
|
+
}
|
|
304
|
+
async function startMcpServer() {
|
|
305
|
+
const server = createVhkMcpServer();
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export {
|
|
311
|
+
__commonJS,
|
|
312
|
+
__toESM,
|
|
313
|
+
safeExecFile,
|
|
314
|
+
safeExecFileStream,
|
|
315
|
+
startMcpServer
|
|
316
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
2
|
+
import {
|
|
3
|
+
__commonJS,
|
|
4
|
+
__toESM,
|
|
5
|
+
safeExecFile,
|
|
6
|
+
safeExecFileStream,
|
|
7
|
+
startMcpServer
|
|
8
|
+
} from "./chunk-NJDRNI3S.js";
|
|
27
9
|
|
|
28
10
|
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
29
11
|
var require_ignore = __commonJS({
|
|
@@ -485,7 +467,7 @@ var require_ignore = __commonJS({
|
|
|
485
467
|
|
|
486
468
|
// src/index.ts
|
|
487
469
|
import { Command, Help } from "commander";
|
|
488
|
-
import
|
|
470
|
+
import inquirer10 from "inquirer";
|
|
489
471
|
|
|
490
472
|
// src/lib/nlp-router.ts
|
|
491
473
|
function normalize(input) {
|
|
@@ -523,6 +505,12 @@ var RULES = [
|
|
|
523
505
|
confidence: "high",
|
|
524
506
|
test: (t2) => /ํ๋ก์ ํธ.*(๋ง๋ค|์์)|ํด๋.*๋ง๋ค|๋ง๋ค๊ณ \s*์ถ|ํ๋ค์ค|์ด๊ธฐํ/.test(t2) || /^์์$/.test(t2)
|
|
525
507
|
},
|
|
508
|
+
{
|
|
509
|
+
command: "mcp-init",
|
|
510
|
+
explanation: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815 (vhk mcp-init)",
|
|
511
|
+
confidence: "high",
|
|
512
|
+
test: (t2) => /mcp.*(์ค์ |์ฐ๋|์ด๊ธฐ|init)|์ปค์.*(์ฐ๋|์ค์ |mcp)|cursor.*mcp/.test(t2)
|
|
513
|
+
},
|
|
526
514
|
{
|
|
527
515
|
command: "secure",
|
|
528
516
|
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
|
|
@@ -585,9 +573,33 @@ var RULES = [
|
|
|
585
573
|
},
|
|
586
574
|
{
|
|
587
575
|
command: "ship",
|
|
588
|
-
explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk
|
|
576
|
+
explanation: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 (vhk \uCD9C\uD558)",
|
|
577
|
+
confidence: "high",
|
|
578
|
+
test: (t2) => /^์ถํ$|^ship$|๋น๋\s*์ |(๋ฐฐํฌ|์ถํ)\s*(์ฒดํฌ|์ค๋น|์ ๊ฒ)/.test(t2)
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
command: "deploy",
|
|
582
|
+
explanation: "\uD504\uB85C\uB355\uC158 \uBC30\uD3EC (vhk deploy)",
|
|
583
|
+
confidence: "high",
|
|
584
|
+
test: (t2) => /^๋ฐฐํฌ$|๋ฐฐํฌ\s*ํด|๋ฐฐํฌํ|๋ฐฐํฌํด์ค|^deploy$|๋ํ๋ก์ด|vercel|netlify|cloudflare|wrangler|ํ๋ก๋์
|์ฌ๋ ค์ค/.test(t2) && !/์ฒดํฌ|์ค๋น|์ ๊ฒ|์ถํ|ํ๊ณ |๋น๋\s*์ /.test(t2)
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
command: "env-check",
|
|
588
|
+
explanation: "\uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (vhk env-check)",
|
|
589
|
+
confidence: "high",
|
|
590
|
+
test: (t2) => /ํ๊ฒฝ๋ณ์\s*(์ ๊ฒ|ํ์ธ|๋๋ฝ)|env\s*(์ฒดํฌ|ํ์ธ|check)|ํค\s*(ํ์ธ|๋๋ฝ)/.test(t2)
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
command: "env",
|
|
594
|
+
explanation: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC (vhk env)",
|
|
595
|
+
confidence: "high",
|
|
596
|
+
test: (t2) => /ํ๊ฒฝ๋ณ์|\.env|env\s*example|env\s*๋๊ธฐํ|์ํฌ๋ฆฟ\s*์ ๋ฆฌ|ํค\s*์ค์ /.test(t2) && !/์ ๊ฒ|ํ์ธ|๋๋ฝ|์ฒดํฌ|check/.test(t2)
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
command: "publish",
|
|
600
|
+
explanation: "npm \uBC30\uD3EC (vhk publish)",
|
|
589
601
|
confidence: "high",
|
|
590
|
-
test: (t2) =>
|
|
602
|
+
test: (t2) => /^์ถ์$|์ถ์\s*ํด|^publish$|ํผ๋ธ๋ฆฌ์|npm\s*(๋ฐฐํฌ|์ถ์)|๋ฒ์ \s*์ฌ|^๋ฆด๋ฆฌ์ฆ$|^release$/.test(t2) && !/์ฒดํฌ|์ค๋น|ํ๊ณ /.test(t2)
|
|
591
603
|
}
|
|
592
604
|
];
|
|
593
605
|
function routeNaturalLanguage(input) {
|
|
@@ -632,8 +644,6 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
632
644
|
"scan",
|
|
633
645
|
"\uC2A4\uCE94",
|
|
634
646
|
"ship",
|
|
635
|
-
"\uBC30\uD3EC",
|
|
636
|
-
"\uB9B4\uB9AC\uC988",
|
|
637
647
|
"doctor",
|
|
638
648
|
"\uD658\uACBD",
|
|
639
649
|
"\uC9C4\uB2E8",
|
|
@@ -647,6 +657,18 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
647
657
|
"diff",
|
|
648
658
|
"\uBCC0\uACBD",
|
|
649
659
|
"\uCC28\uC774",
|
|
660
|
+
"mcp",
|
|
661
|
+
"mcp-init",
|
|
662
|
+
"mcp\uC124\uC815",
|
|
663
|
+
"deploy",
|
|
664
|
+
"\uBC30\uD3EC",
|
|
665
|
+
"env",
|
|
666
|
+
"\uD658\uACBD\uBCC0\uC218",
|
|
667
|
+
"env-check",
|
|
668
|
+
"\uD658\uACBD\uBCC0\uC218\uC810\uAC80",
|
|
669
|
+
"publish",
|
|
670
|
+
"\uCD9C\uC2DC",
|
|
671
|
+
"\uCD9C\uD558",
|
|
650
672
|
"help"
|
|
651
673
|
]);
|
|
652
674
|
function isOptionToken(token) {
|
|
@@ -670,8 +692,8 @@ function detectNaturalLanguageInput(argv) {
|
|
|
670
692
|
}
|
|
671
693
|
|
|
672
694
|
// src/lib/nlp-run.ts
|
|
673
|
-
import
|
|
674
|
-
import
|
|
695
|
+
import chalk20 from "chalk";
|
|
696
|
+
import inquirer9 from "inquirer";
|
|
675
697
|
|
|
676
698
|
// src/i18n/ko.ts
|
|
677
699
|
var ko = {
|
|
@@ -915,6 +937,34 @@ var ko = {
|
|
|
915
937
|
changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
|
|
916
938
|
changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
|
|
917
939
|
changelogMissing: "CHANGELOG.md\uAC00 \uC5C6\uC5B4\uC694. \uB9CC\uB4E4\uBA74 ship\uC774 \uC790\uB3D9\uC73C\uB85C [Unreleased] \u2192 \uBC84\uC804 \uC139\uC158\uC73C\uB85C \uC62E\uACA8\uC90D\uB2C8\uB2E4."
|
|
940
|
+
},
|
|
941
|
+
mcp: {
|
|
942
|
+
initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
|
|
943
|
+
serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
|
|
944
|
+
},
|
|
945
|
+
deploy: {
|
|
946
|
+
title: "\uBC30\uD3EC\uD558\uAE30",
|
|
947
|
+
selectPlatform: "\uC5B4\uB5A4 \uD50C\uB7AB\uD3FC\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?",
|
|
948
|
+
deploying: "\uBC30\uD3EC \uC911...",
|
|
949
|
+
success: "\uBC30\uD3EC \uC131\uACF5!",
|
|
950
|
+
failed: "\uBC30\uD3EC \uC2E4\uD328"
|
|
951
|
+
},
|
|
952
|
+
env: {
|
|
953
|
+
title: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
|
|
954
|
+
checkTitle: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80"
|
|
955
|
+
},
|
|
956
|
+
publish: {
|
|
957
|
+
title: "npm \uBC30\uD3EC",
|
|
958
|
+
selectBump: "\uBC84\uC804\uC744 \uC5B4\uB5BB\uAC8C \uC62C\uB9B4\uAE4C\uC694?",
|
|
959
|
+
building: "\uBE4C\uB4DC \uC911...",
|
|
960
|
+
buildSuccess: "\uBE4C\uB4DC \uC131\uACF5",
|
|
961
|
+
buildFailed: "\uBE4C\uB4DC \uC2E4\uD328",
|
|
962
|
+
testing: "\uD14C\uC2A4\uD2B8 \uC911...",
|
|
963
|
+
testSuccess: "\uD14C\uC2A4\uD2B8 \uD1B5\uACFC",
|
|
964
|
+
testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
|
|
965
|
+
publishing: "npm \uBC30\uD3EC \uC911...",
|
|
966
|
+
publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
|
|
967
|
+
publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
|
|
918
968
|
}
|
|
919
969
|
};
|
|
920
970
|
function lookup(path15) {
|
|
@@ -3553,6 +3603,360 @@ ${t("diff.summaryHeader")}`));
|
|
|
3553
3603
|
console.log("");
|
|
3554
3604
|
}
|
|
3555
3605
|
|
|
3606
|
+
// src/commands/mcp-init.ts
|
|
3607
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3608
|
+
import { join } from "path";
|
|
3609
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3610
|
+
import chalk16 from "chalk";
|
|
3611
|
+
function resolveVhkMcpPath() {
|
|
3612
|
+
try {
|
|
3613
|
+
const pkgPath = join(process.cwd(), "package.json");
|
|
3614
|
+
if (existsSync(pkgPath)) {
|
|
3615
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3616
|
+
if (pkg.name === "@byh3071/vhk") {
|
|
3617
|
+
return join(process.cwd(), "dist", "mcp", "index.js");
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
} catch {
|
|
3621
|
+
}
|
|
3622
|
+
try {
|
|
3623
|
+
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
3624
|
+
if (typeof url === "string") return fileURLToPath2(url);
|
|
3625
|
+
} catch {
|
|
3626
|
+
}
|
|
3627
|
+
return join(process.cwd(), "node_modules", "@byh3071", "vhk", "dist", "mcp", "index.js");
|
|
3628
|
+
}
|
|
3629
|
+
async function mcpInit() {
|
|
3630
|
+
console.log(chalk16.bold("\n\u{1F50C} " + t("mcp.initTitle")));
|
|
3631
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
3632
|
+
const cursorDir = join(process.cwd(), ".cursor");
|
|
3633
|
+
if (!existsSync(cursorDir)) {
|
|
3634
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
3635
|
+
}
|
|
3636
|
+
const configPath = join(cursorDir, "mcp.json");
|
|
3637
|
+
const vhkEntry = {
|
|
3638
|
+
command: "node",
|
|
3639
|
+
args: [resolveVhkMcpPath()]
|
|
3640
|
+
};
|
|
3641
|
+
let config;
|
|
3642
|
+
if (existsSync(configPath)) {
|
|
3643
|
+
try {
|
|
3644
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3645
|
+
config = {
|
|
3646
|
+
mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
|
|
3647
|
+
};
|
|
3648
|
+
} catch {
|
|
3649
|
+
console.log(chalk16.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
|
|
3650
|
+
config = { mcpServers: { vhk: vhkEntry } };
|
|
3651
|
+
}
|
|
3652
|
+
} else {
|
|
3653
|
+
config = { mcpServers: { vhk: vhkEntry } };
|
|
3654
|
+
}
|
|
3655
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3656
|
+
console.log(chalk16.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
|
|
3657
|
+
console.log(chalk16.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
|
|
3658
|
+
console.log(` ${configPath}`);
|
|
3659
|
+
console.log(chalk16.cyan("\n\u{1F504} \uB2E4\uC74C \uB2E8\uACC4:"));
|
|
3660
|
+
console.log(" 1. Cursor\uB97C \uC7AC\uC2DC\uC791\uD558\uC138\uC694");
|
|
3661
|
+
console.log(" 2. Cursor \uCC44\uD305\uC5D0\uC11C vhk \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
|
|
3662
|
+
console.log(chalk16.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
// src/commands/deploy.ts
|
|
3666
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3667
|
+
import chalk17 from "chalk";
|
|
3668
|
+
import inquirer7 from "inquirer";
|
|
3669
|
+
var PLATFORMS = {
|
|
3670
|
+
vercel: {
|
|
3671
|
+
name: "Vercel",
|
|
3672
|
+
detectFiles: ["vercel.json", ".vercel"],
|
|
3673
|
+
command: "vercel",
|
|
3674
|
+
commandArgs: ["--prod"],
|
|
3675
|
+
checkArgs: ["--version"],
|
|
3676
|
+
installHint: "npm i -g vercel"
|
|
3677
|
+
},
|
|
3678
|
+
netlify: {
|
|
3679
|
+
name: "Netlify",
|
|
3680
|
+
detectFiles: ["netlify.toml", ".netlify"],
|
|
3681
|
+
command: "netlify",
|
|
3682
|
+
commandArgs: ["deploy", "--prod"],
|
|
3683
|
+
checkArgs: ["--version"],
|
|
3684
|
+
installHint: "npm i -g netlify-cli"
|
|
3685
|
+
},
|
|
3686
|
+
cloudflare: {
|
|
3687
|
+
name: "Cloudflare Workers",
|
|
3688
|
+
detectFiles: ["wrangler.toml"],
|
|
3689
|
+
command: "wrangler",
|
|
3690
|
+
commandArgs: ["deploy"],
|
|
3691
|
+
checkArgs: ["--version"],
|
|
3692
|
+
installHint: "npm i -g wrangler"
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
function detectPlatform() {
|
|
3696
|
+
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
3697
|
+
for (const file of config.detectFiles) {
|
|
3698
|
+
if (existsSync2(file)) return key;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
return null;
|
|
3702
|
+
}
|
|
3703
|
+
function isCLIAvailable(cmd, checkArgs) {
|
|
3704
|
+
return safeExecFile(cmd, checkArgs).ok;
|
|
3705
|
+
}
|
|
3706
|
+
async function deploy() {
|
|
3707
|
+
console.log(chalk17.bold("\n\u{1F680} " + t("deploy.title")));
|
|
3708
|
+
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
3709
|
+
let platform = detectPlatform();
|
|
3710
|
+
if (platform) {
|
|
3711
|
+
console.log(chalk17.cyan(`
|
|
3712
|
+
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
3713
|
+
} else {
|
|
3714
|
+
const { selected } = await inquirer7.prompt([
|
|
3715
|
+
{
|
|
3716
|
+
type: "list",
|
|
3717
|
+
name: "selected",
|
|
3718
|
+
message: t("deploy.selectPlatform"),
|
|
3719
|
+
choices: [
|
|
3720
|
+
{ name: "\u25B2 Vercel", value: "vercel" },
|
|
3721
|
+
{ name: "\u25C6 Netlify", value: "netlify" },
|
|
3722
|
+
{ name: "\u2601 Cloudflare Workers", value: "cloudflare" }
|
|
3723
|
+
]
|
|
3724
|
+
}
|
|
3725
|
+
]);
|
|
3726
|
+
platform = selected;
|
|
3727
|
+
}
|
|
3728
|
+
const config = PLATFORMS[platform];
|
|
3729
|
+
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
3730
|
+
console.log(chalk17.red(`
|
|
3731
|
+
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
3732
|
+
console.log(chalk17.yellow(` \u2192 ${config.installHint}`));
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
const { confirm } = await inquirer7.prompt([
|
|
3736
|
+
{
|
|
3737
|
+
type: "confirm",
|
|
3738
|
+
name: "confirm",
|
|
3739
|
+
message: `${config.name}\uC5D0 \uD504\uB85C\uB355\uC158 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3740
|
+
default: true
|
|
3741
|
+
}
|
|
3742
|
+
]);
|
|
3743
|
+
if (!confirm) {
|
|
3744
|
+
console.log(chalk17.gray("\uCDE8\uC18C\uB428"));
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
console.log(chalk17.cyan(`
|
|
3748
|
+
${t("deploy.deploying")}
|
|
3749
|
+
`));
|
|
3750
|
+
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
3751
|
+
if (result.ok) {
|
|
3752
|
+
console.log(chalk17.green(`
|
|
3753
|
+
\u2705 ${t("deploy.success")}`));
|
|
3754
|
+
printNextStep({
|
|
3755
|
+
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
3756
|
+
command: "vhk status",
|
|
3757
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3758
|
+
});
|
|
3759
|
+
} else {
|
|
3760
|
+
console.log(chalk17.red(`
|
|
3761
|
+
\u274C ${t("deploy.failed")}`));
|
|
3762
|
+
console.log(chalk17.red(result.err));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
// src/commands/env.ts
|
|
3767
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
3768
|
+
import chalk18 from "chalk";
|
|
3769
|
+
function parseEnvKeys(content) {
|
|
3770
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
|
|
3771
|
+
}
|
|
3772
|
+
function ensureGitignore() {
|
|
3773
|
+
const gitignorePath = ".gitignore";
|
|
3774
|
+
if (existsSync3(gitignorePath)) {
|
|
3775
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
3776
|
+
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
3777
|
+
appendFileSync(gitignorePath, "\n.env\n");
|
|
3778
|
+
console.log(chalk18.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
|
|
3779
|
+
}
|
|
3780
|
+
} else {
|
|
3781
|
+
writeFileSync2(gitignorePath, ".env\nnode_modules/\ndist/\n");
|
|
3782
|
+
console.log(chalk18.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
async function env() {
|
|
3786
|
+
console.log(chalk18.bold("\n\u{1F510} " + t("env.title")));
|
|
3787
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3788
|
+
if (!existsSync3(".env")) {
|
|
3789
|
+
console.log(chalk18.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3790
|
+
console.log(chalk18.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
const envContent = readFileSync2(".env", "utf-8");
|
|
3794
|
+
const keys = parseEnvKeys(envContent);
|
|
3795
|
+
if (keys.length === 0) {
|
|
3796
|
+
console.log(chalk18.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3799
|
+
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
3800
|
+
writeFileSync2(".env.example", exampleContent, "utf-8");
|
|
3801
|
+
console.log(chalk18.green(`
|
|
3802
|
+
\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
|
|
3803
|
+
keys.forEach((k) => console.log(chalk18.gray(` ${k}`)));
|
|
3804
|
+
ensureGitignore();
|
|
3805
|
+
printNextStep({
|
|
3806
|
+
message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
|
|
3807
|
+
command: "vhk env-check",
|
|
3808
|
+
cursorHint: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80\uD574\uC918"
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
async function envCheck() {
|
|
3812
|
+
console.log(chalk18.bold("\n\u{1F50D} " + t("env.checkTitle")));
|
|
3813
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3814
|
+
if (!existsSync3(".env.example")) {
|
|
3815
|
+
console.log(chalk18.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
|
|
3819
|
+
const currentKeys = existsSync3(".env") ? parseEnvKeys(readFileSync2(".env", "utf-8")) : [];
|
|
3820
|
+
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
3821
|
+
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
3822
|
+
console.log(chalk18.cyan(`
|
|
3823
|
+
\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
|
|
3824
|
+
if (missing.length === 0) {
|
|
3825
|
+
console.log(chalk18.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
|
|
3826
|
+
} else {
|
|
3827
|
+
console.log(chalk18.red(`
|
|
3828
|
+
\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
|
|
3829
|
+
missing.forEach((k) => console.log(chalk18.red(` \u2022 ${k}`)));
|
|
3830
|
+
}
|
|
3831
|
+
if (extra.length > 0) {
|
|
3832
|
+
console.log(chalk18.yellow(`
|
|
3833
|
+
\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
|
|
3834
|
+
extra.forEach((k) => console.log(chalk18.yellow(` \u2022 ${k}`)));
|
|
3835
|
+
}
|
|
3836
|
+
ensureGitignore();
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/commands/publish.ts
|
|
3840
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3841
|
+
import chalk19 from "chalk";
|
|
3842
|
+
import inquirer8 from "inquirer";
|
|
3843
|
+
import ora2 from "ora";
|
|
3844
|
+
function bumpVersion(current, type) {
|
|
3845
|
+
const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
3846
|
+
switch (type) {
|
|
3847
|
+
case "major":
|
|
3848
|
+
return `${major + 1}.0.0`;
|
|
3849
|
+
case "minor":
|
|
3850
|
+
return `${major}.${minor + 1}.0`;
|
|
3851
|
+
case "patch":
|
|
3852
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
async function publish() {
|
|
3856
|
+
console.log(chalk19.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
3857
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
3858
|
+
if (!existsSync4("package.json")) {
|
|
3859
|
+
console.log(chalk19.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
let pkg;
|
|
3863
|
+
try {
|
|
3864
|
+
pkg = JSON.parse(readFileSync3("package.json", "utf-8"));
|
|
3865
|
+
} catch {
|
|
3866
|
+
console.log(chalk19.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
const currentVersion = pkg.version || "0.0.0";
|
|
3870
|
+
console.log(chalk19.cyan(`
|
|
3871
|
+
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
3872
|
+
const { bumpType } = await inquirer8.prompt([
|
|
3873
|
+
{
|
|
3874
|
+
type: "list",
|
|
3875
|
+
name: "bumpType",
|
|
3876
|
+
message: t("publish.selectBump"),
|
|
3877
|
+
choices: [
|
|
3878
|
+
{ name: `\u{1F527} patch (${bumpVersion(currentVersion, "patch")}) \u2014 \uBC84\uADF8 \uC218\uC815`, value: "patch" },
|
|
3879
|
+
{ name: `\u2728 minor (${bumpVersion(currentVersion, "minor")}) \u2014 \uC0C8 \uAE30\uB2A5`, value: "minor" },
|
|
3880
|
+
{ name: `\u{1F4A5} major (${bumpVersion(currentVersion, "major")}) \u2014 \uD638\uD658\uC131 \uBCC0\uACBD`, value: "major" }
|
|
3881
|
+
]
|
|
3882
|
+
}
|
|
3883
|
+
]);
|
|
3884
|
+
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
3885
|
+
console.log(chalk19.cyan(`
|
|
3886
|
+
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
3887
|
+
pkg.version = newVersion;
|
|
3888
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3889
|
+
console.log(chalk19.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
|
|
3890
|
+
const buildSpinner = ora2(t("publish.building")).start();
|
|
3891
|
+
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
3892
|
+
if (!buildResult.ok) {
|
|
3893
|
+
buildSpinner.fail(t("publish.buildFailed"));
|
|
3894
|
+
console.log(chalk19.red(buildResult.err.slice(0, 500)));
|
|
3895
|
+
pkg.version = currentVersion;
|
|
3896
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
buildSpinner.succeed(t("publish.buildSuccess"));
|
|
3900
|
+
const testSpinner = ora2(t("publish.testing")).start();
|
|
3901
|
+
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
3902
|
+
if (!testResult.ok) {
|
|
3903
|
+
testSpinner.fail(t("publish.testFailed"));
|
|
3904
|
+
console.log(chalk19.red(testResult.err.slice(0, 500)));
|
|
3905
|
+
pkg.version = currentVersion;
|
|
3906
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
testSpinner.succeed(t("publish.testSuccess"));
|
|
3910
|
+
const { confirm } = await inquirer8.prompt([
|
|
3911
|
+
{
|
|
3912
|
+
type: "confirm",
|
|
3913
|
+
name: "confirm",
|
|
3914
|
+
message: `v${newVersion}\uC744 npm\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3915
|
+
default: true
|
|
3916
|
+
}
|
|
3917
|
+
]);
|
|
3918
|
+
if (!confirm) {
|
|
3919
|
+
pkg.version = currentVersion;
|
|
3920
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3921
|
+
console.log(chalk19.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
const pubSpinner = ora2(t("publish.publishing")).start();
|
|
3925
|
+
const pubResult = safeExecFile("npm", ["publish", "--access", "public"]);
|
|
3926
|
+
if (!pubResult.ok) {
|
|
3927
|
+
pubSpinner.fail(t("publish.publishFailed"));
|
|
3928
|
+
console.log(chalk19.red(pubResult.err.slice(0, 500)));
|
|
3929
|
+
pkg.version = currentVersion;
|
|
3930
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3931
|
+
console.log(chalk19.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
pubSpinner.succeed(t("publish.publishSuccess"));
|
|
3935
|
+
const addResult = safeExecFile("git", ["add", "package.json"]);
|
|
3936
|
+
if (addResult.ok) {
|
|
3937
|
+
safeExecFile("git", ["commit", "-m", `chore: release v${newVersion}`]);
|
|
3938
|
+
const tagResult = safeExecFile("git", ["tag", `v${newVersion}`]);
|
|
3939
|
+
if (tagResult.ok) {
|
|
3940
|
+
const pushResult = safeExecFile("git", ["push"]);
|
|
3941
|
+
const pushTagsResult = safeExecFile("git", ["push", "--tags"]);
|
|
3942
|
+
if (pushResult.ok && pushTagsResult.ok) {
|
|
3943
|
+
console.log(chalk19.green(`
|
|
3944
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
3945
|
+
} else {
|
|
3946
|
+
console.log(chalk19.yellow(`
|
|
3947
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
console.log(chalk19.green.bold(`
|
|
3952
|
+
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
3953
|
+
printNextStep({
|
|
3954
|
+
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
3955
|
+
command: "vhk status",
|
|
3956
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3957
|
+
});
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3556
3960
|
// src/lib/nlp-run.ts
|
|
3557
3961
|
async function dispatchNlpRoute(route, input) {
|
|
3558
3962
|
switch (route.command) {
|
|
@@ -3583,28 +3987,38 @@ async function dispatchNlpRoute(route, input) {
|
|
|
3583
3987
|
return status();
|
|
3584
3988
|
case "diff":
|
|
3585
3989
|
return diff();
|
|
3990
|
+
case "mcp-init":
|
|
3991
|
+
return mcpInit();
|
|
3992
|
+
case "deploy":
|
|
3993
|
+
return deploy();
|
|
3994
|
+
case "env":
|
|
3995
|
+
return env();
|
|
3996
|
+
case "env-check":
|
|
3997
|
+
return envCheck();
|
|
3998
|
+
case "publish":
|
|
3999
|
+
return publish();
|
|
3586
4000
|
}
|
|
3587
4001
|
}
|
|
3588
4002
|
async function runNaturalLanguageRoute(input) {
|
|
3589
4003
|
const route = routeNaturalLanguage(input);
|
|
3590
4004
|
if (!route) {
|
|
3591
|
-
console.log(
|
|
4005
|
+
console.log(chalk20.yellow(`
|
|
3592
4006
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3593
4007
|
`));
|
|
3594
4008
|
return;
|
|
3595
4009
|
}
|
|
3596
4010
|
console.log("");
|
|
3597
|
-
console.log(
|
|
3598
|
-
console.log(
|
|
4011
|
+
console.log(chalk20.cyan(` \u{1F4AC} "${input}"`));
|
|
4012
|
+
console.log(chalk20.cyan(` \u2192 ${route.explanation}`));
|
|
3599
4013
|
if (route.confidence === "low") {
|
|
3600
|
-
const { confirm } = await
|
|
4014
|
+
const { confirm } = await inquirer9.prompt([{
|
|
3601
4015
|
type: "confirm",
|
|
3602
4016
|
name: "confirm",
|
|
3603
4017
|
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
3604
4018
|
default: true
|
|
3605
4019
|
}]);
|
|
3606
4020
|
if (!confirm) {
|
|
3607
|
-
console.log(
|
|
4021
|
+
console.log(chalk20.dim(` ${ko.nlp.menuHint}`));
|
|
3608
4022
|
return;
|
|
3609
4023
|
}
|
|
3610
4024
|
}
|
|
@@ -3622,21 +4036,28 @@ var KO_ALIASES = {
|
|
|
3622
4036
|
sync: "\uADDC\uCE59",
|
|
3623
4037
|
check: "\uC810\uAC80",
|
|
3624
4038
|
secure: "\uBCF4\uC548",
|
|
3625
|
-
ship: "\
|
|
4039
|
+
ship: "\uCD9C\uD558",
|
|
3626
4040
|
doctor: "\uD658\uACBD",
|
|
3627
4041
|
save: "\uC800\uC7A5",
|
|
3628
4042
|
undo: "\uB418\uB3CC\uB9AC\uAE30",
|
|
3629
4043
|
status: "\uC0C1\uD0DC",
|
|
3630
|
-
diff: "\uBCC0\uACBD"
|
|
4044
|
+
diff: "\uBCC0\uACBD",
|
|
4045
|
+
deploy: "\uBC30\uD3EC",
|
|
4046
|
+
env: "\uD658\uACBD\uBCC0\uC218",
|
|
4047
|
+
"env-check": "\uD658\uACBD\uBCC0\uC218\uC810\uAC80",
|
|
4048
|
+
publish: "\uCD9C\uC2DC"
|
|
3631
4049
|
};
|
|
3632
|
-
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.
|
|
4050
|
+
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.7.0");
|
|
3633
4051
|
program.configureHelp({
|
|
3634
4052
|
formatHelp(cmd, helper) {
|
|
3635
4053
|
if (cmd.parent) {
|
|
3636
4054
|
return defaultHelp.formatHelp(cmd, helper);
|
|
3637
4055
|
}
|
|
3638
4056
|
const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
|
|
3639
|
-
const terms = subs.map((c) =>
|
|
4057
|
+
const terms = subs.map((c) => {
|
|
4058
|
+
const alias = KO_ALIASES[c.name()];
|
|
4059
|
+
return alias ? `${c.name()} (${alias})` : c.name();
|
|
4060
|
+
});
|
|
3640
4061
|
const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
|
|
3641
4062
|
const lines = [
|
|
3642
4063
|
helper.commandDescription(cmd),
|
|
@@ -3657,7 +4078,7 @@ program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").descri
|
|
|
3657
4078
|
program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").description("RULES.md \uADDC\uCE59 \uC810\uAC80 \u2014 \uCF54\uB4DC \uC704\uBC18 \uAC80\uC0AC").action(check);
|
|
3658
4079
|
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C \u2014 scan: \uC2DC\uD06C\uB9BF\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC").action(secure);
|
|
3659
4080
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
3660
|
-
program.command("ship").alias("\
|
|
4081
|
+
program.command("ship").alias("\uCD9C\uD558").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
3661
4082
|
program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
|
|
3662
4083
|
program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
|
|
3663
4084
|
await save();
|
|
@@ -3669,6 +4090,24 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
|
|
|
3669
4090
|
await status();
|
|
3670
4091
|
});
|
|
3671
4092
|
program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
|
|
4093
|
+
program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1 MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC6A9)").action(async () => {
|
|
4094
|
+
await startMcpServer();
|
|
4095
|
+
});
|
|
4096
|
+
program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
|
|
4097
|
+
await mcpInit();
|
|
4098
|
+
});
|
|
4099
|
+
program.command("deploy").alias("\uBC30\uD3EC").description("\uD504\uB85C\uB355\uC158 \uBC30\uD3EC (Vercel/Netlify/Cloudflare \uC790\uB3D9 \uAC10\uC9C0)").action(async () => {
|
|
4100
|
+
await deploy();
|
|
4101
|
+
});
|
|
4102
|
+
program.command("env").alias("\uD658\uACBD\uBCC0\uC218").description(".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore \uC790\uB3D9 \uCD94\uAC00").action(async () => {
|
|
4103
|
+
await env();
|
|
4104
|
+
});
|
|
4105
|
+
program.command("env-check").alias("\uD658\uACBD\uBCC0\uC218\uC810\uAC80").description("\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC").action(async () => {
|
|
4106
|
+
await envCheck();
|
|
4107
|
+
});
|
|
4108
|
+
program.command("publish").alias("\uCD9C\uC2DC").description("npm \uBC30\uD3EC (\uBC84\uC804 \uBC94\uD504 \u2192 \uBE4C\uB4DC \u2192 \uD14C\uC2A4\uD2B8 \u2192 publish)").action(async () => {
|
|
4109
|
+
await publish();
|
|
4110
|
+
});
|
|
3672
4111
|
program.on("command:*", async (operands) => {
|
|
3673
4112
|
const unknown = operands[0] ?? "";
|
|
3674
4113
|
const rest = operands.slice(1);
|
|
@@ -3677,7 +4116,7 @@ program.on("command:*", async (operands) => {
|
|
|
3677
4116
|
});
|
|
3678
4117
|
program.action(async () => {
|
|
3679
4118
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
3680
|
-
const { choice } = await
|
|
4119
|
+
const { choice } = await inquirer10.prompt([{
|
|
3681
4120
|
type: "list",
|
|
3682
4121
|
name: "choice",
|
|
3683
4122
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
package/package.json
CHANGED
|
@@ -1,62 +1,65 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@byh3071/vhk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Vibe Harness Kit โ ๋ฐ์ด๋ธ์ฝ๋ฉ ํ์ฌ์ดํด CLI",
|
|
5
|
-
"bin": {
|
|
6
|
-
"vhk": "dist/index.js"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"test
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@byh3071/vhk",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Vibe Harness Kit โ ๋ฐ์ด๋ธ์ฝ๋ฉ ํ์ฌ์ดํด CLI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"vhk": "dist/index.js",
|
|
7
|
+
"vhk-mcp": "dist/mcp/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:run": "vitest --run",
|
|
15
|
+
"prepublishOnly": "pnpm build && pnpm test:run",
|
|
16
|
+
"save": "vhk save",
|
|
17
|
+
"check": "vhk check",
|
|
18
|
+
"scan": "vhk secure scan",
|
|
19
|
+
"recap": "vhk recap",
|
|
20
|
+
"ship": "vhk ship",
|
|
21
|
+
"doctor": "vhk doctor"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"vibe-coding",
|
|
30
|
+
"harness",
|
|
31
|
+
"cli",
|
|
32
|
+
"scaffold",
|
|
33
|
+
"session-log",
|
|
34
|
+
"rules-sync"
|
|
35
|
+
],
|
|
36
|
+
"author": "byh3071 <byh3071@gmail.com>",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/byh3071-cpu/vhk.git"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
+
"@notionhq/client": "^5.22.0",
|
|
48
|
+
"chalk": "^5.6.2",
|
|
49
|
+
"commander": "^14.0.3",
|
|
50
|
+
"handlebars": "^4.7.9",
|
|
51
|
+
"inquirer": "^9.3.8",
|
|
52
|
+
"ora": "^9.4.0",
|
|
53
|
+
"simple-git": "^3.36.0",
|
|
54
|
+
"zod": "^4.4.3"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/inquirer": "^9.0.9",
|
|
58
|
+
"@types/node": "^25.9.1",
|
|
59
|
+
"ignore": "^7.0.5",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"tsx": "^4.22.3",
|
|
62
|
+
"typescript": "^6.0.3",
|
|
63
|
+
"vitest": "^4.1.7"
|
|
64
|
+
}
|
|
65
|
+
}
|