pai-zero 0.11.2 → 0.11.4

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
@@ -71,7 +71,7 @@ pai grade --fail-under 70 --output report.md
71
71
  - **OMC 설치 방식 전환**: 기존 `npx -y github:Yeachan-Heo/oh-my-claudecode install` 호출을 제거하고, Claude Code 네이티브 플러그인 마켓플레이스에 등록하는 방식으로 바꿨습니다.
72
72
  - PAI는 `~/.claude/settings.json`에 `extraKnownMarketplaces.omc`와 `enabledPlugins["oh-my-claudecode@omc"]`만 안전 병합합니다. 실제 설치는 Claude Code 기동 시 자동으로 수행됩니다.
73
73
  - **이점**: 공급망 안전성↑, 버전 고정 가능, 사내망/Windows 호환성↑, 제거 단순화.
74
- - 등록 실패 시 사용자에게 수동 명령 `/plugin install Yeachan-Heo/oh-my-claudecode` 안내만 제공합니다 (npx 폴백 없음).
74
+ - 등록 실패 시 사용자에게 수동 명령 `/plugin install SoInKyu/oh-my-claudecode` 안내만 제공합니다 (npx 폴백 없음).
75
75
  - **명령어 이름 정리**(예정): `pai env setup → add`, `doctor → check`, `evaluate → grade`, `validate → test`, `pipeline → run`, `/pai install → /pai add`. v0.12에서 alias 도입, v0.13에서 구 명령어 제거 예정.
76
76
 
77
77
  ---
package/dist/bin/pai.js CHANGED
@@ -716,6 +716,7 @@ async function interactiveInterview(analysis, _cwd, projectName) {
716
716
  console.log(` ${colors.success("\u2713")} /pai \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC`);
717
717
  if (extraTools.length > 0) {
718
718
  console.log("");
719
+ if (extraTools.includes("omc")) console.log(` ${colors.accent("+")} OMC (oh-my-claudecode) ${colors.dim("\u2500 AI \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158")}`);
719
720
  if (extraTools.includes("vercel")) console.log(` ${colors.accent("+")} Vercel \uC790\uB3D9 \uBC30\uD3EC`);
720
721
  if (extraTools.includes("supabase")) console.log(` ${colors.accent("+")} Supabase \uB370\uC774\uD130\uBCA0\uC774\uC2A4`);
721
722
  if (extraTools.includes("gstack")) console.log(` ${colors.accent("+")} gstack \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654`);
@@ -2210,6 +2211,34 @@ async function provisionOMC(ctx) {
2210
2211
  "- (\uC5EC\uAE30\uC5D0 \uD575\uC2EC \uBE44\uC988\uB2C8\uC2A4 \uB85C\uC9C1\uC744 \uAE30\uC220)"
2211
2212
  ].join("\n") + "\n");
2212
2213
  }
2214
+ const omcDir = join4(ctx.cwd, ".omc");
2215
+ await fs7.ensureDir(omcDir);
2216
+ await fs7.ensureDir(join4(omcDir, "state"));
2217
+ await fs7.ensureDir(join4(omcDir, "sessions"));
2218
+ await fs7.ensureDir(join4(omcDir, "logs"));
2219
+ const omcReadme = join4(omcDir, "README.md");
2220
+ if (!await fs7.pathExists(omcReadme)) {
2221
+ await fs7.writeFile(omcReadme, [
2222
+ "# .omc/ \u2014 OMC \uB7F0\uD0C0\uC784 \uB514\uB809\uD1A0\uB9AC",
2223
+ "",
2224
+ "\uC774 \uD3F4\uB354\uB294 OMC(oh-my-claudecode)\uAC00 Claude Code \uC2E4\uD589 \uC911 \uC0AC\uC6A9\uD558\uB294",
2225
+ "\uC0C1\uD0DC/\uC138\uC158/\uB85C\uADF8 \uB370\uC774\uD130\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4.",
2226
+ "",
2227
+ "- `state/` \u2014 \uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uC0C1\uD0DC",
2228
+ "- `sessions/` \u2014 \uC138\uC158 \uAE30\uB85D",
2229
+ "- `logs/` \u2014 \uC2E4\uD589 \uB85C\uADF8",
2230
+ "",
2231
+ "\uAC01 \uD558\uC704 \uACBD\uB85C\uB294 `.gitignore`\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. (\uC0C1\uC704 \uB514\uB809\uD1A0\uB9AC\uC640",
2232
+ "\uC774 README\uB294 \uCD94\uC801\uB428)",
2233
+ "",
2234
+ "## \uCC38\uACE0",
2235
+ "",
2236
+ "- OMC \uC800\uC7A5\uC18C: https://github.com/SoInKyu/oh-my-claudecode",
2237
+ "- \uC790\uB3D9 \uC124\uCE58: Claude Code \uAE30\uB3D9 \uC2DC `~/.claude/settings.json` \uB4F1\uB85D\uC5D0",
2238
+ " \uB530\uB77C \uD50C\uB7EC\uADF8\uC778\uC774 \uC790\uB3D9 \uB85C\uB4DC\uB429\uB2C8\uB2E4.",
2239
+ ""
2240
+ ].join("\n") + "\n");
2241
+ }
2213
2242
  }
