@byh3071/vhk 0.5.2 โ 0.6.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 +43 -3
- package/dist/chunk-IU37BEQA.js +298 -0
- package/dist/index.js +154 -36
- package/dist/mcp/index.js +10 -0
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
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
|
+
>
|
|
11
|
+
> ๐ฝ๏ธ **VHK๋ VHK๋ก ๋ถํธ์คํธ๋ฉ๋จ** โ ์ด ๋ ํฌ์ `docs/`, `CLAUDE.md`, `.cursorrules`๋ `vhk init`์ด ๋ง๋ค์์ต๋๋ค.
|
|
10
12
|
|
|
11
13
|
๋ช
๋ น์ด๋ฅผ ์ธ์ฐ์ง ์์๋ ๋ฉ๋๋ค. `vhk`๋ง ์น๋ฉด ๋ฉ๋ด๊ฐ ๋์ค๊ณ , ํ๊ตญ์ด๋ก ๋งํด๋ ์์๋ฃ์ต๋๋ค.
|
|
12
14
|
|
|
@@ -87,6 +89,8 @@ vhk ๊ธฐํ ๋๋ฌ๊ณ ๋ฐ๋ก ์์
|
|
|
87
89
|
| `vhk undo` | `๋๋๋ฆฌ๊ธฐ`, `์ทจ์` | ์ต๊ทผ ์ปค๋ฐ soft reset (๋ณ๊ฒฝ์ staged ์ ์ง) |
|
|
88
90
|
| `vhk diff` | `๋ณ๊ฒฝ`, `์ฐจ์ด` | staged / unstaged / ์ ํ์ผ ์์ฝ (์ค ์ ํฉ๊ณ๋ trackedยทHEAD ๊ธฐ์ค) |
|
|
89
91
|
| `vhk status` | `์ํ`, `ํํฉ` | ๋ธ๋์นยท๋ณ๊ฒฝยท์ปค๋ฐยท์๊ฒฉยท๋ฒ์ ๋์๋ณด๋ |
|
|
92
|
+
| `vhk mcp` | โ | MCP ์๋ฒ ์์ (Cursor ๋ฑ MCP ํด๋ผ์ด์ธํธ์ฉ, stdio) |
|
|
93
|
+
| `vhk mcp-init` | `mcp์ค์ ` | Cursor `.cursor/mcp.json` ์๋ ์์ฑ |
|
|
90
94
|
|
|
91
95
|
### init ์ต์
|
|
92
96
|
|
|
@@ -103,6 +107,42 @@ vhk ๊ธฐํ ๋๋ฌ๊ณ ๋ฐ๋ก ์์
|
|
|
103
107
|
|------|------|
|
|
104
108
|
| `--since YYYY-MM-DD` | ๋ถ์ ์์์ผ (๊ธฐ๋ณธ: ์ค๋) |
|
|
105
109
|
|
|
110
|
+
## Cursor์ MCP๋ก ์ฐ๋ํ๊ธฐ
|
|
111
|
+
|
|
112
|
+
`v0.6.0`๋ถํฐ vhk๋ [Model Context Protocol](https://modelcontextprotocol.io) ์๋ฒ๋ฅผ ๋ด์ฅํฉ๋๋ค. Cursor ์ฑํ
์์ ์์ฐ์ด๋ก vhk ๋๊ตฌ๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค.
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
vhk mcp-init # .cursor/mcp.json ์๋ ์์ฑ
|
|
116
|
+
# โ Cursor ์ฌ์์ ํ ์ฑํ
์์ ์์ฐ์ด๋ก vhk ๋๊ตฌ ํธ์ถ ๊ฐ๋ฅ
|
|
117
|
+
# ์: "์ํ ์๋ ค์ค" โ Cursor๊ฐ vhk status ๋๊ตฌ ํธ์ถ
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
๋
ธ์ถ๋๋ MCP ๋๊ตฌ 8๊ฐ: `save`, `undo`, `status`, `diff`, `ship`, `doctor`, `check`, `recap`.
|
|
121
|
+
|
|
122
|
+
MCP ์๋ฒ๋ฅผ ์๋์ผ๋ก ๋์ฐ๋ ค๋ฉด:
|
|
123
|
+
|
|
124
|
+
```powershell
|
|
125
|
+
vhk mcp # stdio ์๋ฒ ์์ (Cursor๊ฐ ์๋์ผ๋ก ํธ์ถ)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## v0.6.0 ํ์ด๋ผ์ดํธ
|
|
129
|
+
|
|
130
|
+
| ๊ธฐ๋ฅ | ์ค๋ช
|
|
|
131
|
+
|------|------|
|
|
132
|
+
| **MCP ์๋ฒ** | `vhk mcp` โ 8๊ฐ ๋๊ตฌ(save/undo/status/diff/ship/doctor/check/recap)๋ฅผ stdio๋ก ๋
ธ์ถ. Cursor ๋ฑ MCP ํด๋ผ์ด์ธํธ์์ ์์ฐ์ด๋ก ํธ์ถ |
|
|
133
|
+
| **mcp-init** | `vhk mcp-init` โ Cursor `.cursor/mcp.json` ์๋ ์์ฑ. ์ฌ์์ ํ ๋ฒ์ผ๋ก ์ฐ๋ ์๋ฃ |
|
|
134
|
+
| **์์ฐ์ด ๋ผ์ฐํ
ํ์ฅ** | `vhk mcp์ค์ ` โ `vhk mcp-init` ๋ณ์นญ |
|
|
135
|
+
| **๋ณด์** | MCP save ๋๊ตฌ์ shell injection ์ฐจ๋จ โ ๋ชจ๋ git ํธ์ถ์ `execFileSync` ์ฌ์ฉ |
|
|
136
|
+
|
|
137
|
+
## v0.5.3 ํ์ด๋ผ์ดํธ
|
|
138
|
+
|
|
139
|
+
| ๊ธฐ๋ฅ | ์ค๋ช
|
|
|
140
|
+
|------|------|
|
|
141
|
+
| **์
ํํธ์คํ
** | `vhk init`์ด vhk-cli ๋ ํฌ ์์ฒด๋ฅผ ๋ถํธ์คํธ๋ฉ โ ์๊ธฐ ๋๊ตฌ๋ก ์๊ธฐ ๋ ํฌ ๋ง๋ค๊ธฐ |
|
|
142
|
+
| **CHANGELOG.md** | ๋ณ๊ฒฝ ์ด๋ ฅ ํ์ค ํ์ผ ์ ์ค. `vhk ship`์ด `[Unreleased]` โ `[๋ฒ์ ]` ์๋ ์ด๋ |
|
|
143
|
+
| **doctor ์
๋ฐ์ดํธ ์๋ฆผ** | `vhk doctor`๊ฐ npm ์ต์ ๋ฒ์ ๋น๊ต ํ `๐ v0.X.X ์ฌ์ฉ ๊ฐ๋ฅ` ํ ์ค ํ์ |
|
|
144
|
+
| **init ์์ ์ฑ** | ์ต์
๊ฐ ํฌํจ ๋ช
๋ น ๋ผ์ฐํ
๋ฒ๊ทธ ํฝ์ค, ์ฌ์ฉ์ ์ ์ `package.json` scripts ๋ณด์กด |
|
|
145
|
+
|
|
106
146
|
## v0.5.0 ํ์ด๋ผ์ดํธ
|
|
107
147
|
|
|
108
148
|
| ๊ธฐ๋ฅ | ์ค๋ช
|
|
|
@@ -0,0 +1,298 @@
|
|
|
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 { execFileSync } from "child_process";
|
|
33
|
+
import { existsSync, readFileSync } from "fs";
|
|
34
|
+
var SERVER_VERSION = "0.6.0";
|
|
35
|
+
var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
36
|
+
function platformCmd(cmd) {
|
|
37
|
+
if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
|
|
38
|
+
return `${cmd}.cmd`;
|
|
39
|
+
}
|
|
40
|
+
return cmd;
|
|
41
|
+
}
|
|
42
|
+
function safeExecFile(cmd, args) {
|
|
43
|
+
try {
|
|
44
|
+
const out = execFileSync(platformCmd(cmd), args, {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
47
|
+
}).toString();
|
|
48
|
+
return { ok: true, out: out.trim() };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
51
|
+
return { ok: false, err: msg };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isGitRepo() {
|
|
55
|
+
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
56
|
+
}
|
|
57
|
+
function createVhkMcpServer() {
|
|
58
|
+
const server = new McpServer({
|
|
59
|
+
name: "vhk",
|
|
60
|
+
version: SERVER_VERSION
|
|
61
|
+
});
|
|
62
|
+
server.registerTool(
|
|
63
|
+
"save",
|
|
64
|
+
{
|
|
65
|
+
description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async ({ message }) => {
|
|
71
|
+
if (!isGitRepo()) {
|
|
72
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
73
|
+
}
|
|
74
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
75
|
+
if (!status.ok) {
|
|
76
|
+
return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
|
|
77
|
+
}
|
|
78
|
+
if (!status.out) {
|
|
79
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
80
|
+
}
|
|
81
|
+
const files = status.out.split("\n");
|
|
82
|
+
const now = /* @__PURE__ */ new Date();
|
|
83
|
+
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")}`;
|
|
84
|
+
const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
|
|
85
|
+
const add = safeExecFile("git", ["add", "."]);
|
|
86
|
+
if (!add.ok) {
|
|
87
|
+
return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
|
|
88
|
+
}
|
|
89
|
+
const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
|
|
90
|
+
if (!commit.ok) {
|
|
91
|
+
return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
|
|
92
|
+
}
|
|
93
|
+
const push = safeExecFile("git", ["push"]);
|
|
94
|
+
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)";
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
|
|
100
|
+
\uCEE4\uBC0B: ${commitMsg}`
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
|
|
107
|
+
if (!isGitRepo()) {
|
|
108
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
109
|
+
}
|
|
110
|
+
const last = safeExecFile("git", ["log", "--oneline", "-1"]);
|
|
111
|
+
if (!last.ok || !last.out) {
|
|
112
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
113
|
+
}
|
|
114
|
+
const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
|
|
115
|
+
if (!reset.ok) {
|
|
116
|
+
return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
|
|
123
|
+
\uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
|
|
124
|
+
\u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
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 () => {
|
|
130
|
+
const lines = [];
|
|
131
|
+
if (existsSync("package.json")) {
|
|
132
|
+
try {
|
|
133
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
134
|
+
lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!isGitRepo()) {
|
|
139
|
+
lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
140
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
141
|
+
}
|
|
142
|
+
const branch = safeExecFile("git", ["branch", "--show-current"]);
|
|
143
|
+
if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
|
|
144
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
145
|
+
if (status.ok) {
|
|
146
|
+
if (!status.out) {
|
|
147
|
+
lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
|
|
148
|
+
} else {
|
|
149
|
+
const fileLines = status.out.split("\n");
|
|
150
|
+
const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
|
|
151
|
+
const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
|
|
152
|
+
const untracked = fileLines.filter((l) => l.startsWith("??")).length;
|
|
153
|
+
const parts = [];
|
|
154
|
+
if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
|
|
155
|
+
if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
|
|
156
|
+
if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
|
|
157
|
+
lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const log = safeExecFile("git", ["log", "--oneline", "-3"]);
|
|
161
|
+
if (log.ok && log.out) {
|
|
162
|
+
lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
|
|
163
|
+
log.out.split("\n").forEach((l) => lines.push(` ${l}`));
|
|
164
|
+
}
|
|
165
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
166
|
+
});
|
|
167
|
+
server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
|
|
168
|
+
if (!isGitRepo()) {
|
|
169
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
170
|
+
}
|
|
171
|
+
const unstaged = safeExecFile("git", ["diff", "--stat"]);
|
|
172
|
+
const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
|
|
173
|
+
const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
174
|
+
const unstagedOut = unstaged.ok ? unstaged.out : "";
|
|
175
|
+
const stagedOut = staged.ok ? staged.out : "";
|
|
176
|
+
const untrackedOut = untracked.ok ? untracked.out : "";
|
|
177
|
+
if (!unstagedOut && !stagedOut && !untrackedOut) {
|
|
178
|
+
return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
|
|
179
|
+
}
|
|
180
|
+
const lines = [];
|
|
181
|
+
if (stagedOut) {
|
|
182
|
+
lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
|
|
183
|
+
lines.push(stagedOut);
|
|
184
|
+
}
|
|
185
|
+
if (unstagedOut) {
|
|
186
|
+
lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
|
|
187
|
+
lines.push(unstagedOut);
|
|
188
|
+
}
|
|
189
|
+
if (untrackedOut) {
|
|
190
|
+
const files = untrackedOut.split("\n");
|
|
191
|
+
lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
|
|
192
|
+
files.forEach((f) => lines.push(` + ${f}`));
|
|
193
|
+
}
|
|
194
|
+
const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
|
|
195
|
+
if (numstat.ok && numstat.out) {
|
|
196
|
+
let totalAdd = 0;
|
|
197
|
+
let totalDel = 0;
|
|
198
|
+
let fileCount = 0;
|
|
199
|
+
numstat.out.split("\n").forEach((line) => {
|
|
200
|
+
const [add, del] = line.split(" ");
|
|
201
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
202
|
+
totalDel += parseInt(del, 10) || 0;
|
|
203
|
+
fileCount += 1;
|
|
204
|
+
});
|
|
205
|
+
lines.push(`
|
|
206
|
+
\u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
|
|
207
|
+
}
|
|
208
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
209
|
+
});
|
|
210
|
+
server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
|
|
211
|
+
const checks = [];
|
|
212
|
+
const build = safeExecFile("pnpm", ["build"]);
|
|
213
|
+
checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
|
|
214
|
+
const test = safeExecFile("pnpm", ["test", "--run"]);
|
|
215
|
+
checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
216
|
+
if (existsSync("package.json")) {
|
|
217
|
+
try {
|
|
218
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
219
|
+
checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (isGitRepo()) {
|
|
224
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
225
|
+
if (status.ok) {
|
|
226
|
+
if (status.out) {
|
|
227
|
+
checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
|
|
228
|
+
} else {
|
|
229
|
+
checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
|
|
234
|
+
});
|
|
235
|
+
server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
|
|
236
|
+
const checks = [];
|
|
237
|
+
const node = safeExecFile("node", ["--version"]);
|
|
238
|
+
checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
|
|
239
|
+
const git = safeExecFile("git", ["--version"]);
|
|
240
|
+
checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
|
|
241
|
+
const pnpm = safeExecFile("pnpm", ["--version"]);
|
|
242
|
+
checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
|
|
243
|
+
const npm = safeExecFile("npm", ["--version"]);
|
|
244
|
+
checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
|
|
245
|
+
const tsc = safeExecFile("npx", ["tsc", "--version"]);
|
|
246
|
+
checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
|
|
247
|
+
return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
|
|
248
|
+
});
|
|
249
|
+
server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
|
|
250
|
+
const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
|
|
251
|
+
const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
|
|
252
|
+
const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
|
|
253
|
+
required.forEach((f) => {
|
|
254
|
+
lines.push(` ${existsSync(f) ? "\u2705" : "\u274C"} ${f}`);
|
|
255
|
+
});
|
|
256
|
+
lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
|
|
257
|
+
recommended.forEach((f) => {
|
|
258
|
+
lines.push(` ${existsSync(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
|
|
259
|
+
});
|
|
260
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
261
|
+
});
|
|
262
|
+
server.registerTool(
|
|
263
|
+
"recap",
|
|
264
|
+
{
|
|
265
|
+
description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
async ({ count }) => {
|
|
271
|
+
if (!isGitRepo()) {
|
|
272
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
273
|
+
}
|
|
274
|
+
const n = count && count > 0 ? Math.floor(count) : 10;
|
|
275
|
+
const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
|
|
276
|
+
if (!log.ok) {
|
|
277
|
+
return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
|
|
278
|
+
}
|
|
279
|
+
if (!log.out) {
|
|
280
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
281
|
+
}
|
|
282
|
+
return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
|
|
283
|
+
${log.out}` }] };
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
return server;
|
|
287
|
+
}
|
|
288
|
+
async function startMcpServer() {
|
|
289
|
+
const server = createVhkMcpServer();
|
|
290
|
+
const transport = new StdioServerTransport();
|
|
291
|
+
await server.connect(transport);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export {
|
|
295
|
+
__commonJS,
|
|
296
|
+
__toESM,
|
|
297
|
+
startMcpServer
|
|
298
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
));
|
|
2
|
+
import {
|
|
3
|
+
__commonJS,
|
|
4
|
+
__toESM,
|
|
5
|
+
startMcpServer
|
|
6
|
+
} from "./chunk-IU37BEQA.js";
|
|
27
7
|
|
|
28
8
|
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
29
9
|
var require_ignore = __commonJS({
|
|
@@ -523,6 +503,12 @@ var RULES = [
|
|
|
523
503
|
confidence: "high",
|
|
524
504
|
test: (t2) => /ํ๋ก์ ํธ.*(๋ง๋ค|์์)|ํด๋.*๋ง๋ค|๋ง๋ค๊ณ \s*์ถ|ํ๋ค์ค|์ด๊ธฐํ/.test(t2) || /^์์$/.test(t2)
|
|
525
505
|
},
|
|
506
|
+
{
|
|
507
|
+
command: "mcp-init",
|
|
508
|
+
explanation: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815 (vhk mcp-init)",
|
|
509
|
+
confidence: "high",
|
|
510
|
+
test: (t2) => /mcp.*(์ค์ |์ฐ๋|์ด๊ธฐ|init)|์ปค์.*(์ฐ๋|์ค์ |mcp)|cursor.*mcp/.test(t2)
|
|
511
|
+
},
|
|
526
512
|
{
|
|
527
513
|
command: "secure",
|
|
528
514
|
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
|
|
@@ -647,6 +633,9 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
647
633
|
"diff",
|
|
648
634
|
"\uBCC0\uACBD",
|
|
649
635
|
"\uCC28\uC774",
|
|
636
|
+
"mcp",
|
|
637
|
+
"mcp-init",
|
|
638
|
+
"mcp\uC124\uC815",
|
|
650
639
|
"help"
|
|
651
640
|
]);
|
|
652
641
|
function isOptionToken(token) {
|
|
@@ -657,11 +646,11 @@ function detectNaturalLanguageInput(argv) {
|
|
|
657
646
|
if (rest.length === 0) return null;
|
|
658
647
|
const first = rest[0];
|
|
659
648
|
if (isOptionToken(first)) return null;
|
|
649
|
+
if (rest.some(isOptionToken)) return null;
|
|
660
650
|
const input = rest.join(" ").trim();
|
|
661
651
|
if (!input) return null;
|
|
662
652
|
const firstIsKnown = KNOWN_COMMAND_TOKENS.has(first);
|
|
663
653
|
if (firstIsKnown && rest.length === 1) return null;
|
|
664
|
-
if (firstIsKnown && rest.slice(1).every(isOptionToken)) return null;
|
|
665
654
|
if (firstIsKnown && rest.length > 1) {
|
|
666
655
|
if (routeNaturalLanguage(input)) return input;
|
|
667
656
|
return null;
|
|
@@ -670,7 +659,7 @@ function detectNaturalLanguageInput(argv) {
|
|
|
670
659
|
}
|
|
671
660
|
|
|
672
661
|
// src/lib/nlp-run.ts
|
|
673
|
-
import
|
|
662
|
+
import chalk17 from "chalk";
|
|
674
663
|
import inquirer7 from "inquirer";
|
|
675
664
|
|
|
676
665
|
// src/i18n/ko.ts
|
|
@@ -854,7 +843,9 @@ var ko = {
|
|
|
854
843
|
projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
|
|
855
844
|
envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
|
|
856
845
|
nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
|
|
857
|
-
nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694."
|
|
846
|
+
nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
|
|
847
|
+
updateAvailable: (latest) => `\u{1F195} v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 npm i -g @byh3071/vhk`,
|
|
848
|
+
updateCurrent: "\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC4F0\uACE0 \uC788\uC5B4\uC694"
|
|
858
849
|
},
|
|
859
850
|
nlp: {
|
|
860
851
|
matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
|
|
@@ -909,7 +900,14 @@ var ko = {
|
|
|
909
900
|
checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
|
|
910
901
|
hintSecurity: "vhk \uBCF4\uC548 scan",
|
|
911
902
|
checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
|
|
912
|
-
hintCommit: "git status \uD655\uC778"
|
|
903
|
+
hintCommit: "git status \uD655\uC778",
|
|
904
|
+
changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
|
|
905
|
+
changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
|
|
906
|
+
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."
|
|
907
|
+
},
|
|
908
|
+
mcp: {
|
|
909
|
+
initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
|
|
910
|
+
serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
|
|
913
911
|
}
|
|
914
912
|
};
|
|
915
913
|
function lookup(path15) {
|
|
@@ -1730,7 +1728,7 @@ function enhancePackageScripts(projectDir) {
|
|
|
1730
1728
|
const pkgPath = path3.join(projectDir, "package.json");
|
|
1731
1729
|
if (!fs3.existsSync(pkgPath)) return false;
|
|
1732
1730
|
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1733
|
-
pkg.scripts = { ...pkg.scripts
|
|
1731
|
+
pkg.scripts = { ...VHK_PACKAGE_SCRIPTS, ...pkg.scripts };
|
|
1734
1732
|
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1735
1733
|
return true;
|
|
1736
1734
|
}
|
|
@@ -2796,6 +2794,27 @@ function getVhkVersion() {
|
|
|
2796
2794
|
}
|
|
2797
2795
|
return void 0;
|
|
2798
2796
|
}
|
|
2797
|
+
function fetchLatestNpmVersion(packageName) {
|
|
2798
|
+
try {
|
|
2799
|
+
const result = execSync(`npm view ${packageName} version`, {
|
|
2800
|
+
encoding: "utf-8",
|
|
2801
|
+
timeout: 5e3,
|
|
2802
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2803
|
+
}).trim();
|
|
2804
|
+
if (/^\d+\.\d+\.\d+/.test(result)) return result;
|
|
2805
|
+
return void 0;
|
|
2806
|
+
} catch {
|
|
2807
|
+
return void 0;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
function compareSemver(a, b) {
|
|
2811
|
+
const parse = (v) => v.replace(/^v/i, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
2812
|
+
const [a1 = 0, a2 = 0, a3 = 0] = parse(a);
|
|
2813
|
+
const [b1 = 0, b2 = 0, b3 = 0] = parse(b);
|
|
2814
|
+
if (a1 !== b1) return a1 - b1;
|
|
2815
|
+
if (a2 !== b2) return a2 - b2;
|
|
2816
|
+
return a3 - b3;
|
|
2817
|
+
}
|
|
2799
2818
|
async function doctor() {
|
|
2800
2819
|
console.log(chalk10.bold(`
|
|
2801
2820
|
${ko.doctor.title}
|
|
@@ -2823,6 +2842,14 @@ ${ko.doctor.title}
|
|
|
2823
2842
|
} else {
|
|
2824
2843
|
console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
2825
2844
|
}
|
|
2845
|
+
if (vhkVersion) {
|
|
2846
|
+
const latest = fetchLatestNpmVersion("@byh3071/vhk");
|
|
2847
|
+
if (latest && compareSemver(latest, vhkVersion) > 0) {
|
|
2848
|
+
console.log(chalk10.yellow(` ${ko.doctor.updateAvailable(latest)}`));
|
|
2849
|
+
} else if (latest) {
|
|
2850
|
+
console.log(chalk10.dim(` ${ko.doctor.updateCurrent}`));
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2826
2853
|
console.log("");
|
|
2827
2854
|
console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
|
|
2828
2855
|
const cwd = process.cwd();
|
|
@@ -2885,6 +2912,29 @@ var CHECKLIST = [
|
|
|
2885
2912
|
function sanitizeVersion(version) {
|
|
2886
2913
|
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
2887
2914
|
}
|
|
2915
|
+
function updateChangelogUnreleased(cwd, version, date) {
|
|
2916
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
2917
|
+
if (!fs13.existsSync(changelogPath)) return { status: "missing" };
|
|
2918
|
+
const content = fs13.readFileSync(changelogPath, "utf-8");
|
|
2919
|
+
const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
|
|
2920
|
+
if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
|
|
2921
|
+
const blankUnreleased = [
|
|
2922
|
+
"## [Unreleased]",
|
|
2923
|
+
"",
|
|
2924
|
+
"### Added",
|
|
2925
|
+
"- ",
|
|
2926
|
+
"",
|
|
2927
|
+
"### Fixed",
|
|
2928
|
+
"- ",
|
|
2929
|
+
"",
|
|
2930
|
+
"---",
|
|
2931
|
+
"",
|
|
2932
|
+
`## [${version}] \u2014 ${date}`
|
|
2933
|
+
].join("\n");
|
|
2934
|
+
const updated = content.replace(unreleasedHeading, blankUnreleased);
|
|
2935
|
+
fs13.writeFileSync(changelogPath, updated, "utf-8");
|
|
2936
|
+
return { status: "updated", version };
|
|
2937
|
+
}
|
|
2888
2938
|
async function ship() {
|
|
2889
2939
|
console.log(chalk11.bold(`
|
|
2890
2940
|
${ko.ship.title}
|
|
@@ -2976,6 +3026,14 @@ ${ko.ship.title}
|
|
|
2976
3026
|
fs13.writeFileSync(filePath, content, "utf-8");
|
|
2977
3027
|
console.log(chalk11.green(`
|
|
2978
3028
|
${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
|
|
3029
|
+
const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
|
|
3030
|
+
if (changelogResult.status === "updated") {
|
|
3031
|
+
log.success(ko.ship.changelogUpdated(changelogResult.version));
|
|
3032
|
+
} else if (changelogResult.status === "no-unreleased") {
|
|
3033
|
+
log.warn(ko.ship.changelogNoUnreleased);
|
|
3034
|
+
} else {
|
|
3035
|
+
log.info(ko.ship.changelogMissing);
|
|
3036
|
+
}
|
|
2979
3037
|
printNextStep({
|
|
2980
3038
|
message: ko.ship.deployMessage,
|
|
2981
3039
|
command: "npm publish --access=public",
|
|
@@ -3488,6 +3546,55 @@ ${t("diff.summaryHeader")}`));
|
|
|
3488
3546
|
console.log("");
|
|
3489
3547
|
}
|
|
3490
3548
|
|
|
3549
|
+
// src/commands/mcp-init.ts
|
|
3550
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3551
|
+
import { join } from "path";
|
|
3552
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3553
|
+
import chalk16 from "chalk";
|
|
3554
|
+
function resolveVhkMcpPath() {
|
|
3555
|
+
try {
|
|
3556
|
+
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
3557
|
+
if (typeof url === "string") return fileURLToPath2(url);
|
|
3558
|
+
} catch {
|
|
3559
|
+
}
|
|
3560
|
+
return join(process.cwd(), "node_modules", "@byh3071", "vhk", "dist", "mcp", "index.js");
|
|
3561
|
+
}
|
|
3562
|
+
async function mcpInit() {
|
|
3563
|
+
console.log(chalk16.bold("\n\u{1F50C} " + t("mcp.initTitle")));
|
|
3564
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
3565
|
+
const cursorDir = join(process.cwd(), ".cursor");
|
|
3566
|
+
if (!existsSync(cursorDir)) {
|
|
3567
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
3568
|
+
}
|
|
3569
|
+
const configPath = join(cursorDir, "mcp.json");
|
|
3570
|
+
const vhkEntry = {
|
|
3571
|
+
command: "node",
|
|
3572
|
+
args: [resolveVhkMcpPath()]
|
|
3573
|
+
};
|
|
3574
|
+
let config;
|
|
3575
|
+
if (existsSync(configPath)) {
|
|
3576
|
+
try {
|
|
3577
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3578
|
+
config = {
|
|
3579
|
+
mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
|
|
3580
|
+
};
|
|
3581
|
+
} catch {
|
|
3582
|
+
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."));
|
|
3583
|
+
config = { mcpServers: { vhk: vhkEntry } };
|
|
3584
|
+
}
|
|
3585
|
+
} else {
|
|
3586
|
+
config = { mcpServers: { vhk: vhkEntry } };
|
|
3587
|
+
}
|
|
3588
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3589
|
+
console.log(chalk16.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
|
|
3590
|
+
console.log(chalk16.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
|
|
3591
|
+
console.log(` ${configPath}`);
|
|
3592
|
+
console.log(chalk16.cyan("\n\u{1F504} \uB2E4\uC74C \uB2E8\uACC4:"));
|
|
3593
|
+
console.log(" 1. Cursor\uB97C \uC7AC\uC2DC\uC791\uD558\uC138\uC694");
|
|
3594
|
+
console.log(" 2. Cursor \uCC44\uD305\uC5D0\uC11C vhk \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
|
|
3595
|
+
console.log(chalk16.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3491
3598
|
// src/lib/nlp-run.ts
|
|
3492
3599
|
async function dispatchNlpRoute(route, input) {
|
|
3493
3600
|
switch (route.command) {
|
|
@@ -3518,19 +3625,21 @@ async function dispatchNlpRoute(route, input) {
|
|
|
3518
3625
|
return status();
|
|
3519
3626
|
case "diff":
|
|
3520
3627
|
return diff();
|
|
3628
|
+
case "mcp-init":
|
|
3629
|
+
return mcpInit();
|
|
3521
3630
|
}
|
|
3522
3631
|
}
|
|
3523
3632
|
async function runNaturalLanguageRoute(input) {
|
|
3524
3633
|
const route = routeNaturalLanguage(input);
|
|
3525
3634
|
if (!route) {
|
|
3526
|
-
console.log(
|
|
3635
|
+
console.log(chalk17.yellow(`
|
|
3527
3636
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3528
3637
|
`));
|
|
3529
3638
|
return;
|
|
3530
3639
|
}
|
|
3531
3640
|
console.log("");
|
|
3532
|
-
console.log(
|
|
3533
|
-
console.log(
|
|
3641
|
+
console.log(chalk17.cyan(` \u{1F4AC} "${input}"`));
|
|
3642
|
+
console.log(chalk17.cyan(` \u2192 ${route.explanation}`));
|
|
3534
3643
|
if (route.confidence === "low") {
|
|
3535
3644
|
const { confirm } = await inquirer7.prompt([{
|
|
3536
3645
|
type: "confirm",
|
|
@@ -3539,7 +3648,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
3539
3648
|
default: true
|
|
3540
3649
|
}]);
|
|
3541
3650
|
if (!confirm) {
|
|
3542
|
-
console.log(
|
|
3651
|
+
console.log(chalk17.dim(` ${ko.nlp.menuHint}`));
|
|
3543
3652
|
return;
|
|
3544
3653
|
}
|
|
3545
3654
|
}
|
|
@@ -3564,14 +3673,17 @@ var KO_ALIASES = {
|
|
|
3564
3673
|
status: "\uC0C1\uD0DC",
|
|
3565
3674
|
diff: "\uBCC0\uACBD"
|
|
3566
3675
|
};
|
|
3567
|
-
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.
|
|
3676
|
+
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.6.0");
|
|
3568
3677
|
program.configureHelp({
|
|
3569
3678
|
formatHelp(cmd, helper) {
|
|
3570
3679
|
if (cmd.parent) {
|
|
3571
3680
|
return defaultHelp.formatHelp(cmd, helper);
|
|
3572
3681
|
}
|
|
3573
3682
|
const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
|
|
3574
|
-
const terms = subs.map((c) =>
|
|
3683
|
+
const terms = subs.map((c) => {
|
|
3684
|
+
const alias = KO_ALIASES[c.name()];
|
|
3685
|
+
return alias ? `${c.name()} (${alias})` : c.name();
|
|
3686
|
+
});
|
|
3575
3687
|
const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
|
|
3576
3688
|
const lines = [
|
|
3577
3689
|
helper.commandDescription(cmd),
|
|
@@ -3604,6 +3716,12 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
|
|
|
3604
3716
|
await status();
|
|
3605
3717
|
});
|
|
3606
3718
|
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);
|
|
3719
|
+
program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1 MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC6A9)").action(async () => {
|
|
3720
|
+
await startMcpServer();
|
|
3721
|
+
});
|
|
3722
|
+
program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
|
|
3723
|
+
await mcpInit();
|
|
3724
|
+
});
|
|
3607
3725
|
program.on("command:*", async (operands) => {
|
|
3608
3726
|
const unknown = operands[0] ?? "";
|
|
3609
3727
|
const rest = operands.slice(1);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byh3071/vhk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Vibe Harness Kit โ ๋ฐ์ด๋ธ์ฝ๋ฉ ํ์ฌ์ดํด CLI",
|
|
5
5
|
"bin": {
|
|
6
|
-
"vhk": "
|
|
6
|
+
"vhk": "dist/index.js",
|
|
7
|
+
"vhk-mcp": "dist/mcp/index.js"
|
|
7
8
|
},
|
|
8
9
|
"type": "module",
|
|
9
10
|
"scripts": {
|
|
@@ -11,7 +12,13 @@
|
|
|
11
12
|
"build": "tsup",
|
|
12
13
|
"test": "vitest",
|
|
13
14
|
"test:run": "vitest --run",
|
|
14
|
-
"prepublishOnly": "pnpm build && pnpm test: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"
|
|
15
22
|
},
|
|
16
23
|
"files": [
|
|
17
24
|
"dist",
|
|
@@ -30,19 +37,21 @@
|
|
|
30
37
|
"license": "MIT",
|
|
31
38
|
"repository": {
|
|
32
39
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/byh3071-cpu/vhk.git"
|
|
40
|
+
"url": "git+https://github.com/byh3071-cpu/vhk.git"
|
|
34
41
|
},
|
|
35
42
|
"engines": {
|
|
36
43
|
"node": ">=20"
|
|
37
44
|
},
|
|
38
45
|
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
47
|
"@notionhq/client": "^5.22.0",
|
|
40
48
|
"chalk": "^5.6.2",
|
|
41
49
|
"commander": "^14.0.3",
|
|
42
50
|
"handlebars": "^4.7.9",
|
|
43
51
|
"inquirer": "^9.3.8",
|
|
44
52
|
"ora": "^9.4.0",
|
|
45
|
-
"simple-git": "^3.36.0"
|
|
53
|
+
"simple-git": "^3.36.0",
|
|
54
|
+
"zod": "^4.4.3"
|
|
46
55
|
},
|
|
47
56
|
"devDependencies": {
|
|
48
57
|
"@types/inquirer": "^9.0.9",
|