@panguard-ai/panguard 0.2.3 → 0.2.6

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 (96) hide show
  1. package/dist/bridges/scan-to-report.d.ts +34 -0
  2. package/dist/bridges/scan-to-report.d.ts.map +1 -0
  3. package/dist/bridges/scan-to-report.js +28 -0
  4. package/dist/bridges/scan-to-report.js.map +1 -0
  5. package/dist/cli/auth-guard.d.ts +3 -2
  6. package/dist/cli/auth-guard.d.ts.map +1 -1
  7. package/dist/cli/auth-guard.js +6 -18
  8. package/dist/cli/auth-guard.js.map +1 -1
  9. package/dist/cli/commands/admin.d.ts.map +1 -1
  10. package/dist/cli/commands/admin.js +2 -1
  11. package/dist/cli/commands/admin.js.map +1 -1
  12. package/dist/cli/commands/config.d.ts.map +1 -1
  13. package/dist/cli/commands/config.js +2 -1
  14. package/dist/cli/commands/config.js.map +1 -1
  15. package/dist/cli/commands/demo.d.ts.map +1 -1
  16. package/dist/cli/commands/demo.js +2 -1
  17. package/dist/cli/commands/demo.js.map +1 -1
  18. package/dist/cli/commands/deploy.js +34 -1
  19. package/dist/cli/commands/deploy.js.map +1 -1
  20. package/dist/cli/commands/doctor.d.ts +22 -0
  21. package/dist/cli/commands/doctor.d.ts.map +1 -0
  22. package/dist/cli/commands/doctor.js +502 -0
  23. package/dist/cli/commands/doctor.js.map +1 -0
  24. package/dist/cli/commands/guard.d.ts.map +1 -1
  25. package/dist/cli/commands/guard.js +209 -0
  26. package/dist/cli/commands/guard.js.map +1 -1
  27. package/dist/cli/commands/init.d.ts.map +1 -1
  28. package/dist/cli/commands/init.js +17 -8
  29. package/dist/cli/commands/init.js.map +1 -1
  30. package/dist/cli/commands/login.js +5 -2
  31. package/dist/cli/commands/login.js.map +1 -1
  32. package/dist/cli/commands/manager.d.ts.map +1 -1
  33. package/dist/cli/commands/manager.js +43 -34
  34. package/dist/cli/commands/manager.js.map +1 -1
  35. package/dist/cli/commands/scan.d.ts.map +1 -1
  36. package/dist/cli/commands/scan.js +107 -12
  37. package/dist/cli/commands/scan.js.map +1 -1
  38. package/dist/cli/commands/serve.d.ts.map +1 -1
  39. package/dist/cli/commands/serve.js +175 -5
  40. package/dist/cli/commands/serve.js.map +1 -1
  41. package/dist/cli/commands/threat.d.ts.map +1 -1
  42. package/dist/cli/commands/threat.js +8 -1
  43. package/dist/cli/commands/threat.js.map +1 -1
  44. package/dist/cli/credentials.d.ts +2 -2
  45. package/dist/cli/credentials.d.ts.map +1 -1
  46. package/dist/cli/credentials.js +3 -7
  47. package/dist/cli/credentials.js.map +1 -1
  48. package/dist/cli/index.js +2 -0
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/cli/interactive.d.ts +2 -2
  51. package/dist/cli/interactive.d.ts.map +1 -1
  52. package/dist/cli/interactive.js +505 -155
  53. package/dist/cli/interactive.js.map +1 -1
  54. package/dist/cli/menu.d.ts +25 -15
  55. package/dist/cli/menu.d.ts.map +1 -1
  56. package/dist/cli/menu.js +117 -124
  57. package/dist/cli/menu.js.map +1 -1
  58. package/dist/cli/theme.d.ts +1 -1
  59. package/dist/cli/theme.d.ts.map +1 -1
  60. package/dist/cli/theme.js +2 -3
  61. package/dist/cli/theme.js.map +1 -1
  62. package/dist/cli/ux-helpers.d.ts +4 -0
  63. package/dist/cli/ux-helpers.d.ts.map +1 -1
  64. package/dist/cli/ux-helpers.js +10 -0
  65. package/dist/cli/ux-helpers.js.map +1 -1
  66. package/dist/index.d.ts +3 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +6 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/init/config-writer.d.ts +12 -1
  71. package/dist/init/config-writer.d.ts.map +1 -1
  72. package/dist/init/config-writer.js +149 -6
  73. package/dist/init/config-writer.js.map +1 -1
  74. package/dist/init/environment.d.ts +9 -0
  75. package/dist/init/environment.d.ts.map +1 -1
  76. package/dist/init/environment.js +127 -0
  77. package/dist/init/environment.js.map +1 -1
  78. package/dist/init/index.d.ts +4 -4
  79. package/dist/init/index.d.ts.map +1 -1
  80. package/dist/init/index.js +3 -3
  81. package/dist/init/index.js.map +1 -1
  82. package/dist/init/steps.d.ts +5 -0
  83. package/dist/init/steps.d.ts.map +1 -1
  84. package/dist/init/steps.js +155 -1
  85. package/dist/init/steps.js.map +1 -1
  86. package/dist/init/types.d.ts +13 -0
  87. package/dist/init/types.d.ts.map +1 -1
  88. package/dist/init/wizard-runner.d.ts +3 -2
  89. package/dist/init/wizard-runner.d.ts.map +1 -1
  90. package/dist/init/wizard-runner.js +32 -16
  91. package/dist/init/wizard-runner.js.map +1 -1
  92. package/package.json +5 -4
  93. package/dist/manager/manager-server.d.ts +0 -102
  94. package/dist/manager/manager-server.d.ts.map +0 -1
  95. package/dist/manager/manager-server.js +0 -515
  96. package/dist/manager/manager-server.js.map +0 -1
@@ -1,28 +1,26 @@
1
1
  /**
2
2
  * Panguard AI - Interactive CLI Mode
3
3
  *
4
- * Minimalist arrow-key menu interface inspired by Claude Code / Vercel CLI.
5
- * Single-language display, no box borders, brand sage green theme.
4
+ * Number-key menu [0]-[7] with panguard > prompt for text commands.
5
+ * Box-bordered status panel, breadcrumb navigation, no "press any key" interrupts.
6
6
  *
7
7
  * @module @panguard-ai/panguard/cli/interactive
8
8
  */
9
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, openSync, closeSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
11
  import { homedir } from 'node:os';
12
12
  import { c, spinner, colorSeverity, formatDuration, box } from '@panguard-ai/core';
13
13
  import { PANGUARD_VERSION } from '../index.js';
14
14
  import { renderLogo, theme } from './theme.js';