2214
2243
  async function provisionGstack(ctx) {
2215
2244
  await fs7.ensureDir(join4(ctx.cwd, ".pai"));
@@ -3316,8 +3345,8 @@ var init_environment = __esm({
3316
3345
  "tests/",
3317
3346
  "public/",
3318
3347
  "docs/openspec.md",
3319
- ".pai/omc.md",
3320
3348
  ".pai/roboco.json",
3349
+ ...interview.extraTools.includes("omc") ? [".pai/omc.md", ".omc/"] : [],
3321
3350
  ...interview.extraTools.includes("vercel") ? ["vercel.json"] : [],
3322
3351
  ...interview.extraTools.includes("supabase") ? ["supabase/config.toml"] : [],
3323
3352
  ...interview.extraTools.includes("gstack") ? [".pai/gstack.json"] : [],
@@ -3478,13 +3507,15 @@ var init_detector = __esm({
3478
3507
  label: "roboco (AI \uC9C4\uB2E8)",
3479
3508
  description: "\uC124\uCE58 \uC0C1\uD0DC \uD3C9\uAC00 \uBC0F AI \uC900\uBE44\uB3C4 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131",
3480
3509
  modes: ["prototype", "poc", "production"],
3481
- required: false
3510
+ required: false,
3511
+ url: "https://github.com/SoInKyu/roboco-cli"
3482
3512
  },
3483
3513
  omc: {
3484
- label: "OMC (Object Model Context)",
3485
- description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 \u2014 AI\uAC00 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uB294 \uAD6C\uC870",
3514
+ label: "OMC (oh-my-claudecode)",
3515
+ description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 + Claude Code \uBA40\uD2F0 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158",
3486
3516
  modes: ["poc", "production"],
3487
- required: false
3517
+ required: false,
3518
+ url: "https://github.com/SoInKyu/oh-my-claudecode"
3488
3519
  },
3489
3520
  vercel: {
3490
3521
  label: "Vercel \uBC30\uD3EC \uC5F0\uB3D9",
@@ -3496,7 +3527,8 @@ var init_detector = __esm({
3496
3527
  label: "gstack (QA / \uD488\uC9C8\uAD00\uB9AC)",
3497
3528
  description: "\uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654 \uBC0F \uD488\uC9C8 \uAE30\uC900 \uC124\uC815",
3498
3529
  modes: ["production"],
3499
- required: false
3530
+ required: false,
3531
+ url: "https://github.com/SoInKyu/gstack"
3500
3532
  },
3501
3533
  harness: {
3502
3534
  label: "Harness Engineering (\uAC80\uC99D \uC790\uB3D9\uD654)",
@@ -4320,7 +4352,7 @@ var init_claude_settings = __esm({
4320
4352
  "use strict";
4321
4353
  init_platform();
4322
4354
  DEFAULT_MARKETPLACE_ID = "omc";
4323
- DEFAULT_MARKETPLACE_URL = "https://github.com/Yeachan-Heo/oh-my-claudecode.git";
4355
+ DEFAULT_MARKETPLACE_URL = "https://github.com/SoInKyu/oh-my-claudecode.git";
4324
4356
  DEFAULT_PLUGIN_ID = "oh-my-claudecode@omc";
4325
4357
  ClaudeSettingsError = class extends Error {
4326
4358
  constructor(message, backupPath) {
@@ -4801,6 +4833,9 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4801
4833
  console.log(` ${chalk8.cyan("OpenSpec+OMC")} \uC124\uACC4\uB3C4 & \uC9C0\uD615\uB3C4 \u2014 \uBB34\uC5C7\uC744, \uC5B4\uB514\uC5D0 \uB9CC\uB4E4\uC9C0 \uC815\uC758`);
4802
4834
  console.log(` ${chalk8.cyan("RoboCo CLI")} \uD604\uC7A5 \uC18C\uC7A5 \u2014 \uC804\uCCB4 \uACF5\uC815 \uAD00\uB9AC, AI\uC5D0\uAC8C \uC791\uC5C5 \uC9C0\uC2DC`);
4803
4835
  console.log(` ${chalk8.cyan("Vibe-Ready")} \uD488\uC9C8 \uAC80\uC218 \u2014 AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00`);
4836
+ if (extraTools.includes("omc")) {
4837
+ console.log(` ${chalk8.cyan("OMC")} AI \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158 (oh-my-claudecode)`);
4838
+ }
4804
4839
  if (extraTools.includes("gstack")) {
4805
4840
  console.log(` ${chalk8.cyan("gstack")} \uD45C\uC900 \uC790\uC7AC & \uACF5\uBC95 \u2014 \uAE30\uC220 \uC2A4\uD0DD \uD45C\uC900 \uAD6C\uC870 \uC81C\uACF5`);
4806
4841
  }
@@ -4824,7 +4859,8 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4824
4859
  { label: "PRD \uD15C\uD50C\uB9BF", path: "docs/openspec.md" },
4825
4860
  { label: "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815", path: ".pai/config.json" },
4826
4861
  { label: "Claude \uC124\uC815", path: ".claude/settings.json" },
4827
- { label: "\uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC", path: ".claude/skills/pai/SKILL.md" }
4862
+ { label: "\uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC", path: ".claude/skills/pai/SKILL.md" },
4863
+ ...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
4828
4864
  ];
4829
4865
  for (const check of checks) {
4830
4866
  const exists = await fs15.pathExists(join11(projectDir, check.path));
@@ -4838,9 +4874,9 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4838
4874
  default: true
4839
4875
  }]);
4840
4876
  if (runEval) {
4841
- const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
4877
+ const { createSpinner: createSpinner3 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
4842
4878
  await sleep2(2e3);
4843
- const spinner = createSpinner2("\uBC14\uC774\uBE0C\uCF54\uB529 \uC0C1\uD0DC \uCCB4\uD06C \uC911...");
4879
+ const spinner = createSpinner3("\uBC14\uC774\uBE0C\uCF54\uB529 \uC0C1\uD0DC \uCCB4\uD06C \uC911...");
4844
4880
  try {
4845
4881
  const { analyzeRepository: analyzeRepository2 } = await Promise.resolve().then(() => (init_analyzer2(), analyzer_exports2));
4846
4882
  const { computeResult: computeResult2 } = await Promise.resolve().then(() => (init_scorer(), scorer_exports));
@@ -4879,97 +4915,68 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4879
4915
  success("\uC774\uC81C Claude Code\uC640 \uD568\uAED8 PRD \uBB38\uC11C\uB97C \uC791\uC131\uD558\uC138\uC694.");
4880
4916
  console.log("");
4881
4917
  console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4918
+ console.log("");
4919
+ const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
4920
+ const omcSpinner = createSpinner2("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC911...");
4921
+ try {
4922
+ const { enableOmcPlugin: enableOmcPlugin2 } = await Promise.resolve().then(() => (init_claude_settings(), claude_settings_exports));
4923
+ const result = await enableOmcPlugin2();
4924
+ if (result.action === "already-enabled") {
4925
+ omcSpinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uC774\uBBF8 \uB4F1\uB85D\uB428");
4926
+ } else if (result.action === "created") {
4927
+ omcSpinner.succeed("Claude \uC124\uC815 \uC2E0\uADDC \uC0DD\uC131 + OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC644\uB8CC");
4928
+ } else {
4929
+ omcSpinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC644\uB8CC");
4930
+ }
4931
+ hint("Claude Code \uAE30\uB3D9 \uC2DC \uD50C\uB7EC\uADF8\uC778\uC774 \uC790\uB3D9 \uC124\uCE58\uB429\uB2C8\uB2E4.");
4932
+ if (result.backupPath) {
4933
+ hint(`\uC124\uC815 \uBC31\uC5C5: ${result.backupPath}`);
4934
+ }
4935
+ } catch (err) {
4936
+ const msg = err instanceof Error ? err.message : String(err);
4937
+ omcSpinner.warn(`OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC2E4\uD328 \u2014 ${msg}`);
4938
+ hint("Claude Code \uAE30\uB3D9 \uD6C4 \uC9C1\uC811 \uB4F1\uB85D\uD558\uC138\uC694:");
4939
+ hint(" /plugin install SoInKyu/oh-my-claudecode");
4940
+ }
4882
4941
  const { getShellRcPath: getShellRcPath2, hasYoloAlias: checkYolo } = await Promise.resolve().then(() => (init_platform(), platform_exports));
4883
4942
  const shellRc = getShellRcPath2();
4884
- let hasYoloAliasSet = false;
4943
+ let yoloAlreadyAliased = false;
4885
4944
  try {
4886
4945
  const rcContent = await fs15.readFile(shellRc, "utf8");
4887
- hasYoloAliasSet = checkYolo(rcContent);
4946
+ yoloAlreadyAliased = checkYolo(rcContent);
4888
4947
  } catch {
4889
4948
  }
4890
- let useYolo = false;
4891
- if (!hasYoloAliasSet) {
4892
- console.log("");
4893
- const { mode } = await inquirer.prompt([{
4894
- type: "list",
4895
- name: "mode",
4896
- message: "Claude Code \uC2E4\uD589 \uBAA8\uB4DC\uB97C \uC124\uC815\uD569\uB2C8\uB2E4:",
4897
- choices: [
4898
- { name: `\uC77C\uBC18 \uBAA8\uB4DC ${colors.dim("\u2500 \uAD8C\uD55C \uD655\uC778 \uD6C4 \uC2E4\uD589")}`, value: "normal" },
4899
- { name: `claude-YOLO mode ${colors.dim("\u2500 \uAD8C\uD55C \uD655\uC778 \uC5C6\uC774 \uC790\uB3D9 \uC2E4\uD589 (alias \uC124\uC815)")}`, value: "yolo" }
4900
- ]
4901
- }]);
4902
- if (mode === "yolo") {
4903
- useYolo = true;
4904
- const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
4905
- const aliasLine = getYoloAliasLine2();
4906
- try {
4907
- const rcContent = await fs15.readFile(shellRc, "utf8").catch(() => "");
4908
- if (!rcContent.includes("claude-yolo")) {
4909
- await fs15.ensureDir(join11(shellRc, ".."));
4910
- await fs15.appendFile(shellRc, `
4911
- # PAI \u2014 claude-YOLO mode
4912
- ${aliasLine}
4913
- `);
4914
- await sleep2(500);
4915
- success("claude-yolo alias \uC124\uC815 \uC644\uB8CC");
4916
- hint("\uC0C8 \uD130\uBBF8\uB110\uC5D0\uC11C claude-yolo \uB85C \uC2E4\uD589 \uAC00\uB2A5");
4917
- }
4918
- } catch {
4919
- }
4920
- }
4921
- } else {
4922
- useYolo = true;
4923
- }
4924
4949
  console.log("");
4925
- const claudeCmd = useYolo ? "claude --dangerously-skip-permissions" : "claude";
4950
+ console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4926
4951
  const { launch } = await inquirer.prompt([{
4927
4952
  type: "list",
4928
4953
  name: "launch",
4929
- message: "Claude Code\uB97C \uC2DC\uC791\uD560\uAE4C\uC694?",
4954
+ message: "Claude Code\uB97C \uC5B4\uB5A4 \uBAA8\uB4DC\uB85C \uC2DC\uC791\uD560\uAE4C\uC694?",
4930
4955
  choices: [
4931
- { name: `OMC + Claude \uC2DC\uC791 ${colors.dim("\u2500 oh-my-claudecode \uC124\uC815 \uD6C4 \uC2DC\uC791")}`, value: "omc" },
4932
- { name: `Claude \uBC14\uB85C \uC2DC\uC791 ${colors.dim(`\u2500 ${claudeCmd}`)}`, value: "claude" },
4933
- { name: chalk8.gray("\uB098\uC911\uC5D0 \uC9C1\uC811 \uC2E4\uD589"), value: "none" }
4956
+ { name: `claude ${colors.dim("\u2500 \uAD8C\uD55C \uD655\uC778 \uD6C4 \uC2E4\uD589 (\uC77C\uBC18 \uBAA8\uB4DC)")}`, value: "claude" },
4957
+ { name: `claude YOLO ${colors.dim("\u2500 \uAD8C\uD55C \uD655\uC778 \uC5C6\uC774 \uC790\uB3D9 \uC2E4\uD589 (--dangerously-skip-permissions)")}`, value: "yolo" }
4934
4958
  ]
4935
4959
  }]);
4936
- if (launch === "none") {
4937
- console.log("");
4938
- hint("claude \uB97C \uC785\uB825\uD558\uBA74 \uC2DC\uC791\uB429\uB2C8\uB2E4.");
4939
- console.log("");
4940
- if (!isCurrentDir) {
4941
- const { spawnSubshell: spawnSubshell2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
4942
- spawnSubshell2(projectDir);
4943
- }
4944
- return;
4945
- }
4946
- if (launch === "omc") {
4947
- console.log("");
4948
- const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
4949
- const spinner = createSpinner2("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC911...");
4960
+ const useYolo = launch === "yolo";
4961
+ if (useYolo && !yoloAlreadyAliased) {
4950
4962
  try {
4951
- const { enableOmcPlugin: enableOmcPlugin2, ClaudeSettingsError: ClaudeSettingsError2 } = await Promise.resolve().then(() => (init_claude_settings(), claude_settings_exports));
4952
- const result = await enableOmcPlugin2();
4953
- if (result.action === "already-enabled") {
4954
- spinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uC774\uBBF8 \uB4F1\uB85D\uB428");
4955
- } else if (result.action === "created") {
4956
- spinner.succeed("Claude \uC124\uC815 \uC2E0\uADDC \uC0DD\uC131 + OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC644\uB8CC");
4957
- } else {
4958
- spinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC644\uB8CC");
4959
- }
4960
- hint("Claude Code \uAE30\uB3D9 \uC2DC \uD50C\uB7EC\uADF8\uC778\uC774 \uC790\uB3D9 \uC124\uCE58\uB429\uB2C8\uB2E4.");
4961
- if (result.backupPath) {
4962
- hint(`\uC124\uC815 \uBC31\uC5C5: ${result.backupPath}`);
4963
+ const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
4964
+ const aliasLine = getYoloAliasLine2();
4965
+ const rcContent = await fs15.readFile(shellRc, "utf8").catch(() => "");
4966
+ if (!rcContent.includes("claude-yolo")) {
4967
+ await fs15.ensureDir(join11(shellRc, ".."));
4968
+ await fs15.appendFile(shellRc, `
4969
+ # PAI \u2014 claude-YOLO mode
4970
+ ${aliasLine}
4971
+ `);
4972
+ await sleep2(300);
4973
+ success("claude-yolo alias \uC124\uC815 \uC644\uB8CC");
4974
+ hint("\uC0C8 \uD130\uBBF8\uB110\uC5D0\uC11C claude-yolo \uB85C \uC2E4\uD589 \uAC00\uB2A5");
4963
4975
  }
4964
- void ClaudeSettingsError2;
4965
- } catch (err) {
4966
- const msg = err instanceof Error ? err.message : String(err);
4967
- spinner.warn(`OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC2E4\uD328 \u2014 ${msg}`);
4968
- hint("Claude Code \uAE30\uB3D9 \uD6C4 \uC9C1\uC811 \uB4F1\uB85D\uD558\uC138\uC694:");
4969
- hint(" /plugin install Yeachan-Heo/oh-my-claudecode");
4976
+ } catch {
4970
4977
  }
4971
4978
  }
4972
- const cmd = claudeCmd;
4979
+ const cmd = useYolo ? "claude --dangerously-skip-permissions" : "claude";
4973
4980
  console.log("");
4974
4981
  if (!isCurrentDir) {
4975
4982
  console.log(colors.dim(` \u2192 cd ${projectName}`));