@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 CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  id: vhk-readme
3
- date: 2026-05-23
4
- tags: [vhk, cli, readme, v0.5.3]
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.5.3)
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
- 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
- ));
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 inquirer8 from "inquirer";
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 ship)",
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) => /๋ฐฐํฌ|์ถœ์‹œ|๋ฆด๋ฆฌ์Šค|ship|๋นŒ๋“œ\s*์ „/.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 chalk16 from "chalk";
674
- import inquirer7 from "inquirer";
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(chalk16.yellow(`
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(chalk16.cyan(` \u{1F4AC} "${input}"`));
3598
- console.log(chalk16.cyan(` \u2192 ${route.explanation}`));
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 inquirer7.prompt([{
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(chalk16.dim(` ${ko.nlp.menuHint}`));
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: "\uBC30\uD3EC",
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.5.3");
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) => `${c.name()} (${KO_ALIASES[c.name()]})`);
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("\uBC30\uD3EC").alias("\uB9B4\uB9AC\uC988").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
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 inquirer8.prompt([{
4119
+ const { choice } = await inquirer10.prompt([{
3681
4120
  type: "list",
3682
4121
  name: "choice",
3683
4122
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ startMcpServer
4
+ } from "../chunk-NJDRNI3S.js";
5
+
6
+ // src/mcp/index.ts
7
+ startMcpServer().catch((err) => {
8
+ console.error("VHK MCP \uC11C\uBC84 \uC2DC\uC791 \uC2E4\uD328:", err);
9
+ process.exit(1);
10
+ });
package/package.json CHANGED
@@ -1,62 +1,65 @@
1
- {
2
- "name": "@byh3071/vhk",
3
- "version": "0.5.3",
4
- "description": "Vibe Harness Kit โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ํ’€์‚ฌ์ดํด CLI",
5
- "bin": {
6
- "vhk": "dist/index.js"
7
- },
8
- "type": "module",
9
- "scripts": {
10
- "dev": "tsx src/index.ts",
11
- "build": "tsup",
12
- "test": "vitest",
13
- "test:run": "vitest --run",
14
- "prepublishOnly": "pnpm build && pnpm test:run",
15
- "save": "vhk save",
16
- "check": "vhk check",
17
- "scan": "vhk secure scan",
18
- "recap": "vhk recap",
19
- "ship": "vhk ship",
20
- "doctor": "vhk doctor"
21
- },
22
- "files": [
23
- "dist",
24
- "README.md",
25
- "LICENSE"
26
- ],
27
- "keywords": [
28
- "vibe-coding",
29
- "harness",
30
- "cli",
31
- "scaffold",
32
- "session-log",
33
- "rules-sync"
34
- ],
35
- "author": "byh3071 <byh3071@gmail.com>",
36
- "license": "MIT",
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/byh3071-cpu/vhk.git"
40
- },
41
- "engines": {
42
- "node": ">=20"
43
- },
44
- "dependencies": {
45
- "@notionhq/client": "^5.22.0",
46
- "chalk": "^5.6.2",
47
- "commander": "^14.0.3",
48
- "handlebars": "^4.7.9",
49
- "inquirer": "^9.3.8",
50
- "ora": "^9.4.0",
51
- "simple-git": "^3.36.0"
52
- },
53
- "devDependencies": {
54
- "@types/inquirer": "^9.0.9",
55
- "@types/node": "^25.9.1",
56
- "ignore": "^7.0.5",
57
- "tsup": "^8.5.1",
58
- "tsx": "^4.22.3",
59
- "typescript": "^6.0.3",
60
- "vitest": "^4.1.7"
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
+ }