15
- import { runArrowMenu, pressAnyKey, cleanupTerminal, renderCompactMenu, waitForCompactChoice, } from './menu.js';
15
+ import { waitForMainInput, pressAnyKey, cleanupTerminal, renderCompactMenu, waitForCompactChoice, } from './menu.js';
16
16
  import { checkFeatureAccess, showUpgradePrompt, getLicense } from './auth-guard.js';
17
- import { tierDisplayName } from './credentials.js';
18
- import { breadcrumb, nextSteps, confirmDestructive, moduleCountDisplay } from './ux-helpers.js';
17
+ import { breadcrumb, nextSteps, confirmDestructive, moduleCountDisplay, formatError } from './ux-helpers.js';
19
18
  // ---------------------------------------------------------------------------
20
19
  // Language management
21
20
  // ---------------------------------------------------------------------------
22
21
  const CONFIG_DIR = join(homedir(), '.panguard');
23
22
  const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
24
23
  function detectLang() {
25
- // 1. Read saved preference
26
24
  if (existsSync(CONFIG_PATH)) {
27
25
  try {
28
26
  const data = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
@@ -33,7 +31,6 @@ function detectLang() {
33
31
  /* ignore */
34
32
  }
35
33
  }
36
- // 2. Detect from system locale
37
34
  const sysLang = process.env['LANG'] ?? process.env['LC_ALL'] ?? '';
38
35
  if (sysLang.includes('zh'))
39
36
  return 'zh-TW';
@@ -56,42 +53,201 @@ function saveLang(lang) {
56
53
  }
57
54
  let currentLang = 'en';
58
55
  const MENU_DEFS = [
59
- { key: 'scan', en: 'Security scan', zh: '\u5B89\u5168\u6383\u63CF', tier: 'community' },
60
- { key: 'guard', en: 'Real-time protection', zh: '\u5373\u6642\u9632\u8B77', tier: 'community' },
61
56
  {
62
- key: 'threat-cloud',
63
- en: 'Threat intelligence API',
64
- zh: '\u5A01\u8105\u60C5\u5831 API',
65
- tier: 'community',
57
+ key: 'setup',
58
+ icon: '>',
59
+ number: 0,
60
+ en: 'Setup Wizard',
61
+ zh: '\u521D\u59CB\u8A2D\u5B9A',
62
+ enDesc: 'Configure all modules with guided setup',
63
+ zhDesc: '\u958B\u555F\u6B64\u5F15\u64CE\uFF0C\u81EA\u52D5\u914D\u7F6E\u6240\u6709\u6A21\u7D44',
64
+ tierBadge: '',
65
+ featureKey: 'setup',
66
66
  },
67
67
  {
68
- key: 'notify',
69
- en: 'Notifications (Telegram, Slack)',
70
- zh: '\u901A\u77E5\u7CFB\u7D71 (Telegram, Slack)',
71
- tier: 'solo',
68
+ key: 'scan',
69
+ icon: '\u26A1',
70
+ number: 1,
71
+ en: 'Security Scan',
72
+ zh: '\u5B89\u5168\u6383\u63CF',
73
+ enDesc: 'Scan system security and analyze risks',
74
+ zhDesc: '\u6383\u63CF\u7CFB\u7D71\u5B89\u5168\u72C0\u614B\uFF0C\u5206\u6790\u6240\u6709\u98A8\u96AA',
75
+ tierBadge: '',
76
+ featureKey: 'scan',
72
77
  },
73
- { key: 'trap', en: 'Honeypot system', zh: '\u871C\u7F50\u7CFB\u7D71', tier: 'solo' },
74
78
  {
75
79
  key: 'report',
76
- en: 'Compliance report (ISO 27001, SOC 2)',
77
- zh: '\u5408\u898F\u5831\u544A (ISO 27001, SOC 2)',
78
- tier: 'business',
80
+ icon: '\u25A0',
81
+ number: 2,
82
+ en: 'Compliance Report',
83
+ zh: '\u5408\u898F\u5831\u544A',
84
+ enDesc: 'Generate ISO 27001, SOC 2, TW Cyber Security Act reports',
85
+ zhDesc: '\u7522\u751F ISO 27001\u3001SOC 2\u3001\u8CC7\u5B89\u7BA1\u7406\u6CD5\u5408\u898F\u5831\u544A',
86
+ tierBadge: '[PRO]',
87
+ featureKey: 'report',
88
+ },
89
+ {
90
+ key: 'guard',
91
+ icon: '\u2713',
92
+ number: 3,
93
+ en: 'Guard Engine',
94
+ zh: '\u5B88\u8B77\u5F15\u64CE',
95
+ enDesc: 'Real-time monitoring and continuous protection',
96
+ zhDesc: '\u5373\u6642\u76E3\u63A7\u9023\u7E8C\u9632\u8B77\u7CFB\u7D71',
97
+ tierBadge: '',
98
+ featureKey: 'guard',
99
+ },
100
+ {
101
+ key: 'trap',
102
+ icon: '\u00B7',
103
+ number: 4,
104
+ en: 'Honeypot System',
105
+ zh: '\u871C\u7F50\u7CFB\u7D71',
106
+ enDesc: 'Detect and profile attackers with decoy services',
107
+ zhDesc: '\u5373\u770B\u5373\u8B58\u99ED\u5BA2\uFF0C\u5206\u6790\u653B\u64CA\u8005\u884C\u70BA',
108
+ tierBadge: '[PRO]',
109
+ featureKey: 'trap',
110
+ },
111
+ {
112
+ key: 'notify',
113
+ icon: '\u00B7',
114
+ number: 5,
115
+ en: 'Notifications',
116
+ zh: '\u901A\u77E5\u7CFB\u7D71',
117
+ enDesc: 'LINE, Telegram, Slack, Email, Webhook channels',
118
+ zhDesc: 'LINE\u3001Telegram\u3001Slack\u3001Email\u3001Webhook \u901A\u77E5\u7BA1\u9053',
119
+ tierBadge: '[STARTER]',
120
+ featureKey: 'notify',
121
+ },
122
+ {
123
+ key: 'threat-cloud',
124
+ icon: '\u00B7',
125
+ number: 6,
126
+ en: 'Threat Cloud',
127
+ zh: '\u5A01\u8105\u60C5\u5831',
128
+ enDesc: 'Threat intelligence REST API server',
129
+ zhDesc: '\u5A01\u8105\u60C5\u5831 REST API \u4F3A\u670D\u52D9',
130
+ tierBadge: '[ENT]',
131
+ featureKey: 'threat-cloud',
132
+ },
133
+ {
134
+ key: 'demo',
135
+ icon: '\u00B7',
136
+ number: 7,
137
+ en: 'Auto Demo',
138
+ zh: '\u81EA\u52D5\u5C55\u793A',
139
+ enDesc: 'Run through all security modules',
140
+ zhDesc: '\u81EA\u52D5\u57F7\u884C\u7D9C\u5408\u529F\u80FD\u5C55\u793A',
141
+ tierBadge: '',
142
+ featureKey: 'demo',
79
143
  },
80
- { key: '__sep__', en: '', zh: '', tier: '' },
81
- { key: 'setup', en: 'Initial configuration', zh: '\u521D\u59CB\u8A2D\u5B9A', tier: 'community' },
82
- { key: 'demo', en: 'Feature demo', zh: '\u529F\u80FD\u5C55\u793A', tier: 'community' },
83
- { key: 'upgrade', en: 'Upgrade plan', zh: '\u5347\u7D1A\u65B9\u6848', tier: 'community' },
84
144
  ];
85
- function buildMenuItems(lang) {
86
- return MENU_DEFS.map((def) => {
87
- if (def.key === '__sep__')
88
- return { key: '__sep__', label: '', separator: true };
89
- return {
90
- key: def.key,
91
- label: lang === 'zh-TW' ? def.zh : def.en,
92
- tier: def.tier || undefined,
93
- };
94
- });
145
+ // ---------------------------------------------------------------------------
146
+ // Guard process helpers
147
+ // ---------------------------------------------------------------------------
148
+ function isGuardRunning() {
149
+ const pidPath = join(homedir(), '.panguard-guard', 'panguard-guard.pid');
150
+ if (!existsSync(pidPath))
151
+ return { running: false };
152
+ try {
153
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
154
+ process.kill(pid, 0);
155
+ return { running: true, pid };
156
+ }
157
+ catch {
158
+ return { running: false };
159
+ }
160
+ }
161
+ // ---------------------------------------------------------------------------
162
+ // Status panel with box border
163
+ // ---------------------------------------------------------------------------
164
+ function renderStatusPanel() {
165
+ const guardStatus = isGuardRunning();
166
+ const modulesValue = moduleCountDisplay(currentLang);
167
+ const guardLine = guardStatus.running
168
+ ? `${c.safe('PROTECTED')} ${c.dim(`(PID: ${guardStatus.pid})`)}`
169
+ : c.dim('Inactive');
170
+ const lines = currentLang === 'zh-TW'
171
+ ? [
172
+ `\u9632\u8B77\u72C0\u614B\uFF1A${guardLine}`,
173
+ `\u53EF\u7528\u6A21\u7D44\uFF1A${modulesValue}`,
174
+ `\u7248\u672C\uFF1A v${PANGUARD_VERSION}`,
175
+ ]
176
+ : [
177
+ `Protection: ${guardLine}`,
178
+ `Modules: ${modulesValue}`,
179
+ `Version: v${PANGUARD_VERSION}`,
180
+ ];
181
+ const title = currentLang === 'zh-TW'
182
+ ? '\u7CFB\u7D71\u72C0\u614B / System Status'
183
+ : 'System Status';
184
+ console.log(box(lines.join('\n'), { borderColor: c.sage, title }));
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // Menu rendering
188
+ // ---------------------------------------------------------------------------
189
+ function renderMenu() {
190
+ const titleText = currentLang === 'zh-TW'
191
+ ? '\u4E3B\u9078\u55AE / Main Menu'
192
+ : 'Main Menu';
193
+ console.log(` ${theme.title(titleText)}`);
194
+ console.log('');
195
+ for (const def of MENU_DEFS) {
196
+ const name = currentLang === 'zh-TW' ? def.zh : def.en;
197
+ const desc = currentLang === 'zh-TW' ? def.zhDesc : def.enDesc;
198
+ const badge = def.tierBadge ? ` ${c.dim(def.tierBadge)}` : '';
199
+ const iconStr = def.icon === '\u2713' ? c.safe(def.icon) : c.dim(def.icon);
200
+ console.log(` ${iconStr} ${c.sage(`[${def.number}]`)} ${name}${badge}`);
201
+ console.log(` ${c.dim(desc)}`);
202
+ console.log('');
203
+ }
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // Footer shortcuts
207
+ // ---------------------------------------------------------------------------
208
+ function renderFooter() {
209
+ const quit = currentLang === 'zh-TW' ? '\u9000\u51FA' : 'Quit';
210
+ const help = currentLang === 'zh-TW' ? '\u8AAA\u660E' : 'Help';
211
+ const langToggle = currentLang === 'zh-TW' ? '\u4E2D/EN' : '\u4E2D/EN';
212
+ console.log(`${c.dim(`[q] ${quit} [h] ${help} [b] ${langToggle}`)}`);
213
+ console.log('');
214
+ }
215
+ // ---------------------------------------------------------------------------
216
+ // Help display
217
+ // ---------------------------------------------------------------------------
218
+ function showHelp() {
219
+ console.log('');
220
+ const title = currentLang === 'zh-TW' ? '\u6307\u4EE4\u8AAA\u660E' : 'Available Commands';
221
+ console.log(` ${theme.brandBold(title)}`);
222
+ console.log('');
223
+ const menuTitle = currentLang === 'zh-TW' ? '\u4E3B\u9078\u55AE' : 'Main Menu';
224
+ console.log(` ${c.sage(menuTitle)}`);
225
+ console.log(c.dim(currentLang === 'zh-TW'
226
+ ? ' [0]-[7] \u6309\u6578\u5B57\u9375\u5373\u523B\u9078\u64C7\uFF08\u4E0D\u9700\u6309 Enter\uFF09'
227
+ : ' [0]-[7] Press number key to select instantly (no Enter needed)'));
228
+ console.log('');
229
+ const promptTitle = currentLang === 'zh-TW' ? '\u6587\u5B57\u6307\u4EE4' : 'Text Commands';
230
+ console.log(` ${c.sage(promptTitle)}`);
231
+ const cmds = [
232
+ { cmd: 'status', en: 'System status overview', zh: '\u7CFB\u7D71\u72C0\u614B\u7E3D\u89BD' },
233
+ { cmd: 'login', en: 'Account login', zh: '\u5E33\u865F\u767B\u5165' },
234
+ { cmd: 'logout', en: 'Account logout', zh: '\u767B\u51FA' },
235
+ { cmd: 'config', en: 'Settings management', zh: '\u8A2D\u5B9A\u7BA1\u7406' },
236
+ { cmd: 'upgrade', en: 'Upgrade plan', zh: '\u5347\u7D1A\u65B9\u6848' },
237
+ { cmd: 'hardening', en: 'Security hardening', zh: '\u5B89\u5168\u52A0\u56FA' },
238
+ { cmd: 'doctor', en: 'Health diagnostics', zh: '\u5065\u5EB7\u8A3A\u65B7' },
239
+ { cmd: 'whoami', en: 'Current account info', zh: '\u986F\u793A\u7576\u524D\u5E33\u865F' },
240
+ { cmd: 'help', en: 'Show this help', zh: '\u986F\u793A\u6B64\u8AAA\u660E' },
241
+ ];
242
+ for (const { cmd, en, zh } of cmds) {
243
+ const desc = currentLang === 'zh-TW' ? zh : en;
244
+ console.log(` ${c.sage(cmd.padEnd(14))} ${c.dim(desc)}`);
245
+ }
246
+ const shortcutTitle = currentLang === 'zh-TW' ? '\u5FEB\u6377\u9375' : 'Shortcuts';
247
+ console.log('');
248
+ console.log(` ${c.sage(shortcutTitle)}`);
249
+ console.log(c.dim(` [q] ${currentLang === 'zh-TW' ? '\u9000\u51FA' : 'Quit'} [h] ${currentLang === 'zh-TW' ? '\u8AAA\u660E' : 'Help'} [b] ${currentLang === 'zh-TW' ? '\u5207\u63DB\u8A9E\u8A00' : 'Toggle language'}`));
250
+ console.log('');
95
251
  }
96
252
  // ---------------------------------------------------------------------------
97
253
  // Startup screen
@@ -103,28 +259,15 @@ function renderStartup() {
103
259
  console.log('');
104
260
  // Tagline + version
105
261
  const tagline = c.dim(' AI-Powered Security Platform');
106
- const version = c.dim(`v${PANGUARD_VERSION}`);
107
- // Right-align version
108
- // eslint-disable-next-line no-control-regex
109
- const tagLen = tagline.replace(/\x1b\[[0-9;]*m/g, '').length;
110
- // eslint-disable-next-line no-control-regex
111
- const verLen = version.replace(/\x1b\[[0-9;]*m/g, '').length;
112
- const totalWidth = Math.max(60, process.stdout.columns ?? 60);
113
- const gap = Math.max(2, totalWidth - tagLen - verLen - 4);
114
- console.log(`${tagline}${' '.repeat(gap)}${version}`);
115
- console.log('');
116
- // Status info
117
- const { tier } = getLicense();
118
- const tierName = tierDisplayName(tier);
119
- const tierColor = tier === 'community' ? c.caution : c.safe;
120
- const statusLabel = currentLang === 'zh-TW' ? '\u72C0\u614B' : 'Status';
121
- const licenseLabel = currentLang === 'zh-TW' ? '\u6388\u6B0A' : 'License';
122
- const modulesLabel = currentLang === 'zh-TW' ? '\u6A21\u7D44' : 'Modules';
123
- const modulesValue = moduleCountDisplay(currentLang);
124
- console.log(` ${c.dim(statusLabel.padEnd(10))} ${c.safe('Ready')}`);
125
- console.log(` ${c.dim(licenseLabel.padEnd(10))} ${tierColor(tierName)}`);
126
- console.log(` ${c.dim(modulesLabel.padEnd(10))} ${modulesValue}`);
262
+ console.log(tagline);
263
+ console.log('');
264
+ // Status panel
265
+ renderStatusPanel();
127
266
  console.log('');
267
+ // Menu
268
+ renderMenu();
269
+ // Footer
270
+ renderFooter();
128
271
  }
129
272
  // ---------------------------------------------------------------------------
130
273
  // Entry point
@@ -143,61 +286,159 @@ export async function startInteractive(lang) {
143
286
  // First-time user hint
144
287
  if (!existsSync(CONFIG_PATH)) {
145
288
  const hint = currentLang === 'zh-TW'
146
- ? ` ${c.sage('\u25C6')} \u9996\u6B21\u4F7F\u7528\uFF1F\u5EFA\u8B70\u5148\u57F7\u884C\u300C\u521D\u59CB\u8A2D\u5B9A\u300D\u6216\u300C\u529F\u80FD\u5C55\u793A\u300D`
147
- : ` ${c.sage('\u25C6')} First time? Try "Initial configuration" or "Feature demo" to get started.`;
289
+ ? ` ${c.sage('\u25C6')} \u9996\u6B21\u4F7F\u7528\uFF1F\u5EFA\u8B70\u6309 [0] \u57F7\u884C\u521D\u59CB\u8A2D\u5B9A\u6216\u6309 [7] \u529F\u80FD\u5C55\u793A`
290
+ : ` ${c.sage('\u25C6')} First time? Press [0] for Setup Wizard or [7] for Auto Demo`;
148
291
  console.log(hint);
149
292
  console.log('');
150
293
  }
151
294
  // Main loop
152
295
  while (true) {
153
- const title = currentLang === 'zh-TW' ? ' \u6307\u4EE4' : ' Commands';
154
- console.log(theme.title(title));
155
- console.log('');
156
- const items = buildMenuItems(currentLang);
157
- const choice = await runArrowMenu(items, { lang: currentLang });
158
- if (!choice) {
159
- exit();
160
- return;
296
+ const promptLabel = c.sage('panguard >') + ' ';
297
+ process.stdout.write(promptLabel);
298
+ const input = await waitForMainInput();
299
+ switch (input.type) {
300
+ case 'quit':
301
+ exit();
302
+ return;
303
+ case 'help':
304
+ showHelp();
305
+ continue;
306
+ case 'lang_toggle':
307
+ currentLang = currentLang === 'zh-TW' ? 'en' : 'zh-TW';
308
+ saveLang(currentLang);
309
+ renderStartup();
310
+ continue;
311
+ case 'number': {
312
+ const def = MENU_DEFS[input.index];
313
+ if (!def)
314
+ continue;
315
+ // Feature gate check
316
+ if (!checkFeatureAccess(def.featureKey)) {
317
+ showUpgradePrompt(def.featureKey, currentLang);
318
+ await new Promise((r) => setTimeout(r, 500));
319
+ renderStartup();
320
+ continue;
321
+ }
322
+ console.clear();
323
+ try {
324
+ await dispatch(def.key);
325
+ }
326
+ catch (err) {
327
+ console.log('');
328
+ console.log(formatError(err instanceof Error ? err.message : String(err), `${currentLang === 'zh-TW' ? '\u57F7\u884C' : 'Running'} ${def.key}`, currentLang === 'zh-TW'
329
+ ? '\u8ACB\u67E5\u770B\u65E5\u8A8C\u6216\u91CD\u8A66'
330
+ : 'Check logs or retry'));
331
+ }
332
+ await new Promise((r) => setTimeout(r, 500));
333
+ renderStartup();
334
+ continue;
335
+ }
336
+ case 'command': {
337
+ const text = input.text.toLowerCase();
338
+ if (!text)
339
+ continue;
340
+ // Handle text commands
341
+ const handled = await dispatchCommand(text);
342
+ if (!handled) {
343
+ console.log(c.dim(currentLang === 'zh-TW'
344
+ ? ` \u672A\u77E5\u6307\u4EE4\u3002\u8F38\u5165 help \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002`
345
+ : ` Unknown command. Type 'help' for available commands.`));
346
+ console.log('');
347
+ }
348
+ continue;
349
+ }
161
350
  }
162
- if (choice.key === '__lang__') {
163
- currentLang = currentLang === 'zh-TW' ? 'en' : 'zh-TW';
164
- saveLang(currentLang);
351
+ }
352
+ }
353
+ // ---------------------------------------------------------------------------
354
+ // Prompt command dispatch
355
+ // ---------------------------------------------------------------------------
356
+ async function dispatchCommand(text) {
357
+ switch (text) {
358
+ case 'status':
359
+ console.clear();
360
+ await actionStatus();
361
+ await new Promise((r) => setTimeout(r, 500));
165
362
  renderStartup();
166
- continue;
167
- }
168
- // Feature gate check
169
- if (!checkFeatureAccess(choice.key)) {
170
- showUpgradePrompt(choice.key, currentLang);
171
- await pressAnyKey(currentLang);
363
+ return true;
364
+ case 'login':
365
+ console.clear();
366
+ await actionLogin();
367
+ await new Promise((r) => setTimeout(r, 500));
172
368
  renderStartup();
173
- continue;
174
- }
175
- console.clear();
176
- try {
177
- await dispatch(choice.key);
369
+ return true;
370
+ case 'logout': {
371
+ console.clear();
372
+ const { logoutCommand } = await import('./commands/logout.js');
373
+ const cmd = logoutCommand();
374
+ await cmd.parseAsync(['logout'], { from: 'user' });
375
+ await new Promise((r) => setTimeout(r, 500));
376
+ renderStartup();
377
+ return true;
178
378
  }
179
- catch (err) {
180
- console.log('');
181
- console.log(` ${c.critical('Error:')} ${err instanceof Error ? err.message : String(err)}`);
379
+ case 'config':
380
+ console.clear();
381
+ await actionConfig();
382
+ await new Promise((r) => setTimeout(r, 500));
383
+ renderStartup();
384
+ return true;
385
+ case 'upgrade':
386
+ console.clear();
387
+ await actionUpgrade();
388
+ await new Promise((r) => setTimeout(r, 500));
389
+ renderStartup();
390
+ return true;
391
+ case 'hardening':
392
+ console.clear();
393
+ await actionHardening();
394
+ await new Promise((r) => setTimeout(r, 500));
395
+ renderStartup();
396
+ return true;
397
+ case 'doctor':
398
+ console.clear();
399
+ try {
400
+ const { runDoctor } = await import('./commands/doctor.js');
401
+ await runDoctor(currentLang);
402
+ }
403
+ catch (err) {
404
+ console.log(formatError(err instanceof Error ? err.message : String(err), currentLang === 'zh-TW' ? '\u57F7\u884C\u5065\u5EB7\u8A3A\u65B7' : 'Running diagnostics', currentLang === 'zh-TW' ? '\u8ACB\u91CD\u8A66' : 'Please retry'));
405
+ }
406
+ await new Promise((r) => setTimeout(r, 500));
407
+ renderStartup();
408
+ return true;
409
+ case 'whoami': {
410
+ console.clear();
411
+ const { whoamiCommand } = await import('./commands/whoami.js');
412
+ const cmd = whoamiCommand();
413
+ await cmd.parseAsync(['whoami'], { from: 'user' });
414
+ await new Promise((r) => setTimeout(r, 500));
415
+ renderStartup();
416
+ return true;
182
417
  }
183
- await pressAnyKey(currentLang);
184
- renderStartup();
418
+ case 'help':
419
+ showHelp();
420
+ return true;
421
+ default:
422
+ return false;
185
423
  }
186
424
  }
187
425
  // ---------------------------------------------------------------------------
188
- // Action dispatch
426
+ // Action dispatch (from numbered menu)
189
427
  // ---------------------------------------------------------------------------
190
428
  async function dispatch(key) {
191
429
  switch (key) {
430
+ case 'setup':
431
+ await actionInit();
432
+ break;
192
433
  case 'scan':
193
434
  await actionScan();
194
435
  break;
195
- case 'guard':
196
- await actionGuard();
197
- break;
198
436
  case 'report':
199
437
  await actionReport();
200
438
  break;
439
+ case 'guard':
440
+ await actionGuard();
441
+ break;
201
442
  case 'trap':
202
443
  await actionTrap();
203
444
  break;
@@ -207,19 +448,13 @@ async function dispatch(key) {
207
448
  case 'threat-cloud':
208
449
  await actionThreat();
209
450
  break;
210
- case 'setup':
211
- await actionInit();
212
- break;
213
451
  case 'demo':
214
452
  await actionDemo();
215
453
  break;
216
- case 'upgrade':
217
- await actionUpgrade();
218
- break;
219
454
  }
220
455
  }
221
456
  // ---------------------------------------------------------------------------
222
- // 0. Setup Wizard
457
+ // [0] Setup Wizard
223
458
  // ---------------------------------------------------------------------------
224
459
  async function actionInit() {
225
460
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u521D\u59CB\u8A2D\u5B9A' : 'Setup']);
@@ -227,14 +462,13 @@ async function actionInit() {
227
462
  await runInitWizard(currentLang);
228
463
  }
229
464
  // ---------------------------------------------------------------------------
230
- // 1. Security Scan
465
+ // [1] Security Scan
231
466
  // ---------------------------------------------------------------------------
232
467
  async function actionScan() {
233
468
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B89\u5168\u6383\u63CF' : 'Security Scan']);
234
469
  const scanTitle = currentLang === 'zh-TW' ? '\u5B89\u5168\u6383\u63CF' : 'Security Scan';
235
470
  console.log(` ${theme.brandBold(scanTitle)}`);
236
471
  console.log('');
237
- // Scan depth selection
238
472
  const depthItems = [
239
473
  {
240
474
  key: '1',
@@ -271,7 +505,6 @@ async function actionScan() {
271
505
  : safetyScore >= 40
272
506
  ? 'D'
273
507
  : 'F';
274
- // Compact score display
275
508
  console.log('');
276
509
  const scoreLabel = currentLang === 'zh-TW' ? '\u6383\u63CF\u5B8C\u6210' : 'Scan Complete';
277
510
  console.log(` ${theme.title(scoreLabel)}${' '.repeat(30)}Score: ${c.bold(`${safetyScore}/100`)} (${grade})`);
@@ -282,7 +515,6 @@ async function actionScan() {
282
515
  for (const f of result.findings) {
283
516
  const sev = colorSeverity(f.severity).padEnd(10);
284
517
  console.log(` ${sev} ${f.title}`);
285
- // Show manual fix commands for free tier
286
518
  if (tier === 'community' && f.manualFix && f.manualFix.length > 0) {
287
519
  const fixLabel = currentLang === 'zh-TW' ? '\u624B\u52D5\u4FEE\u5FA9:' : 'Manual fix:';
288
520
  console.log(c.dim(` ${fixLabel}`));
@@ -297,7 +529,6 @@ async function actionScan() {
297
529
  ? `${result.findings.length} \u500B\u554F\u984C`
298
530
  : `${result.findings.length} issue(s) found`;
299
531
  console.log(c.dim(` ${issuesText}`));
300
- // Upgrade prompt for free tier
301
532
  if (tier === 'community' && fixableCount > 0) {
302
533
  const upgradeLines = currentLang === 'zh-TW'
303
534
  ? [
@@ -315,7 +546,6 @@ async function actionScan() {
315
546
  : 'No security issues found';
316
547
  console.log(` ${c.safe(noIssues)}`);
317
548
  }
318
- // Next steps
319
549
  nextSteps(currentLang === 'zh-TW'
320
550
  ? [
321
551
  { cmd: 'guard start', desc: '\u555F\u52D5\u5373\u6642\u9632\u8B77' },
@@ -329,7 +559,7 @@ async function actionScan() {
329
559
  ], currentLang);
330
560
  }
331
561
  // ---------------------------------------------------------------------------
332
- // 2. Compliance Report
562
+ // [2] Compliance Report
333
563
  // ---------------------------------------------------------------------------
334
564
  async function actionReport() {
335
565
  breadcrumb([
@@ -375,7 +605,6 @@ async function actionReport() {
375
605
  console.log('');
376
606
  const { executeCli } = await import('@panguard-ai/panguard-report');
377
607
  await executeCli(['generate', '--framework', framework, '--language', reportLang]);
378
- // Next steps
379
608
  nextSteps(currentLang === 'zh-TW'
380
609
  ? [
381
610
  { cmd: 'scan', desc: '\u57F7\u884C\u5B89\u5168\u6383\u63CF' },
@@ -387,38 +616,24 @@ async function actionReport() {
387
616
  ], currentLang);
388
617
  }
389
618
  // ---------------------------------------------------------------------------
390
- // 3. Guard Engine
619
+ // [3] Guard Engine
391
620
  // ---------------------------------------------------------------------------
392
621
  async function actionGuard() {
393
622
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine']);
394
623
  const title = currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine';
395
624
  console.log(` ${theme.brandBold(title)}`);
396
- // Show current Guard running status
397
- const guardPidPath = join(homedir(), '.panguard-guard', 'panguard-guard.pid');
398
- let guardRunning = false;
399
- if (existsSync(guardPidPath)) {
400
- try {
401
- const pid = parseInt(readFileSync(guardPidPath, 'utf-8').trim(), 10);
402
- process.kill(pid, 0);
403
- guardRunning = true;
404
- }
405
- catch {
406
- // not running
407
- }
408
- }
625
+ const guardInfo = isGuardRunning();
626
+ const guardRunning = guardInfo.running;
409
627
  const statusText = guardRunning
410
628
  ? c.safe(currentLang === 'zh-TW' ? '\u904B\u884C\u4E2D' : 'Running')
411
629
  : c.caution(currentLang === 'zh-TW' ? '\u672A\u904B\u884C' : 'Not running');
412
630
  console.log(` ${c.dim(currentLang === 'zh-TW' ? '\u72C0\u614B' : 'Status')} ${statusText}`);
413
631
  console.log('');
414
632
  const items = [
415
- { key: '1', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u72C0\u614B' : 'Status' },
416
- { key: '2', label: currentLang === 'zh-TW' ? '\u555F\u52D5\u5F15\u64CE' : 'Start' },
417
- { key: '3', label: currentLang === 'zh-TW' ? '\u505C\u6B62\u5F15\u64CE' : 'Stop' },
418
- {
419
- key: '4',
420
- label: currentLang === 'zh-TW' ? '\u7522\u751F\u6E2C\u8A66\u91D1\u9470' : 'Generate Test Key',
421
- },
633
+ { key: '1', label: currentLang === 'zh-TW' ? '\u555F\u52D5\u5F15\u64CE Start' : 'Start' },
634
+ { key: '2', label: currentLang === 'zh-TW' ? '\u505C\u6B62\u5F15\u64CE Stop' : 'Stop' },
635
+ { key: '3', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u72C0\u614B Status' : 'Status' },
636
+ { key: '4', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u65E5\u8A8C Logs' : 'Logs' },
422
637
  ];
423
638
  renderCompactMenu(currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine', items);
424
639
  const choice = await waitForCompactChoice(items, currentLang);
@@ -427,11 +642,22 @@ async function actionGuard() {
427
642
  console.log('');
428
643
  const { runCLI } = await import('@panguard-ai/panguard-guard');
429
644
  switch (choice.key) {
430
- case '1':
431
- await runCLI(['status']);
432
- break;
433
- case '2': {
434
- // Free tier — show positive capabilities first, then dimmed upgrade hints
645
+ case '1': {
646
+ // Start guard engine
647
+ if (guardRunning) {
648
+ console.log(` ${c.safe('\u2713')} ${currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE\u5DF2\u5728\u904B\u884C\u4E2D' : 'Guard engine is already running'} ${c.dim(`(PID: ${guardInfo.pid})`)}`);
649
+ nextSteps(currentLang === 'zh-TW'
650
+ ? [
651
+ { cmd: 'guard > \u67E5\u770B\u72C0\u614B', desc: '\u67E5\u770B\u8A73\u7D30\u72C0\u614B' },
652
+ { cmd: 'guard > \u505C\u6B62\u5F15\u64CE', desc: '\u505C\u6B62\u9632\u8B77' },
653
+ ]
654
+ : [
655
+ { cmd: 'guard > Status', desc: 'View detailed status' },
656
+ { cmd: 'guard > Stop', desc: 'Stop protection' },
657
+ ], currentLang);
658
+ break;
659
+ }
660
+ // Free tier — show positive capabilities first
435
661
  const { tier } = getLicense();
436
662
  if (tier === 'community') {
437
663
  console.log(` ${c.sage('\u25C6')} Guard active${' '.repeat(30)}Layer 1 \u00B7 Community`);
@@ -452,8 +678,59 @@ async function actionGuard() {
452
678
  }
453
679
  console.log('');
454
680
  }
455
- await runCLI(['start']);
456
- // Next steps after guard start
681
+ // Spawn guard as background process
682
+ const { spawn: spawnProcess } = await import('node:child_process');
683
+ const { fileURLToPath: toPath } = await import('node:url');
684
+ const guardMainUrl = import.meta.resolve('@panguard-ai/panguard-guard');
685
+ const guardCliScript = join(toPath(guardMainUrl), '..', 'cli', 'index.js');
686
+ const guardDataDir = join(homedir(), '.panguard-guard');
687
+ if (!existsSync(guardDataDir))
688
+ mkdirSync(guardDataDir, { recursive: true });
689
+ const logPath = join(guardDataDir, 'guard.log');
690
+ const logFd = openSync(logPath, 'a');
691
+ const guardSp = spinner(currentLang === 'zh-TW'
692
+ ? '\u6B63\u5728\u555F\u52D5\u5B88\u8B77\u5F15\u64CE...'
693
+ : 'Starting guard engine...');
694
+ const child = spawnProcess(process.execPath, [guardCliScript, 'start'], {
695
+ detached: true,
696
+ stdio: ['ignore', logFd, logFd],
697
+ env: { ...process.env },
698
+ });
699
+ child.unref();
700
+ closeSync(logFd);
701
+ // Wait for PID file to confirm startup
702
+ const pidPath = join(guardDataDir, 'panguard-guard.pid');
703
+ let started = false;
704
+ const deadline = Date.now() + 5000;
705
+ while (Date.now() < deadline) {
706
+ if (existsSync(pidPath)) {
707
+ try {
708
+ const newPid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
709
+ process.kill(newPid, 0);
710
+ started = true;
711
+ break;
712
+ }
713
+ catch {
714
+ /* not yet */
715
+ }
716
+ }
717
+ await new Promise((resolve) => setTimeout(resolve, 300));
718
+ }
719
+ if (started) {
720
+ const newPid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
721
+ guardSp.succeed(currentLang === 'zh-TW'
722
+ ? '\u5B88\u8B77\u5F15\u64CE\u5DF2\u555F\u52D5'
723
+ : 'Guard engine started');
724
+ console.log('');
725
+ console.log(` ${c.safe('\u2713')} ${currentLang === 'zh-TW' ? '\u7CFB\u7D71\u5DF2\u53D7\u4FDD\u8B77' : 'System is now protected'} ${c.dim(`(PID: ${newPid})`)}`);
726
+ console.log(` ${c.dim(currentLang === 'zh-TW' ? ' \u65E5\u8A8C: ~/.panguard-guard/guard.log' : ' Logs: ~/.panguard-guard/guard.log')}`);
727
+ }
728
+ else {
729
+ guardSp.fail(currentLang === 'zh-TW'
730
+ ? '\u5B88\u8B77\u5F15\u64CE\u555F\u52D5\u5931\u6557'
731
+ : 'Failed to start guard engine');
732
+ console.log(` ${c.dim(currentLang === 'zh-TW' ? ' \u67E5\u770B\u65E5\u8A8C: ~/.panguard-guard/guard.log' : ' Check logs: ~/.panguard-guard/guard.log')}`);
733
+ }
457
734
  nextSteps(currentLang === 'zh-TW'
458
735
  ? [
459
736
  { cmd: 'scan', desc: '\u57F7\u884C\u5B89\u5168\u6383\u63CF' },
@@ -465,8 +742,7 @@ async function actionGuard() {
465
742
  ], currentLang);
466
743
  break;
467
744
  }
468
- case '3': {
469
- // Confirm before stopping protection
745
+ case '2': {
470
746
  const confirmed = await confirmDestructive(currentLang === 'zh-TW'
471
747
  ? '\u78BA\u5B9A\u8981\u505C\u6B62\u5373\u6642\u9632\u8B77\uFF1F'
472
748
  : 'Stop real-time protection?', currentLang);
@@ -478,13 +754,29 @@ async function actionGuard() {
478
754
  }
479
755
  break;
480
756
  }
481
- case '4':
482
- await runCLI(['generate-key', 'pro']);
757
+ case '3':
758
+ await runCLI(['status']);
759
+ break;
760
+ case '4': {
761
+ const logFilePath = join(homedir(), '.panguard-guard', 'guard.log');
762
+ if (existsSync(logFilePath)) {
763
+ const content = readFileSync(logFilePath, 'utf-8');
764
+ const lines = content.trim().split('\n').slice(-30);
765
+ console.log(c.dim(currentLang === 'zh-TW' ? ' \u6700\u8FD1 30 \u884C\u65E5\u8A8C:' : ' Last 30 log lines:'));
766
+ console.log('');
767
+ for (const line of lines) {
768
+ console.log(` ${c.dim(line)}`);
769
+ }
770
+ }
771
+ else {
772
+ console.log(c.dim(currentLang === 'zh-TW' ? ' \u5C1A\u7121\u65E5\u8A8C\u6A94\u6848' : ' No log file found'));
773
+ }
483
774
  break;
775
+ }
484
776
  }
485
777
  }
486
778
  // ---------------------------------------------------------------------------
487
- // 4. Honeypot System
779
+ // [4] Honeypot System
488
780
  // ---------------------------------------------------------------------------
489
781
  async function actionTrap() {
490
782
  breadcrumb([
@@ -533,7 +825,7 @@ async function actionTrap() {
533
825
  }
534
826
  }
535
827
  // ---------------------------------------------------------------------------
536
- // 5. Notifications
828
+ // [5] Notifications
537
829
  // ---------------------------------------------------------------------------
538
830
  async function actionChat() {
539
831
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u901A\u77E5\u7CFB\u7D71' : 'Notifications']);
@@ -541,37 +833,36 @@ async function actionChat() {
541
833
  console.log(` ${theme.brandBold(title)}`);
542
834
  console.log('');
543
835
  const items = [
544
- { key: '1', label: currentLang === 'zh-TW' ? '\u8A2D\u5B9A\u901A\u77E5' : 'Setup' },
836
+ { key: '1', label: currentLang === 'zh-TW' ? '\u8A2D\u5B9A\u901A\u77E5\u7BA1\u9053' : 'Setup Channels' },
545
837
  { key: '2', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u72C0\u614B' : 'Status' },
546
- { key: '3', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u914D\u7F6E' : 'Config' },
838
+ { key: '3', label: currentLang === 'zh-TW' ? '\u6E2C\u8A66\u767C\u9001' : 'Test Send' },
547
839
  ];
548
840
  renderCompactMenu(currentLang === 'zh-TW' ? '\u901A\u77E5\u7CFB\u7D71' : 'Notifications', items);
549
841
  const choice = await waitForCompactChoice(items, currentLang);
550
842
  if (!choice)
551
843
  return;
552
844
  console.log('');
553
- const { runCLI } = await import('@panguard-ai/panguard-chat');
845
+ const { runCLI: chatRunCLI } = await import('@panguard-ai/panguard-chat');
554
846
  switch (choice.key) {
555
847
  case '1':
556
- await runCLI(['setup', '--lang', currentLang]);
848
+ await chatRunCLI(['setup', '--lang', currentLang]);
557
849
  break;
558
850
  case '2':
559
- await runCLI(['status']);
851
+ await chatRunCLI(['status']);
560
852
  break;
561
853
  case '3':
562
- await runCLI(['config']);
854
+ await chatRunCLI(['config']);
563
855
  break;
564
856
  }
565
857
  }
566
858
  // ---------------------------------------------------------------------------
567
- // 6. Threat Cloud
859
+ // [6] Threat Cloud
568
860
  // ---------------------------------------------------------------------------
569
861
  async function actionThreat() {
570
862
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5A01\u8105\u60C5\u5831' : 'Threat Cloud']);
571
863
  const title = currentLang === 'zh-TW' ? '\u5A01\u8105\u60C5\u5831' : 'Threat Cloud';
572
864
  console.log(` ${theme.brandBold(title)}`);
573
865
  console.log('');
574
- // Port availability check
575
866
  const port = 8080;
576
867
  const net = await import('node:net');
577
868
  const portAvailable = await new Promise((resolve) => {
@@ -583,9 +874,11 @@ async function actionThreat() {
583
874
  tester.listen(port, '127.0.0.1');
584
875
  });
585
876
  if (!portAvailable) {
586
- console.log(` ${c.critical(currentLang === 'zh-TW'
587
- ? `\u9023\u63A5\u57E0 ${port} \u5DF2\u88AB\u4F54\u7528\u3002\u8ACB\u91CB\u653E\u8A72\u57E0\u5F8C\u518D\u8A66\u3002`
588
- : `Port ${port} is already in use. Please free it and try again.`)}`);
877
+ console.log(formatError(currentLang === 'zh-TW'
878
+ ? `\u9023\u63A5\u57E0 ${port} \u5DF2\u88AB\u4F54\u7528`
879
+ : `Port ${port} is already in use`, `Port ${port}`, currentLang === 'zh-TW'
880
+ ? `\u91CB\u653E\u9023\u63A5\u57E0 ${port} \u5F8C\u518D\u8A66`
881
+ : `Free port ${port} and try again`));
589
882
  return;
590
883
  }
591
884
  console.log(c.dim(currentLang === 'zh-TW'
@@ -624,7 +917,7 @@ async function actionThreat() {
624
917
  }
625
918
  }
626
919
  // ---------------------------------------------------------------------------
627
- // 7. Auto Demo
920
+ // [7] Auto Demo
628
921
  // ---------------------------------------------------------------------------
629
922
  async function actionDemo() {
630
923
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u529F\u80FD\u5C55\u793A' : 'Feature Demo']);
@@ -785,7 +1078,7 @@ async function actionDemo() {
785
1078
  });
786
1079
  }
787
1080
  console.log('');
788
- // Summary — show real results
1081
+ // Summary
789
1082
  console.log(` ${theme.brandBold(currentLang === 'zh-TW' ? '\u5C55\u793A\u5B8C\u6210' : 'Demo Complete')}`);
790
1083
  console.log('');
791
1084
  for (const r of results) {
@@ -796,7 +1089,6 @@ async function actionDemo() {
796
1089
  : c.critical('\u2717');
797
1090
  console.log(` ${icon} ${r.name}`);
798
1091
  }
799
- // Next steps
800
1092
  nextSteps(currentLang === 'zh-TW'
801
1093
  ? [
802
1094
  { cmd: 'init', desc: '\u521D\u59CB\u8A2D\u5B9A\u60A8\u7684\u74B0\u5883' },
@@ -810,13 +1102,71 @@ async function actionDemo() {
810
1102
  ], currentLang);
811
1103
  }
812
1104
  // ---------------------------------------------------------------------------
813
- // 8. Upgrade Plan
1105
+ // Prompt commands: Upgrade
814
1106
  // ---------------------------------------------------------------------------
815
1107
  async function actionUpgrade() {
816
1108
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5347\u7D1A\u65B9\u6848' : 'Upgrade Plan']);
817
- // Delegate to the upgrade command module
818
1109
  const { upgradeCommand } = await import('./commands/upgrade.js');
819
1110
  const cmd = upgradeCommand();
820
1111
  await cmd.parseAsync(['upgrade', '--lang', currentLang], { from: 'user' });
821
1112
  }
1113
+ // ---------------------------------------------------------------------------
1114
+ // Prompt commands: Hardening
1115
+ // ---------------------------------------------------------------------------
1116
+ async function actionHardening() {
1117
+ breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B89\u5168\u52A0\u56FA' : 'Security Hardening']);
1118
+ const title = currentLang === 'zh-TW' ? '\u5B89\u5168\u52A0\u56FA' : 'Security Hardening';
1119
+ console.log(` ${theme.brandBold(title)}`);
1120
+ console.log('');
1121
+ const items = [
1122
+ { key: '1', label: currentLang === 'zh-TW' ? '\u57F7\u884C\u52A0\u56FA\u6383\u63CF' : 'Run hardening audit' },
1123
+ { key: '2', label: currentLang === 'zh-TW' ? '\u81EA\u52D5\u4FEE\u5FA9' : 'Auto-fix issues' },
1124
+ { key: '3', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u5831\u544A' : 'View report' },
1125
+ ];
1126
+ renderCompactMenu(currentLang === 'zh-TW' ? '\u5B89\u5168\u52A0\u56FA' : 'Hardening', items);
1127
+ const choice = await waitForCompactChoice(items, currentLang);
1128
+ if (!choice)
1129
+ return;
1130
+ console.log('');
1131
+ const { hardeningCommand } = await import('./commands/hardening.js');
1132
+ const cmd = hardeningCommand();
1133
+ switch (choice.key) {
1134
+ case '1':
1135
+ await cmd.parseAsync(['hardening', 'audit'], { from: 'user' });
1136
+ break;
1137
+ case '2':
1138
+ await cmd.parseAsync(['hardening', 'fix'], { from: 'user' });
1139
+ break;
1140
+ case '3':
1141
+ await cmd.parseAsync(['hardening', 'report'], { from: 'user' });
1142
+ break;
1143
+ }
1144
+ }
1145
+ // ---------------------------------------------------------------------------
1146
+ // Prompt commands: Status
1147
+ // ---------------------------------------------------------------------------
1148
+ async function actionStatus() {
1149
+ breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u7CFB\u7D71\u72C0\u614B' : 'Status']);
1150
+ const { statusCommand } = await import('./commands/status.js');
1151
+ const cmd = statusCommand();
1152
+ await cmd.parseAsync(['status'], { from: 'user' });
1153
+ }
1154
+ // ---------------------------------------------------------------------------
1155
+ // Prompt commands: Login
1156
+ // ---------------------------------------------------------------------------
1157
+ async function actionLogin() {
1158
+ breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5E33\u865F\u767B\u5165' : 'Login']);
1159
+ const { loginCommand } = await import('./commands/login.js');
1160
+ const cmd = loginCommand();
1161
+ await cmd.parseAsync(['login'], { from: 'user' });
1162
+ }
1163
+ // ---------------------------------------------------------------------------
1164
+ // Prompt commands: Config
1165
+ // ---------------------------------------------------------------------------
1166
+ async function actionConfig() {
1167
+ breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u8A2D\u5B9A\u7BA1\u7406' : 'Settings']);
1168
+ const { configCommand } = await import('./commands/config.js');
1169
+ const cmd = configCommand();
1170
+ await cmd.parseAsync(['config'], { from: 'user' });
1171
+ }
822
1172
  //# sourceMappingURL=interactive.js.map