@brewnet/cli 0.0.1 → 0.0.2

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.
Files changed (33) hide show
  1. package/dist/admin-server-UODBPGWR.js +16 -0
  2. package/dist/app-manager-FIHPVUP7.js +51 -0
  3. package/dist/{boilerplate-manager-P6QYUU7Q.js → boilerplate-manager-WEFTHL2O.js} +3 -3
  4. package/dist/{chunk-DH2VK3YI.js → chunk-54WFZCU6.js} +2 -2
  5. package/dist/{chunk-4TJMJZMO.js → chunk-AXSHZEB3.js} +63 -1
  6. package/dist/{chunk-4TJMJZMO.js.map → chunk-AXSHZEB3.js.map} +1 -1
  7. package/dist/chunk-FZZ3HP2G.js +1151 -0
  8. package/dist/chunk-FZZ3HP2G.js.map +1 -0
  9. package/dist/chunk-JIPAYMOA.js +58 -0
  10. package/dist/chunk-JIPAYMOA.js.map +1 -0
  11. package/dist/{chunk-SIXBB6JU.js → chunk-Q6UUZR2V.js} +238 -1171
  12. package/dist/chunk-Q6UUZR2V.js.map +1 -0
  13. package/dist/{chunk-2VWMDHGI.js → chunk-YAYXULLO.js} +9 -61
  14. package/dist/chunk-YAYXULLO.js.map +1 -0
  15. package/dist/{chunk-JFPHGZ6Z.js → chunk-YXFDB5YX.js} +17 -2
  16. package/dist/chunk-YXFDB5YX.js.map +1 -0
  17. package/dist/{cloudflare-client-TFT6VCXF.js → cloudflare-client-F2TGQXGS.js} +2 -2
  18. package/dist/{compose-generator-O7GSIJ2S.js → compose-generator-OFJ2YWMB.js} +4 -2
  19. package/dist/compose-generator-OFJ2YWMB.js.map +1 -0
  20. package/dist/index.js +122 -168
  21. package/dist/index.js.map +1 -1
  22. package/dist/services/admin-daemon.js +6 -4
  23. package/dist/services/admin-daemon.js.map +1 -1
  24. package/package.json +1 -1
  25. package/dist/admin-server-DQVIEHV3.js +0 -14
  26. package/dist/chunk-2VWMDHGI.js.map +0 -1
  27. package/dist/chunk-JFPHGZ6Z.js.map +0 -1
  28. package/dist/chunk-SIXBB6JU.js.map +0 -1
  29. /package/dist/{admin-server-DQVIEHV3.js.map → admin-server-UODBPGWR.js.map} +0 -0
  30. /package/dist/{boilerplate-manager-P6QYUU7Q.js.map → app-manager-FIHPVUP7.js.map} +0 -0
  31. /package/dist/{cloudflare-client-TFT6VCXF.js.map → boilerplate-manager-WEFTHL2O.js.map} +0 -0
  32. /package/dist/{chunk-DH2VK3YI.js.map → chunk-54WFZCU6.js.map} +0 -0
  33. /package/dist/{compose-generator-O7GSIJ2S.js.map → cloudflare-client-F2TGQXGS.js.map} +0 -0
