@byh3071/vhk 0.6.0 → 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
@@ -83,7 +83,7 @@ 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 유지) |
@@ -91,6 +91,10 @@ vhk 기획 끝났고 바로 시작
91
91
  | `vhk status` | `상태`, `현황` | 브랜치·변경·커밋·원격·버전 대시보드 |
92
92
  | `vhk mcp` | — | MCP 서버 시작 (Cursor 등 MCP 클라이언트용, stdio) |
93
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) |
94
98
 
95
99
  ### init 옵션
96
100
 
@@ -29,9 +29,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
30
30
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31
31
  import { z } from "zod";
32
- import { execFileSync } from "child_process";
33
32
  import { existsSync, readFileSync } from "fs";
34
- var SERVER_VERSION = "0.6.0";
33
+
34
+ // src/lib/exec.ts
35
+ import { execFileSync } from "child_process";
35
36
  var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
36
37
  function platformCmd(cmd) {
37
38
  if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
@@ -51,6 +52,21 @@ function safeExecFile(cmd, args) {
51
52
  return { ok: false, err: msg };
52
53
  }
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";
54
70
  function isGitRepo() {
55
71
  return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
56
72
  }
@@ -294,5 +310,7 @@ async function startMcpServer() {
294
310
  export {
295
311
  __commonJS,
296
312
  __toESM,
313
+ safeExecFile,
314
+ safeExecFileStream,
297
315
  startMcpServer
298
316
  };
package/dist/index.js CHANGED
@@ -2,8 +2,10 @@
2
2
  import {
3
3
  __commonJS,
4
4
  __toESM,
5
+ safeExecFile,
6
+ safeExecFileStream,
5
7
  startMcpServer
6
- } from "./chunk-IU37BEQA.js";
8
+ } from "./chunk-NJDRNI3S.js";
7
9
 
8
10
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
9
11
  var require_ignore = __commonJS({
@@ -465,7 +467,7 @@ var require_ignore = __commonJS({
465
467
 
466
468
  // src/index.ts
467
469
  import { Command, Help } from "commander";
468
- import inquirer8 from "inquirer";
470
+ import inquirer10 from "inquirer";
469
471
 
470
472
  // src/lib/nlp-router.ts
471
473
  function normalize(input) {
@@ -571,9 +573,33 @@ var RULES = [
571
573
  },
572
574
  {
573
575
  command: "ship",
574
- 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)",
575
583
  confidence: "high",
576
- test: (t2) => /배포|출시|릴리스|ship|빌드\s*전/.test(t2)
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)",
601
+ confidence: "high",
602
+ test: (t2) => /^출시$|출시\s*해|^publish$|퍼블리시|npm\s*(배포|출시)|버전\s*올|^릴리즈$|^release$/.test(t2) && !/체크|준비|회고/.test(t2)
577
603
  }
578
604
  ];
579
605
  function routeNaturalLanguage(input) {
@@ -618,8 +644,6 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
618
644
  "scan",
619
645
  "\uC2A4\uCE94",
620
646
  "ship",
621
- "\uBC30\uD3EC",
622
- "\uB9B4\uB9AC\uC988",
623
647
  "doctor",
624
648
  "\uD658\uACBD",
625
649
  "\uC9C4\uB2E8",
@@ -636,6 +660,15 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
636
660
  "mcp",
637
661
  "mcp-init",
638
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",
639
672
  "help"
640
673
  ]);
641
674
  function isOptionToken(token) {
@@ -659,8 +692,8 @@ function detectNaturalLanguageInput(argv) {
659
692
  }
660
693
 
661
694
  // src/lib/nlp-run.ts
662
- import chalk17 from "chalk";
663
- import inquirer7 from "inquirer";
695
+ import chalk20 from "chalk";
696
+ import inquirer9 from "inquirer";
664
697
 
665
698
  // src/i18n/ko.ts
666
699
  var ko = {
@@ -908,6 +941,30 @@ var ko = {
908
941
  mcp: {
909
942
  initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
910
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"
911
968
  }
912
969
  };
913
970
  function lookup(path15) {
@@ -3552,6 +3609,16 @@ import { join } from "path";
3552
3609
  import { fileURLToPath as fileURLToPath2 } from "url";
3553
3610
  import chalk16 from "chalk";
3554
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
+ }
3555
3622
  try {
3556
3623
  const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
3557
3624
  if (typeof url === "string") return fileURLToPath2(url);
@@ -3595,6 +3662,301 @@ async function mcpInit() {
3595
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'));
3596
3663
  }
3597
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
+
3598
3960
  // src/lib/nlp-run.ts
3599
3961
  async function dispatchNlpRoute(route, input) {
3600
3962
  switch (route.command) {
@@ -3627,28 +3989,36 @@ async function dispatchNlpRoute(route, input) {
3627
3989
  return diff();
3628
3990
  case "mcp-init":
3629
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();
3630
4000
  }
3631
4001
  }
3632
4002
  async function runNaturalLanguageRoute(input) {
3633
4003
  const route = routeNaturalLanguage(input);
3634
4004
  if (!route) {
3635
- console.log(chalk17.yellow(`
4005
+ console.log(chalk20.yellow(`
3636
4006
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3637
4007
  `));
3638
4008
  return;
3639
4009
  }
3640
4010
  console.log("");
3641
- console.log(chalk17.cyan(` \u{1F4AC} "${input}"`));
3642
- console.log(chalk17.cyan(` \u2192 ${route.explanation}`));
4011
+ console.log(chalk20.cyan(` \u{1F4AC} "${input}"`));
4012
+ console.log(chalk20.cyan(` \u2192 ${route.explanation}`));
3643
4013
  if (route.confidence === "low") {
3644
- const { confirm } = await inquirer7.prompt([{
4014
+ const { confirm } = await inquirer9.prompt([{
3645
4015
  type: "confirm",
3646
4016
  name: "confirm",
3647
4017
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
3648
4018
  default: true
3649
4019
  }]);
3650
4020
  if (!confirm) {
3651
- console.log(chalk17.dim(` ${ko.nlp.menuHint}`));
4021
+ console.log(chalk20.dim(` ${ko.nlp.menuHint}`));
3652
4022
  return;
3653
4023
  }
3654
4024
  }
@@ -3666,14 +4036,18 @@ var KO_ALIASES = {
3666
4036
  sync: "\uADDC\uCE59",
3667
4037
  check: "\uC810\uAC80",
3668
4038
  secure: "\uBCF4\uC548",
3669
- ship: "\uBC30\uD3EC",
4039
+ ship: "\uCD9C\uD558",
3670
4040
  doctor: "\uD658\uACBD",
3671
4041
  save: "\uC800\uC7A5",
3672
4042
  undo: "\uB418\uB3CC\uB9AC\uAE30",
3673
4043
  status: "\uC0C1\uD0DC",
3674
- 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"
3675
4049
  };
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");
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");
3677
4051
  program.configureHelp({
3678
4052
  formatHelp(cmd, helper) {
3679
4053
  if (cmd.parent) {
@@ -3704,7 +4078,7 @@ program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").descri
3704
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);
3705
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);
3706
4080
  secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
3707
- 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);
3708
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);
3709
4083
  program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
3710
4084
  await save();
@@ -3722,6 +4096,18 @@ program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1
3722
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 () => {
3723
4097
  await mcpInit();
3724
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
+ });
3725
4111
  program.on("command:*", async (operands) => {
3726
4112
  const unknown = operands[0] ?? "";
3727
4113
  const rest = operands.slice(1);
@@ -3730,7 +4116,7 @@ program.on("command:*", async (operands) => {
3730
4116
  });
3731
4117
  program.action(async () => {
3732
4118
  console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
3733
- const { choice } = await inquirer8.prompt([{
4119
+ const { choice } = await inquirer10.prompt([{
3734
4120
  type: "list",
3735
4121
  name: "choice",
3736
4122
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
package/dist/mcp/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "../chunk-IU37BEQA.js";
4
+ } from "../chunk-NJDRNI3S.js";
5
5
 
6
6
  // src/mcp/index.ts
7
7
  startMcpServer().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byh3071/vhk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Vibe Harness Kit — 바이브코딩 풀사이클 CLI",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",