package/dist/index.js CHANGED
@@ -11,7 +11,8 @@ import {
11
11
  queryLogs,
12
12
  removeService,
13
13
  restoreBackup
14
- } from "./chunk-2VWMDHGI.js";
14
+ } from "./chunk-YAYXULLO.js";
15
+ import "./chunk-JIPAYMOA.js";
15
16
  import {
16
17
  FRONTEND_REGISTRY,
17
18
  LANGUAGE_REGISTRY,
@@ -28,7 +29,7 @@ import {
28
29
  getTunnelHealth,
29
30
  getZones,
30
31
  verifyToken
31
- } from "./chunk-JFPHGZ6Z.js";
32
+ } from "./chunk-YXFDB5YX.js";
32
33
  import {
33
34
  cloneStack,
34
35
  generateEnv,
@@ -36,11 +37,11 @@ import {
36
37
  reinitGit,
37
38
  startContainers,
38
39
  verifyEndpoints
39
- } from "./chunk-DH2VK3YI.js";
40
+ } from "./chunk-54WFZCU6.js";
40
41
  import {
41
42
  composeConfigToYaml,
42
43
  generateComposeConfig
43
- } from "./chunk-4TJMJZMO.js";
44
+ } from "./chunk-AXSHZEB3.js";
44
45
  import {
45
46
  applyFullInstallDefaults,
46
47
  applyMinimalInstallDefaults,
@@ -70,7 +71,7 @@ import {
70
71
  import { Command } from "commander";
71
72
 
72
73
  // src/commands/init.ts
73
- import { existsSync as existsSync7 } from "fs";
74
+ import { existsSync as existsSync6 } from "fs";
74
75
  import { resolve } from "path";
75
76
  import { createRequire } from "module";
76
77
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -83,8 +84,7 @@ import chalk from "chalk";
83
84
  var AUTO_PROPAGATED_SERVICES = [
84
85
  "Nextcloud (File Server)",
85
86
  "MinIO (Object Storage)",
86
- "pgAdmin (DB Admin UI)",
87
- "SSH Server (OpenSSH)"
87
+ "pgAdmin (DB Admin UI)"
88
88
  ];
89
89
  var MANUAL_SETUP_SERVICES = [
90
90
  "Gitea (Git server)",
@@ -1755,35 +1755,7 @@ async function runServerComponentsStep(state) {
1755
1755
  next.servers.media.enabled = mediaEnabled;
1756
1756
  next.servers.media.services = mediaEnabled ? ["jellyfin"] : [];
1757
1757
  console.log();
1758
- console.log(chalk5.bold(" SSH Server"));
1759
- console.log(chalk5.dim(" \uD130\uBBF8\uB110\uB85C \uC11C\uBC84\uC5D0 \uC6D0\uACA9 \uC811\uC18D\uD558\uAC70\uB098 SFTP\uB85C \uD30C\uC77C\uC744 \uC804\uC1A1\uD558\uB294 \uBCF4\uC548 \uCC44\uB110"));
1760
- console.log();
1761
- const sshEnabled = await confirm2({
1762
- message: "Enable SSH Server?",
1763
- default: next.servers.sshServer.enabled
1764
- });
1765
- next.servers.sshServer.enabled = sshEnabled;
1766
- if (sshEnabled) {
1767
- next.servers.sshServer.port = 2222;
1768
- console.log(chalk5.dim(" SSH port: 2222"));
1769
- const passwordAuth = await confirm2({
1770
- message: "Allow password authentication? (key-only auth recommended)",
1771
- default: next.servers.sshServer.passwordAuth ?? false
1772
- });
1773
- next.servers.sshServer.passwordAuth = passwordAuth;
1774
- if (shouldAutoSuggestSftp(next)) {
1775
- console.log(
1776
- chalk5.dim(" SFTP auto-enabled (File Server or Media Server is active)")
1777
- );
1778
- next.servers.sshServer.sftp = true;
1779
- } else {
1780
- const sftpEnabled = await confirm2({
1781
- message: "Enable SFTP subsystem?",
1782
- default: next.servers.sshServer.sftp
1783
- });
1784
- next.servers.sshServer.sftp = sftpEnabled;
1785
- }
1786
- }
1758
+ next.servers.sshServer.enabled = false;
1787
1759
  console.log();
1788
1760
  const finalState = applyComponentRules(next);
1789
1761
  const resources = estimateResources(finalState);
@@ -2240,41 +2212,25 @@ async function runDomainNetworkStep(state) {
2240
2212
  description: "\uACC4\uC815 \uC5C6\uC774 \uBC14\uB85C \uC2DC\uC791. \uB2E8, \uC11C\uBC84 \uC7AC\uC2DC\uC791 \uC2DC URL\uC774 \uBCC0\uACBD\uB429\uB2C8\uB2E4."
2241
2213
  },
2242
2214
  {
2243
- name: "2. Named Tunnel \u2014 \uAE30\uC874 Cloudflare \uB3C4\uBA54\uC778 \uC5F0\uACB0 (\uC601\uAD6C URL)",
2244
- value: "2-named-existing",
2245
- description: "Cloudflare \uACC4\uC815 + \uB3C4\uBA54\uC778\uC774 \uC774\uBBF8 \uC788\uB294 \uACBD\uC6B0. API \uD1A0\uD070 1\uD68C \uC785\uB825."
2215
+ name: "2. Named Tunnel (Cloudflare \uACC4\uC815 \uD544\uC694, \uC601\uAD6C URL)",
2216
+ value: "2-named",
2217
+ description: "Cloudflare \uACC4\uC815 + API \uD1A0\uD070 \uD544\uC694. \uB3C4\uBA54\uC778 \uC720\uBB34\uC5D0 \uB530\uB77C \uC124\uC815\uC774 \uB2EC\uB77C\uC9D1\uB2C8\uB2E4."
2246
2218
  },
2247
2219
  {
2248
- name: "3. Named Tunnel \u2014 \uB3C4\uBA54\uC778 \uBA3C\uC800 \uAD6C\uC785 \uD6C4 \uC5F0\uACB0 (\uC548\uB0B4 \uD3EC\uD568)",
2249
- value: "3-named-buy",
2250
- description: "\uB3C4\uBA54\uC778 \uAD6C\uC785 \uAC00\uC774\uB4DC \uC81C\uACF5. \uC784\uC2DC Quick Tunnel\uB85C \uC989\uC2DC \uC811\uADFC \uAC00\uB2A5."
2251
- },
2252
- {
2253
- name: "4. Named Tunnel\uB9CC \uC0DD\uC131 \u2014 \uB3C4\uBA54\uC778\uC740 \uB098\uC911\uC5D0 \uC5F0\uACB0",
2254
- value: "4-named-only",
2255
- description: "\uD130\uB110\uB9CC \uC900\uBE44. `brewnet domain connect`\uB85C \uB3C4\uBA54\uC778 \uCD94\uAC00 \uAC00\uB2A5."
2256
- },
2257
- {
2258
- name: "5. \uB85C\uCEEC \uC804\uC6A9 (\uC678\uBD80 \uC811\uADFC \uC5C6\uC74C)",
2259
- value: "5-local",
2220
+ name: "3. \uB85C\uCEEC \uC804\uC6A9 (\uC678\uBD80 \uC811\uADFC \uC5C6\uC74C)",
2221
+ value: "3-local",
2260
2222
  description: "\uB0B4\uBD80 \uB124\uD2B8\uC6CC\uD06C\uC5D0\uC11C\uB9CC \uC811\uADFC. brewnet.local \uB3C4\uBA54\uC778 \uC0AC\uC6A9."
2261
2223
  }
2262
2224
  ]
2263
2225
  });
2264
2226
  console.log();
2265
- if (scenario === "5-local") {
2227
+ if (scenario === "3-local") {
2266
2228
  return runLocalScenario(next);
2267
2229
  }
2268
2230
  if (scenario === "1-quick") {
2269
2231
  return runQuickTunnelScenario(next, tunnelLogger);
2270
2232
  }
2271
- if (scenario === "2-named-existing") {
2272
- return runNamedTunnelWithDomainScenario(next, tunnelLogger);
2273
- }
2274
- if (scenario === "3-named-buy") {
2275
- return runGuidedDomainPurchaseScenario(next, tunnelLogger);
2276
- }
2277
- return runNamedTunnelOnlyScenario(next, tunnelLogger);
2233
+ return runUnifiedNamedTunnelScenario(next, tunnelLogger);
2278
2234
  }
2279
2235
  function runLocalScenario(next) {
2280
2236
  next.domain.provider = "local";
@@ -2552,63 +2508,56 @@ async function runNamedTunnelApiFlow(next, tunnelLogger, includeDns) {
2552
2508
  next.domain.cloudflare.apiToken = "";
2553
2509
  return next;
2554
2510
  }
2555
- async function runNamedTunnelWithDomainScenario(next, tunnelLogger) {
2511
+ async function runUnifiedNamedTunnelScenario(next, tunnelLogger) {
2556
2512
  next.domain.provider = "tunnel";
2557
2513
  next.domain.ssl = "cloudflare";
2558
2514
  next.domain.cloudflare.enabled = true;
2559
2515
  next.domain.cloudflare.tunnelMode = "named";
2516
+ let qtManager = null;
2560
2517
  try {
2561
- const updated = await runNamedTunnelApiFlow(next, tunnelLogger, true);
2562
- printNetworkSummary(updated);
2563
- return updated;
2564
- } catch (err) {
2565
- console.log(chalk7.red(` \uC624\uB958: ${err instanceof Error ? err.message : String(err)}`));
2518
+ const hasDomain = await confirm4({
2519
+ message: "Cloudflare\uC5D0 \uB4F1\uB85D\uB41C \uB3C4\uBA54\uC778\uC774 \uC774\uBBF8 \uC788\uC73C\uC2E0\uAC00\uC694?",
2520
+ default: true
2521
+ });
2566
2522
  console.log();
2567
- console.log(chalk7.yellow(" Local \uBAA8\uB4DC\uB85C \uC804\uD658\uD569\uB2C8\uB2E4."));
2568
- return runLocalScenario(next);
2569
- }
2570
- }
2571
- async function runGuidedDomainPurchaseScenario(next, tunnelLogger) {
2572
- console.log(chalk7.bold(" \uB3C4\uBA54\uC778 \uAD6C\uC785 \uC548\uB0B4"));
2573
- console.log();
2574
- console.log(chalk7.bold.white(" Cloudflare\uC5D0\uC11C \uB3C4\uBA54\uC778\uC744 \uB4F1\uB85D\uD558\uB294 \uBC29\uBC95:"));
2575
- console.log();
2576
- console.log(chalk7.dim(" 1. https://domains.cloudflare.com \u2192 \uB85C\uADF8\uC778 \uB610\uB294 \uACC4\uC815 \uC0DD\uC131"));
2577
- console.log(chalk7.dim(" 2. \uB3C4\uBA54\uC778 \uAC80\uC0C9 \u2192 \uB4F1\uB85D (\uC5F0 $8~15 \uC218\uC900, .com \uAE30\uC900)"));
2578
- console.log(chalk7.dim(" 3. \uB3C4\uBA54\uC778\uC774 Cloudflare \uB124\uC784\uC11C\uBC84\uB85C \uC790\uB3D9 \uC124\uC815\uB429\uB2C8\uB2E4"));
2579
- console.log(chalk7.dim(" 4. \uB4F1\uB85D \uC644\uB8CC\uAE4C\uC9C0 1~5\uBD84 \uC18C\uC694"));
2580
- console.log();
2581
- const useBridge = await confirm4({
2582
- message: "Quick Tunnel\uB85C \uC784\uC2DC \uC811\uADFC\uC744 \uC2DC\uC791\uD558\uACA0\uC2B5\uB2C8\uAE4C? (\uB3C4\uBA54\uC778 \uC900\uBE44 \uC911\uC5D0\uB3C4 \uC11C\uBE44\uC2A4\uC5D0 \uC811\uADFC \uAC00\uB2A5)",
2583
- default: true
2584
- });
2585
- console.log();
2586
- let qtManager = null;
2587
- if (useBridge) {
2588
- const spinner = ora3("Quick Tunnel \uC2DC\uC791 \uC911...").start();
2589
- try {
2590
- qtManager = new QuickTunnelManager(tunnelLogger);
2591
- const url = await qtManager.start();
2592
- spinner.succeed(chalk7.green(`\uC784\uC2DC URL: ${url}`));
2593
- console.log(chalk7.dim(" \uB3C4\uBA54\uC778 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uBA74 \uC544\uB798\uC5D0\uC11C Enter\uB97C \uB20C\uB7EC Named Tunnel\uB85C \uC804\uD658\uD569\uB2C8\uB2E4."));
2523
+ if (!hasDomain) {
2524
+ console.log(chalk7.bold(" \uB3C4\uBA54\uC778 \uAD6C\uC785 \uC548\uB0B4"));
2594
2525
  console.log();
2595
- } catch (err) {
2596
- spinner.fail(chalk7.yellow(`Quick Tunnel \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`));
2526
+ console.log(chalk7.bold.white(" Cloudflare\uC5D0\uC11C \uB3C4\uBA54\uC778\uC744 \uB4F1\uB85D\uD558\uB294 \uBC29\uBC95:"));
2527
+ console.log();
2528
+ console.log(chalk7.dim(" 1. https://domains.cloudflare.com \u2192 \uB85C\uADF8\uC778 \uB610\uB294 \uACC4\uC815 \uC0DD\uC131"));
2529
+ console.log(chalk7.dim(" 2. \uB3C4\uBA54\uC778 \uAC80\uC0C9 \u2192 \uB4F1\uB85D (\uC5F0 $8~15 \uC218\uC900, .com \uAE30\uC900)"));
2530
+ console.log(chalk7.dim(" 3. \uB3C4\uBA54\uC778\uC774 Cloudflare \uB124\uC784\uC11C\uBC84\uB85C \uC790\uB3D9 \uC124\uC815\uB429\uB2C8\uB2E4"));
2531
+ console.log(chalk7.dim(" 4. \uB4F1\uB85D \uC644\uB8CC\uAE4C\uC9C0 1~5\uBD84 \uC18C\uC694"));
2532
+ console.log();
2533
+ const useBridge = await confirm4({
2534
+ message: "Quick Tunnel\uB85C \uC784\uC2DC \uC811\uADFC\uC744 \uC2DC\uC791\uD558\uACA0\uC2B5\uB2C8\uAE4C? (\uB3C4\uBA54\uC778 \uC900\uBE44 \uC911\uC5D0\uB3C4 \uC11C\uBE44\uC2A4\uC5D0 \uC811\uADFC \uAC00\uB2A5)",
2535
+ default: true
2536
+ });
2537
+ console.log();
2538
+ if (useBridge) {
2539
+ const spinner = ora3("Quick Tunnel \uC2DC\uC791 \uC911...").start();
2540
+ try {
2541
+ qtManager = new QuickTunnelManager(tunnelLogger);
2542
+ const url = await qtManager.start();
2543
+ spinner.succeed(chalk7.green(`\uC784\uC2DC URL: ${url}`));
2544
+ console.log(chalk7.dim(" \uB3C4\uBA54\uC778 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uBA74 \uC544\uB798\uC5D0\uC11C Enter\uB97C \uB20C\uB7EC Named Tunnel\uB85C \uC804\uD658\uD569\uB2C8\uB2E4."));
2545
+ console.log();
2546
+ } catch (err) {
2547
+ spinner.fail(chalk7.yellow(`Quick Tunnel \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`));
2548
+ console.log();
2549
+ qtManager = null;
2550
+ }
2551
+ }
2552
+ await input5({
2553
+ message: "\uB3C4\uBA54\uC778 \uC124\uC815 \uC644\uB8CC \uD6C4 Enter\uB97C \uB204\uB974\uC138\uC694",
2554
+ default: ""
2555
+ });
2597
2556
  console.log();
2598
- qtManager = null;
2557
+ next.domain.cloudflare.zoneId = "";
2558
+ next.domain.cloudflare.zoneName = "";
2599
2559
  }
2600
- }
2601
- await input5({
2602
- message: "\uB3C4\uBA54\uC778 \uC124\uC815 \uC644\uB8CC \uD6C4 Enter\uB97C \uB204\uB974\uC138\uC694",
2603
- default: ""
2604
- });
2605
- console.log();
2606
- next.domain.provider = "tunnel";
2607
- next.domain.ssl = "cloudflare";
2608
- next.domain.cloudflare.enabled = true;
2609
- next.domain.cloudflare.tunnelMode = "named";
2610
- try {
2611
- const updated = await runNamedTunnelApiFlow(next, tunnelLogger, true);
2560
+ const updated = await runNamedTunnelApiFlow(next, tunnelLogger, hasDomain);
2612
2561
  if (qtManager) {
2613
2562
  const stopSpinner = ora3("\uC784\uC2DC Quick Tunnel \uC911\uC9C0 \uC911...").start();
2614
2563
  try {
@@ -2619,11 +2568,16 @@ async function runGuidedDomainPurchaseScenario(next, tunnelLogger) {
2619
2568
  }
2620
2569
  console.log();
2621
2570
  }
2571
+ if (!hasDomain) {
2572
+ console.log(chalk7.dim(" \uB3C4\uBA54\uC778 \uC5F0\uACB0: `brewnet domain connect`"));
2573
+ console.log();
2574
+ }
2622
2575
  printNetworkSummary(updated);
2623
2576
  return updated;
2624
2577
  } catch (err) {
2625
2578
  if (qtManager) {
2626
- await qtManager.stop().catch(() => {
2579
+ await qtManager.stop().catch((stopErr) => {
2580
+ console.warn("[Named Tunnel] Quick Tunnel \uC911\uC9C0 \uC2E4\uD328:", stopErr instanceof Error ? stopErr.message : String(stopErr));
2627
2581
  });
2628
2582
  }
2629
2583
  console.log(chalk7.red(` \uC624\uB958: ${err instanceof Error ? err.message : String(err)}`));
@@ -2632,30 +2586,6 @@ async function runGuidedDomainPurchaseScenario(next, tunnelLogger) {
2632
2586
  return runLocalScenario(next);
2633
2587
  }
2634
2588
  }
2635
- async function runNamedTunnelOnlyScenario(next, tunnelLogger) {
2636
- console.log(chalk7.bold(" Named Tunnel \uC0DD\uC131 (\uB3C4\uBA54\uC778 \uC5F0\uACB0\uC740 \uB098\uC911\uC5D0)"));
2637
- console.log(chalk7.dim(" \uD130\uB110\uB9CC \uC0DD\uC131\uD558\uACE0, \uB3C4\uBA54\uC778 \uC5F0\uACB0\uC740 \uC124\uCE58 \uC644\uB8CC \uD6C4"));
2638
- console.log(chalk7.dim(" `brewnet domain connect` \uBA85\uB839\uC73C\uB85C \uC9C4\uD589\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2639
- console.log();
2640
- next.domain.provider = "tunnel";
2641
- next.domain.ssl = "cloudflare";
2642
- next.domain.cloudflare.enabled = true;
2643
- next.domain.cloudflare.tunnelMode = "named";
2644
- next.domain.cloudflare.zoneId = "";
2645
- next.domain.cloudflare.zoneName = "";
2646
- try {
2647
- const updated = await runNamedTunnelApiFlow(next, tunnelLogger, false);
2648
- console.log(chalk7.dim(" \uB3C4\uBA54\uC778 \uC5F0\uACB0: `brewnet domain connect`"));
2649
- console.log();
2650
- printNetworkSummary(updated);
2651
- return updated;
2652
- } catch (err) {
2653
- console.log(chalk7.red(` \uC624\uB958: ${err instanceof Error ? err.message : String(err)}`));
2654
- console.log();
2655
- console.log(chalk7.yellow(" Local \uBAA8\uB4DC\uB85C \uC804\uD658\uD569\uB2C8\uB2E4."));
2656
- return runLocalScenario(next);
2657
- }
2658
- }
2659
2589
  async function waitForTunnelHealthy(apiToken, accountId, tunnelId, timeoutMs) {
2660
2590
  const start = Date.now();
2661
2591
  const pollIntervalMs = 2e3;
@@ -3152,7 +3082,7 @@ async function runReviewStep(state) {
3152
3082
  }
3153
3083
 
3154
3084
  // src/wizard/steps/generate.ts
3155
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync2, existsSync as existsSync3, chmodSync as chmodSync2 } from "fs";
3085
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync2, existsSync as existsSync2, chmodSync as chmodSync2 } from "fs";
3156
3086
  import { join as join3, dirname } from "path";
3157
3087
  import { homedir as homedir2 } from "os";
3158
3088
  import chalk9 from "chalk";
@@ -3878,6 +3808,15 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
3878
3808
  box-shadow:0 0 6px rgba(34,197,94,0.4); animation:pulse 2.5s ease-in-out infinite; }
3879
3809
  @keyframes pulse { 0%,100%{opacity:1;box-shadow:0 0 6px rgba(34,197,94,0.4)}
3880
3810
  50%{opacity:.5;box-shadow:0 0 2px rgba(34,197,94,0.2)} }
3811
+ .hero-links { display:flex; align-items:center; justify-content:center; gap:.75rem;
3812
+ margin-top:1.5rem; flex-wrap:wrap; }
3813
+ .hero-links a { display:inline-flex; align-items:center; gap:.4rem; padding:.45rem 1rem;
3814
+ border:1px solid var(--border); border-radius:100px; font-family:var(--mono);
3815
+ font-size:.75rem; color:var(--text-muted); background:var(--surface-raised);
3816
+ text-decoration:none; transition:border-color .2s, color .2s; }
3817
+ .hero-links a:hover { border-color:#333; color:var(--text); }
3818
+ .hero-links a svg { flex-shrink:0; fill:currentColor; }
3819
+ .hero-domain svg { fill:none; stroke:currentColor; }
3881
3820
  .footer { position:fixed; bottom:1.2rem; left:0; right:0; text-align:center;
3882
3821
  font-family:var(--mono); font-size:.65rem; color:#2a2a2a; letter-spacing:.08em; z-index:1; }
3883
3822
  .footer span { color:#333; }
@@ -3903,6 +3842,16 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
3903
3842
  <span class="dot"></span>
3904
3843
  systems operational
3905
3844
  </div>
3845
+ <div class="hero-links">
3846
+ <a href="https://brewnet.dev" class="hero-domain" target="_blank" rel="noopener">
3847
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M2 12h20"></path><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
3848
+ brewnet.dev
3849
+ </a>
3850
+ <a href="https://github.com/claude-code-expert/brewnet" class="hero-github" target="_blank" rel="noopener">
3851
+ <svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
3852
+ GitHub
3853
+ </a>
3854
+ </div>
3906
3855
  </div>
3907
3856
  <div class="footer">
3908
3857
  brewnet.dev <span>&middot;</span> MIT
@@ -4570,7 +4519,7 @@ async function runGenerateStep(state) {
4570
4519
  pollHealth: boilerplatePollHealth,
4571
4520
  verifyEndpoints: boilerplateVerifyEndpoints,
4572
4521
  findFreePort
4573
- } = await import("./boilerplate-manager-P6QYUU7Q.js");
4522
+ } = await import("./boilerplate-manager-WEFTHL2O.js");
4574
4523
  const dbPrimary = state.servers.dbServer.primary;
4575
4524
  const dbDriver = dbPrimary === "postgresql" ? "postgres" : dbPrimary === "mysql" ? "mysql" : "sqlite3";
4576
4525
  const dbOpts = {
@@ -4627,7 +4576,7 @@ async function runGenerateStep(state) {
4627
4576
  {
4628
4577
  const { execa: execaFn } = await import("execa");
4629
4578
  const stale = prevStackMetas.filter(
4630
- (prev) => prev.stackId === stackId && prev.appDir !== appDir && existsSync3(prev.appDir)
4579
+ (prev) => prev.stackId === stackId && prev.appDir !== appDir && existsSync2(prev.appDir)
4631
4580
  );
4632
4581
  for (const prev of stale) {
4633
4582
  bpSpinner.text = ` [${stackId}] \uC774\uC804 \uCEE8\uD14C\uC774\uB108 \uC815\uB9AC \uC911...`;
@@ -4640,7 +4589,7 @@ async function runGenerateStep(state) {
4640
4589
  let isNextjsBasePath = false;
4641
4590
  if (state.domain.cloudflare.tunnelMode === "quick") {
4642
4591
  try {
4643
- const { injectTraefikForQuickTunnel } = await import("./boilerplate-manager-P6QYUU7Q.js");
4592
+ const { injectTraefikForQuickTunnel } = await import("./boilerplate-manager-WEFTHL2O.js");
4644
4593
  injectTraefikForQuickTunnel(appDir, stackId, backendPort);
4645
4594
  if (stackId.startsWith("nodejs-nextjs")) isNextjsBasePath = true;
4646
4595
  } catch {
@@ -4873,7 +4822,7 @@ async function runGenerateStep(state) {
4873
4822
  });
4874
4823
  gitea.succeed(` Gitea: admin \uACC4\uC815 \uC0DD\uC131 \uC644\uB8CC (${adminUser})`);
4875
4824
  const tokenPath = join3(homedir2(), ".brewnet", "gitea-token");
4876
- if (!existsSync3(tokenPath)) {
4825
+ if (!existsSync2(tokenPath)) {
4877
4826
  const giteaDirectUrl = "http://localhost:3000";
4878
4827
  const basic = Buffer.from(`${adminUser}:${adminPass}`).toString("base64");
4879
4828
  try {
@@ -4891,6 +4840,11 @@ async function runGenerateStep(state) {
4891
4840
  writeFileSync3(tokenPath, td.sha1, "utf-8");
4892
4841
  chmodSync2(tokenPath, 384);
4893
4842
  gitea.succeed(" Gitea: API \uD1A0\uD070 \uC0DD\uC131 \uC644\uB8CC");
4843
+ try {
4844
+ const { saveGiteaConfig } = await import("./app-manager-FIHPVUP7.js");
4845
+ saveGiteaConfig("http://localhost/git", adminUser);
4846
+ } catch {
4847
+ }
4894
4848
  } else {
4895
4849
  const errBody = await tr.text();
4896
4850
  gitea.warn(` Gitea: API \uD1A0\uD070 \uC0DD\uC131 \uC2E4\uD328 (${tr.status}) \u2014 create-app \uC2DC \uC790\uB3D9 \uC7AC\uC2DC\uB3C4\uB429\uB2C8\uB2E4`);
@@ -4939,7 +4893,7 @@ async function runGenerateStep(state) {
4939
4893
  }
4940
4894
 
4941
4895
  // src/services/uninstall-manager.ts
4942
- import { existsSync as existsSync4, rmSync, readdirSync, readFileSync as readFileSync3 } from "fs";
4896
+ import { existsSync as existsSync3, rmSync, readdirSync, readFileSync as readFileSync3 } from "fs";
4943
4897
  import { homedir as homedir3 } from "os";
4944
4898
  import { join as join4 } from "path";
4945
4899
  import { execa as execa5 } from "execa";
@@ -4958,7 +4912,7 @@ function expandPath(p) {
4958
4912
  var BREWNET_DIR = join4(homedir3(), ".brewnet");
4959
4913
  function readManifest(projectPath) {
4960
4914
  const manifestPath = join4(projectPath, ".brewnet-manifest.json");
4961
- if (!existsSync4(manifestPath)) return null;
4915
+ if (!existsSync3(manifestPath)) return null;
4962
4916
  try {
4963
4917
  const raw = readFileSync3(manifestPath, "utf-8");
4964
4918
  const parsed = JSON.parse(raw);
@@ -4974,7 +4928,7 @@ async function stopBoilerplateContainers(projectPath, stacks, keepData, result)
4974
4928
  for (const stack of stacks) {
4975
4929
  const stackDir = join4(projectPath, stack.directory);
4976
4930
  const stackCompose = join4(stackDir, DOCKER_COMPOSE_FILENAME);
4977
- if (!existsSync4(stackCompose)) {
4931
+ if (!existsSync3(stackCompose)) {
4978
4932
  result.skipped.push(`Boilerplate containers [${stack.stackId}]: compose not found`);
4979
4933
  continue;
4980
4934
  }
@@ -4996,7 +4950,7 @@ async function stopBoilerplateContainers(projectPath, stacks, keepData, result)
4996
4950
  function removeByManifest(projectPath, manifest, result) {
4997
4951
  for (const stack of manifest.boilerplateStacks) {
4998
4952
  const stackDir = join4(projectPath, stack.directory);
4999
- if (existsSync4(stackDir)) {
4953
+ if (existsSync3(stackDir)) {
5000
4954
  try {
5001
4955
  rmSync(stackDir, { recursive: true, force: true });
5002
4956
  result.removed.push(`Boilerplate source [${stack.stackId}]: ${stackDir}`);
@@ -5009,7 +4963,7 @@ function removeByManifest(projectPath, manifest, result) {
5009
4963
  }
5010
4964
  for (const dir of manifest.generatedDirs) {
5011
4965
  const dirPath = join4(projectPath, dir);
5012
- if (existsSync4(dirPath)) {
4966
+ if (existsSync3(dirPath)) {
5013
4967
  try {
5014
4968
  rmSync(dirPath, { recursive: true, force: true });
5015
4969
  result.removed.push(`Generated dir: ${dir}/`);
@@ -5022,7 +4976,7 @@ function removeByManifest(projectPath, manifest, result) {
5022
4976
  let removedFileCount = 0;
5023
4977
  for (const file of manifest.generatedFiles) {
5024
4978
  const filePath = join4(projectPath, file);
5025
- if (existsSync4(filePath)) {
4979
+ if (existsSync3(filePath)) {
5026
4980
  try {
5027
4981
  rmSync(filePath, { force: true });
5028
4982
  removedFileCount++;
@@ -5065,7 +5019,7 @@ function resolveProject(options) {
5065
5019
  function buildUninstallTargets(projectPath, options) {
5066
5020
  const targets = [];
5067
5021
  if (projectPath) projectPath = expandPath(projectPath);
5068
- if (projectPath && existsSync4(join4(projectPath, DOCKER_COMPOSE_FILENAME))) {
5022
+ if (projectPath && existsSync3(join4(projectPath, DOCKER_COMPOSE_FILENAME))) {
5069
5023
  targets.push({
5070
5024
  label: `Docker containers${options.keepData ? "" : " + volumes + images"}`,
5071
5025
  path: join4(projectPath, DOCKER_COMPOSE_FILENAME),
@@ -5077,7 +5031,7 @@ function buildUninstallTargets(projectPath, options) {
5077
5031
  if (manifest) {
5078
5032
  for (const stack of manifest.boilerplateStacks) {
5079
5033
  const stackCompose = join4(projectPath, stack.directory, DOCKER_COMPOSE_FILENAME);
5080
- if (existsSync4(stackCompose)) {
5034
+ if (existsSync3(stackCompose)) {
5081
5035
  targets.push({
5082
5036
  label: `Boilerplate containers [${stack.stackId}]${options.keepData ? "" : " + volumes + images"}`,
5083
5037
  path: stackCompose,
@@ -5103,7 +5057,7 @@ function buildUninstallTargets(projectPath, options) {
5103
5057
  skipReason: options.keepConfig ? "--keep-config" : void 0
5104
5058
  });
5105
5059
  }
5106
- if (existsSync4(BREWNET_DIR)) {
5060
+ if (existsSync3(BREWNET_DIR)) {
5107
5061
  targets.push({
5108
5062
  label: "~/.brewnet/ (all data, source, config)",
5109
5063
  path: BREWNET_DIR,
@@ -5116,7 +5070,7 @@ function buildUninstallTargets(projectPath, options) {
5116
5070
  "/usr/bin/brewnet"
5117
5071
  ];
5118
5072
  for (const bin of possibleBins) {
5119
- if (existsSync4(bin)) {
5073
+ if (existsSync3(bin)) {
5120
5074
  targets.push({ label: `CLI binary: ${bin}`, path: bin, type: "brewnet-meta" });
5121
5075
  }
5122
5076
  }
@@ -5142,7 +5096,7 @@ async function runUninstall(options = {}) {
5142
5096
  await stopBoilerplateContainers(projectPath, manifest.boilerplateStacks, options.keepData ?? false, result);
5143
5097
  }
5144
5098
  }
5145
- if (projectPath && existsSync4(join4(projectPath, DOCKER_COMPOSE_FILENAME))) {
5099
+ if (projectPath && existsSync3(join4(projectPath, DOCKER_COMPOSE_FILENAME))) {
5146
5100
  try {
5147
5101
  const downArgs = ["compose", "-f", DOCKER_COMPOSE_FILENAME, "down"];
5148
5102
  if (!options.keepData) {
@@ -5201,7 +5155,7 @@ async function runUninstall(options = {}) {
5201
5155
  result.skipped.push("Docker networks (not found or already removed)");
5202
5156
  }
5203
5157
  if (projectPath && !options.keepConfig) {
5204
- if (existsSync4(projectPath)) {
5158
+ if (existsSync3(projectPath)) {
5205
5159
  const manifest = readManifest(projectPath);
5206
5160
  if (manifest) {
5207
5161
  removeByManifest(projectPath, manifest, result);
@@ -5224,7 +5178,7 @@ async function runUninstall(options = {}) {
5224
5178
  } else if (options.keepConfig) {
5225
5179
  result.skipped.push(`Project directory preserved (--keep-config): ${projectPath ?? "n/a"}`);
5226
5180
  }
5227
- if (existsSync4(BREWNET_DIR)) {
5181
+ if (existsSync3(BREWNET_DIR)) {
5228
5182
  try {
5229
5183
  rmSync(BREWNET_DIR, { recursive: true, force: true });
5230
5184
  result.removed.push("~/.brewnet/ (all data, source, config)");
@@ -5236,7 +5190,7 @@ async function runUninstall(options = {}) {
5236
5190
  }
5237
5191
  if (projectName) {
5238
5192
  const projectStateDir = getProjectDir(projectName);
5239
- if (existsSync4(projectStateDir)) {
5193
+ if (existsSync3(projectStateDir)) {
5240
5194
  try {
5241
5195
  rmSync(projectStateDir, { recursive: true, force: true });
5242
5196
  result.removed.push(`Wizard state: ~/.brewnet/projects/${projectName}`);
@@ -5250,7 +5204,7 @@ async function runUninstall(options = {}) {
5250
5204
  "/usr/bin/brewnet"
5251
5205
  ];
5252
5206
  for (const bin of possibleBins) {
5253
- if (existsSync4(bin)) {
5207
+ if (existsSync3(bin)) {
5254
5208
  try {
5255
5209
  rmSync(bin, { force: true });
5256
5210
  result.removed.push(`CLI binary: ${bin}`);
@@ -5267,7 +5221,7 @@ async function runUninstall(options = {}) {
5267
5221
  async function cleanupForRestart(projectPath) {
5268
5222
  const expanded = expandPath(projectPath);
5269
5223
  const composePath = join4(expanded, DOCKER_COMPOSE_FILENAME);
5270
- if (existsSync4(composePath)) {
5224
+ if (existsSync3(composePath)) {
5271
5225
  try {
5272
5226
  await execa5("docker", [
5273
5227
  "compose",
@@ -5286,7 +5240,7 @@ async function cleanupForRestart(projectPath) {
5286
5240
  } catch {
5287
5241
  }
5288
5242
  }
5289
- if (existsSync4(expanded)) {
5243
+ if (existsSync3(expanded)) {
5290
5244
  try {
5291
5245
  rmSync(expanded, { recursive: true, force: true });
5292
5246
  } catch {
@@ -5304,7 +5258,7 @@ function listInstallations() {
5304
5258
  results.push({ name, path: rawPath });
5305
5259
  };
5306
5260
  const projectsDir = join4(BREWNET_DIR, "projects");
5307
- if (existsSync4(projectsDir)) {
5261
+ if (existsSync3(projectsDir)) {
5308
5262
  try {
5309
5263
  for (const e of readdirSync(projectsDir, { withFileTypes: true })) {
5310
5264
  if (!e.isDirectory()) continue;
@@ -5315,12 +5269,12 @@ function listInstallations() {
5315
5269
  }
5316
5270
  }
5317
5271
  const brewnetRoot = join4(homedir3(), "brewnet");
5318
- if (existsSync4(brewnetRoot)) {
5272
+ if (existsSync3(brewnetRoot)) {
5319
5273
  try {
5320
5274
  for (const e of readdirSync(brewnetRoot, { withFileTypes: true })) {
5321
5275
  if (!e.isDirectory()) continue;
5322
5276
  const composePath = join4(brewnetRoot, e.name, DOCKER_COMPOSE_FILENAME);
5323
- if (existsSync4(composePath)) {
5277
+ if (existsSync3(composePath)) {
5324
5278
  addIfNew(e.name, `~/brewnet/${e.name}`);
5325
5279
  }
5326
5280
  }
@@ -5334,7 +5288,7 @@ function listInstallations() {
5334
5288
  import chalk10 from "chalk";
5335
5289
  import Table3 from "cli-table3";
5336
5290
  import { execSync as execSync3 } from "child_process";
5337
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
5291
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
5338
5292
  import { join as join6 } from "path";
5339
5293
  import { homedir as homedir5 } from "os";
5340
5294
 
@@ -5342,7 +5296,7 @@ import { homedir as homedir5 } from "os";
5342
5296
  import { spawn } from "child_process";
5343
5297
  import { join as join5, dirname as dirname2 } from "path";
5344
5298
  import { fileURLToPath } from "url";
5345
- import { openSync, mkdirSync as mkdirSync6, existsSync as existsSync5 } from "fs";
5299
+ import { openSync, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
5346
5300
  import { homedir as homedir4 } from "os";
5347
5301
  import { createConnection } from "net";
5348
5302
  var __dirname = dirname2(fileURLToPath(import.meta.url));
@@ -5355,7 +5309,7 @@ function getDaemonPath() {
5355
5309
  join5(__dirname, "..", "services", "admin-daemon.js")
5356
5310
  ];
5357
5311
  for (const p of candidates) {
5358
- if (existsSync5(p)) return p;
5312
+ if (existsSync4(p)) return p;
5359
5313
  }
5360
5314
  return candidates[0];
5361
5315
  }
@@ -5533,7 +5487,7 @@ async function runCompleteStep(state, options) {
5533
5487
  }
5534
5488
  const resolvedProjectPath = state.projectPath.startsWith("~") ? join6(homedir5(), state.projectPath.slice(1)) : state.projectPath;
5535
5489
  const boilerplateMetaPath = join6(resolvedProjectPath, ".brewnet-boilerplate.json");
5536
- if (existsSync6(boilerplateMetaPath)) {
5490
+ if (existsSync5(boilerplateMetaPath)) {
5537
5491
  try {
5538
5492
  const raw = JSON.parse(readFileSync4(boilerplateMetaPath, "utf-8"));
5539
5493
  const stacks = Array.isArray(raw) ? raw : raw.stackId ? [raw] : [];
@@ -5703,7 +5657,7 @@ async function runInitWizard(options = {}) {
5703
5657
  let state = createState();
5704
5658
  if (options.config) {
5705
5659
  const configPath = resolve(options.config);
5706
- if (!existsSync7(configPath)) {
5660
+ if (!existsSync6(configPath)) {
5707
5661
  console.log(chalk12.red(` Config file not found: ${configPath}`));
5708
5662
  console.log();
5709
5663
  return;
@@ -6556,7 +6510,7 @@ function registerAdminCommand(program) {
6556
6510
  return;
6557
6511
  }
6558
6512
  if (options.foreground) {
6559
- const { createAdminServer } = await import("./admin-server-DQVIEHV3.js");
6513
+ const { createAdminServer } = await import("./admin-server-UODBPGWR.js");
6560
6514
  const spinner2 = ora11(`Starting admin panel on port ${port}...`).start();
6561
6515
  const { start } = createAdminServer({ port, projectPath: options.path || void 0 });
6562
6516
  try {
@@ -7598,7 +7552,7 @@ async function handleDomainList() {
7598
7552
  }
7599
7553
 
7600
7554
  // src/commands/create-app.ts
7601
- import { existsSync as existsSync8, rmSync as rmSync2, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2 } from "fs";
7555
+ import { existsSync as existsSync7, rmSync as rmSync2, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2 } from "fs";
7602
7556
  import { resolve as resolve2, join as join9 } from "path";
7603
7557
  import { homedir as homedir8 } from "os";
7604
7558
  import chalk25 from "chalk";
@@ -7661,7 +7615,7 @@ async function runCreateApp(projectName, options) {
7661
7615
  let resolvedStackId = options.stack ?? "(interactive)";
7662
7616
  const sigintHandler = () => {
7663
7617
  process.removeListener("SIGINT", sigintHandler);
7664
- if (!cloneSucceeded && existsSync8(projectDir)) {
7618
+ if (!cloneSucceeded && existsSync7(projectDir)) {
7665
7619
  rmSync2(projectDir, { recursive: true, force: true });
7666
7620
  console.log(chalk25.yellow("\n\nAborted. Partial directory removed."));
7667
7621
  } else {
@@ -7685,7 +7639,7 @@ async function runCreateApp(projectName, options) {
7685
7639
  if (dockerCheck.status !== "pass") {
7686
7640
  throw BrewnetError.dockerNotRunning();
7687
7641
  }
7688
- if (existsSync8(projectDir)) {
7642
+ if (existsSync7(projectDir)) {
7689
7643
  throw BrewnetError.directoryConflict(projectName);
7690
7644
  }
7691
7645
  let stack;
@@ -7809,7 +7763,7 @@ async function runCreateApp(projectName, options) {
7809
7763
  });
7810
7764
  printSuccessBox(projectName, stack, dbDriver, backendPort);
7811
7765
  } catch (err) {
7812
- if (!cloneSucceeded && existsSync8(projectDir)) {
7766
+ if (!cloneSucceeded && existsSync7(projectDir)) {
7813
7767
  rmSync2(projectDir, { recursive: true, force: true });
7814
7768
  }
7815
7769
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -7869,7 +7823,7 @@ function stripAnsi(str) {
7869
7823
  // src/index.ts
7870
7824
  function createProgram() {
7871
7825
  const program = new Command();
7872
- program.name("brewnet").description("Your Home Server, Brewed Fresh").version("0.0.1").showHelpAfterError('(run "brewnet --help" for usage information)');
7826
+ program.name("brewnet").description("Your Home Server, Brewed Fresh").version("0.0.2").showHelpAfterError('(run "brewnet --help" for usage information)');
7873
7827
  registerInitCommand(program);
7874
7828
  registerStatusCommand(program);
7875
7829
  registerAddCommand(program);