@panguard-ai/panguard 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bridges/scan-to-report.d.ts +34 -0
  3. package/dist/bridges/scan-to-report.d.ts.map +1 -0
  4. package/dist/bridges/scan-to-report.js +28 -0
  5. package/dist/bridges/scan-to-report.js.map +1 -0
  6. package/dist/cli/auth-guard.d.ts +3 -2
  7. package/dist/cli/auth-guard.d.ts.map +1 -1
  8. package/dist/cli/auth-guard.js +6 -18
  9. package/dist/cli/auth-guard.js.map +1 -1
  10. package/dist/cli/commands/audit.d.ts +7 -0
  11. package/dist/cli/commands/audit.d.ts.map +1 -0
  12. package/dist/cli/commands/audit.js +85 -0
  13. package/dist/cli/commands/audit.js.map +1 -0
  14. package/dist/cli/commands/deploy.js +34 -1
  15. package/dist/cli/commands/deploy.js.map +1 -1
  16. package/dist/cli/commands/doctor.d.ts +22 -0
  17. package/dist/cli/commands/doctor.d.ts.map +1 -0
  18. package/dist/cli/commands/doctor.js +502 -0
  19. package/dist/cli/commands/doctor.js.map +1 -0
  20. package/dist/cli/commands/guard.d.ts.map +1 -1
  21. package/dist/cli/commands/guard.js +209 -0
  22. package/dist/cli/commands/guard.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +17 -8
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/commands/login.js +5 -2
  27. package/dist/cli/commands/login.js.map +1 -1
  28. package/dist/cli/commands/manager.d.ts.map +1 -1
  29. package/dist/cli/commands/manager.js +43 -34
  30. package/dist/cli/commands/manager.js.map +1 -1
  31. package/dist/cli/commands/scan.d.ts.map +1 -1
  32. package/dist/cli/commands/scan.js +102 -8
  33. package/dist/cli/commands/scan.js.map +1 -1
  34. package/dist/cli/commands/serve.d.ts.map +1 -1
  35. package/dist/cli/commands/serve.js +173 -4
  36. package/dist/cli/commands/serve.js.map +1 -1
  37. package/dist/cli/commands/threat.d.ts.map +1 -1
  38. package/dist/cli/commands/threat.js +8 -1
  39. package/dist/cli/commands/threat.js.map +1 -1
  40. package/dist/cli/credentials.d.ts +2 -2
  41. package/dist/cli/credentials.d.ts.map +1 -1
  42. package/dist/cli/credentials.js +3 -7
  43. package/dist/cli/credentials.js.map +1 -1
  44. package/dist/cli/index.js +4 -0
  45. package/dist/cli/index.js.map +1 -1
  46. package/dist/cli/interactive.d.ts +2 -2
  47. package/dist/cli/interactive.d.ts.map +1 -1
  48. package/dist/cli/interactive.js +450 -211
  49. package/dist/cli/interactive.js.map +1 -1
  50. package/dist/cli/menu.d.ts +25 -15
  51. package/dist/cli/menu.d.ts.map +1 -1
  52. package/dist/cli/menu.js +117 -124
  53. package/dist/cli/menu.js.map +1 -1
  54. package/dist/cli/theme.d.ts +1 -1
  55. package/dist/cli/theme.d.ts.map +1 -1
  56. package/dist/cli/theme.js +1 -2
  57. package/dist/cli/theme.js.map +1 -1
  58. package/dist/cli/ux-helpers.d.ts +4 -0
  59. package/dist/cli/ux-helpers.d.ts.map +1 -1
  60. package/dist/cli/ux-helpers.js +10 -0
  61. package/dist/cli/ux-helpers.js.map +1 -1
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +2 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/init/config-writer.d.ts +12 -1
  67. package/dist/init/config-writer.d.ts.map +1 -1
  68. package/dist/init/config-writer.js +149 -6
  69. package/dist/init/config-writer.js.map +1 -1
  70. package/dist/init/environment.d.ts +9 -0
  71. package/dist/init/environment.d.ts.map +1 -1
  72. package/dist/init/environment.js +127 -0
  73. package/dist/init/environment.js.map +1 -1
  74. package/dist/init/index.d.ts +4 -4
  75. package/dist/init/index.d.ts.map +1 -1
  76. package/dist/init/index.js +3 -3
  77. package/dist/init/index.js.map +1 -1
  78. package/dist/init/steps.d.ts +5 -0
  79. package/dist/init/steps.d.ts.map +1 -1
  80. package/dist/init/steps.js +155 -1
  81. package/dist/init/steps.js.map +1 -1
  82. package/dist/init/types.d.ts +13 -0
  83. package/dist/init/types.d.ts.map +1 -1
  84. package/dist/init/wizard-runner.d.ts +3 -2
  85. package/dist/init/wizard-runner.d.ts.map +1 -1
  86. package/dist/init/wizard-runner.js +30 -15
  87. package/dist/init/wizard-runner.js.map +1 -1
  88. package/package.json +8 -6
  89. package/dist/manager/manager-server.d.ts +0 -102
  90. package/dist/manager/manager-server.d.ts.map +0 -1
  91. package/dist/manager/manager-server.js +0 -515
  92. 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,47 +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
- { key: 'hardening', en: 'Security hardening', zh: '\u5B89\u5168\u52A0\u56FA', tier: 'community' },
62
56
  {
63
- key: 'threat-cloud',
64
- en: 'Threat intelligence API',
65
- zh: '\u5A01\u8105\u60C5\u5831 API',
66
- 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',
67
66
  },
68
67
  {
69
- key: 'notify',
70
- en: 'Notifications (Telegram, Slack)',
71
- zh: '\u901A\u77E5\u7CFB\u7D71 (Telegram, Slack)',
72
- 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',
73
77
  },
74
- { key: 'trap', en: 'Honeypot system', zh: '\u871C\u7F50\u7CFB\u7D71', tier: 'solo' },
75
78
  {
76
79
  key: 'report',
77
- en: 'Compliance report (ISO 27001, SOC 2)',
78
- zh: '\u5408\u898F\u5831\u544A (ISO 27001, SOC 2)',
79
- 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',
80
143
  },
81
- { key: 'manager', en: 'Distributed manager', zh: '\u5206\u6563\u5F0F\u7BA1\u7406', tier: 'business' },
82
- { key: '__sep__', en: '', zh: '', tier: '' },
83
- { key: 'status', en: 'System status', zh: '\u7CFB\u7D71\u72C0\u614B', tier: 'community' },
84
- { key: 'login', en: 'Account login', zh: '\u5E33\u865F\u767B\u5165', tier: 'community' },
85
- { key: 'config', en: 'Settings', zh: '\u8A2D\u5B9A\u7BA1\u7406', tier: 'community' },
86
- { key: 'setup', en: 'Initial configuration', zh: '\u521D\u59CB\u8A2D\u5B9A', tier: 'community' },
87
- { key: 'demo', en: 'Feature demo', zh: '\u529F\u80FD\u5C55\u793A', tier: 'community' },
88
- { key: 'upgrade', en: 'Upgrade plan', zh: '\u5347\u7D1A\u65B9\u6848', tier: 'community' },
89
144
  ];
90
- function buildMenuItems(lang) {
91
- return MENU_DEFS.map((def) => {
92
- if (def.key === '__sep__')
93
- return { key: '__sep__', label: '', separator: true };
94
- return {
95
- key: def.key,
96
- label: lang === 'zh-TW' ? def.zh : def.en,
97
- tier: def.tier || undefined,
98
- };
99
- });
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('');
100
251
  }
101
252
  // ---------------------------------------------------------------------------
102
253
  // Startup screen
@@ -108,28 +259,15 @@ function renderStartup() {
108
259
  console.log('');
109
260
  // Tagline + version
110
261
  const tagline = c.dim(' AI-Powered Security Platform');
111
- const version = c.dim(`v${PANGUARD_VERSION}`);
112
- // Right-align version
113
- // eslint-disable-next-line no-control-regex
114
- const tagLen = tagline.replace(/\x1b\[[0-9;]*m/g, '').length;
115
- // eslint-disable-next-line no-control-regex
116
- const verLen = version.replace(/\x1b\[[0-9;]*m/g, '').length;
117
- const totalWidth = Math.max(60, process.stdout.columns ?? 60);
118
- const gap = Math.max(2, totalWidth - tagLen - verLen - 4);
119
- console.log(`${tagline}${' '.repeat(gap)}${version}`);
120
- console.log('');
121
- // Status info
122
- const { tier } = getLicense();
123
- const tierName = tierDisplayName(tier);
124
- const tierColor = tier === 'community' ? c.caution : c.safe;
125
- const statusLabel = currentLang === 'zh-TW' ? '\u72C0\u614B' : 'Status';
126
- const licenseLabel = currentLang === 'zh-TW' ? '\u6388\u6B0A' : 'License';
127
- const modulesLabel = currentLang === 'zh-TW' ? '\u6A21\u7D44' : 'Modules';
128
- const modulesValue = moduleCountDisplay(currentLang);
129
- console.log(` ${c.dim(statusLabel.padEnd(10))} ${c.safe('Ready')}`);
130
- console.log(` ${c.dim(licenseLabel.padEnd(10))} ${tierColor(tierName)}`);
131
- console.log(` ${c.dim(modulesLabel.padEnd(10))} ${modulesValue}`);
262
+ console.log(tagline);
132
263
  console.log('');
264
+ // Status panel
265
+ renderStatusPanel();
266
+ console.log('');
267
+ // Menu
268
+ renderMenu();
269
+ // Footer
270
+ renderFooter();
133
271
  }
134
272
  // ---------------------------------------------------------------------------
135
273
  // Entry point
@@ -148,61 +286,159 @@ export async function startInteractive(lang) {
148
286
  // First-time user hint
149
287
  if (!existsSync(CONFIG_PATH)) {
150
288
  const hint = currentLang === 'zh-TW'
151
- ? ` ${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`
152
- : ` ${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`;
153
291
  console.log(hint);
154
292
  console.log('');
155
293
  }
156
294
  // Main loop
157
295
  while (true) {
158
- const title = currentLang === 'zh-TW' ? ' \u6307\u4EE4' : ' Commands';
159
- console.log(theme.title(title));
160
- console.log('');
161
- const items = buildMenuItems(currentLang);
162
- const choice = await runArrowMenu(items, { lang: currentLang });
163
- if (!choice) {
164
- exit();
165
- 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
+ }
166
350
  }
167
- if (choice.key === '__lang__') {
168
- currentLang = currentLang === 'zh-TW' ? 'en' : 'zh-TW';
169
- 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));
170
362
  renderStartup();
171
- continue;
172
- }
173
- // Feature gate check
174
- if (!checkFeatureAccess(choice.key)) {
175
- showUpgradePrompt(choice.key, currentLang);
176
- await pressAnyKey(currentLang);
363
+ return true;
364
+ case 'login':
365
+ console.clear();
366
+ await actionLogin();
367
+ await new Promise((r) => setTimeout(r, 500));
177
368
  renderStartup();
178
- continue;
179
- }
180
- console.clear();
181
- try {
182
- 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;
183
378
  }
184
- catch (err) {
185
- console.log('');
186
- 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;
187
417
  }
188
- await pressAnyKey(currentLang);
189
- renderStartup();
418
+ case 'help':
419
+ showHelp();
420
+ return true;
421
+ default:
422
+ return false;
190
423
  }
191
424
  }
192
425
  // ---------------------------------------------------------------------------
193
- // Action dispatch
426
+ // Action dispatch (from numbered menu)
194
427
  // ---------------------------------------------------------------------------
195
428
  async function dispatch(key) {
196
429
  switch (key) {
430
+ case 'setup':
431
+ await actionInit();
432
+ break;
197
433
  case 'scan':
198
434
  await actionScan();
199
435
  break;
200
- case 'guard':
201
- await actionGuard();
202
- break;
203
436
  case 'report':
204
437
  await actionReport();
205
438
  break;
439
+ case 'guard':
440
+ await actionGuard();
441
+ break;
206
442
  case 'trap':
207
443
  await actionTrap();
208
444
  break;
@@ -212,34 +448,13 @@ async function dispatch(key) {
212
448
  case 'threat-cloud':
213
449
  await actionThreat();
214
450
  break;
215
- case 'setup':
216
- await actionInit();
217
- break;
218
451
  case 'demo':
219
452
  await actionDemo();
220
453
  break;
221
- case 'upgrade':
222
- await actionUpgrade();
223
- break;
224
- case 'hardening':
225
- await actionHardening();
226
- break;
227
- case 'manager':
228
- await actionManager();
229
- break;
230
- case 'status':
231
- await actionStatus();
232
- break;
233
- case 'login':
234
- await actionLogin();
235
- break;
236
- case 'config':
237
- await actionConfig();
238
- break;
239
454
  }
240
455
  }
241
456
  // ---------------------------------------------------------------------------
242
- // 0. Setup Wizard
457
+ // [0] Setup Wizard
243
458
  // ---------------------------------------------------------------------------
244
459
  async function actionInit() {
245
460
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u521D\u59CB\u8A2D\u5B9A' : 'Setup']);
@@ -247,14 +462,13 @@ async function actionInit() {
247
462
  await runInitWizard(currentLang);
248
463
  }
249
464
  // ---------------------------------------------------------------------------
250
- // 1. Security Scan
465
+ // [1] Security Scan
251
466
  // ---------------------------------------------------------------------------
252
467
  async function actionScan() {
253
468
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B89\u5168\u6383\u63CF' : 'Security Scan']);
254
469
  const scanTitle = currentLang === 'zh-TW' ? '\u5B89\u5168\u6383\u63CF' : 'Security Scan';
255
470
  console.log(` ${theme.brandBold(scanTitle)}`);
256
471
  console.log('');
257
- // Scan depth selection
258
472
  const depthItems = [
259
473
  {
260
474
  key: '1',
@@ -291,7 +505,6 @@ async function actionScan() {
291
505
  : safetyScore >= 40
292
506
  ? 'D'
293
507
  : 'F';
294
- // Compact score display
295
508
  console.log('');
296
509
  const scoreLabel = currentLang === 'zh-TW' ? '\u6383\u63CF\u5B8C\u6210' : 'Scan Complete';
297
510
  console.log(` ${theme.title(scoreLabel)}${' '.repeat(30)}Score: ${c.bold(`${safetyScore}/100`)} (${grade})`);
@@ -302,7 +515,6 @@ async function actionScan() {
302
515
  for (const f of result.findings) {
303
516
  const sev = colorSeverity(f.severity).padEnd(10);
304
517
  console.log(` ${sev} ${f.title}`);
305
- // Show manual fix commands for free tier
306
518
  if (tier === 'community' && f.manualFix && f.manualFix.length > 0) {
307
519
  const fixLabel = currentLang === 'zh-TW' ? '\u624B\u52D5\u4FEE\u5FA9:' : 'Manual fix:';
308
520
  console.log(c.dim(` ${fixLabel}`));
@@ -317,7 +529,6 @@ async function actionScan() {
317
529
  ? `${result.findings.length} \u500B\u554F\u984C`
318
530
  : `${result.findings.length} issue(s) found`;
319
531
  console.log(c.dim(` ${issuesText}`));
320
- // Upgrade prompt for free tier
321
532
  if (tier === 'community' && fixableCount > 0) {
322
533
  const upgradeLines = currentLang === 'zh-TW'
323
534
  ? [
@@ -335,7 +546,6 @@ async function actionScan() {
335
546
  : 'No security issues found';
336
547
  console.log(` ${c.safe(noIssues)}`);
337
548
  }
338
- // Next steps
339
549
  nextSteps(currentLang === 'zh-TW'
340
550
  ? [
341
551
  { cmd: 'guard start', desc: '\u555F\u52D5\u5373\u6642\u9632\u8B77' },
@@ -349,7 +559,7 @@ async function actionScan() {
349
559
  ], currentLang);
350
560
  }
351
561
  // ---------------------------------------------------------------------------
352
- // 2. Compliance Report
562
+ // [2] Compliance Report
353
563
  // ---------------------------------------------------------------------------
354
564
  async function actionReport() {
355
565
  breadcrumb([
@@ -395,7 +605,6 @@ async function actionReport() {
395
605
  console.log('');
396
606
  const { executeCli } = await import('@panguard-ai/panguard-report');
397
607
  await executeCli(['generate', '--framework', framework, '--language', reportLang]);
398
- // Next steps
399
608
  nextSteps(currentLang === 'zh-TW'
400
609
  ? [
401
610
  { cmd: 'scan', desc: '\u57F7\u884C\u5B89\u5168\u6383\u63CF' },
@@ -407,38 +616,24 @@ async function actionReport() {
407
616
  ], currentLang);
408
617
  }
409
618
  // ---------------------------------------------------------------------------
410
- // 3. Guard Engine
619
+ // [3] Guard Engine
411
620
  // ---------------------------------------------------------------------------
412
621
  async function actionGuard() {
413
622
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine']);
414
623
  const title = currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine';
415
624
  console.log(` ${theme.brandBold(title)}`);
416
- // Show current Guard running status
417
- const guardPidPath = join(homedir(), '.panguard-guard', 'panguard-guard.pid');
418
- let guardRunning = false;
419
- if (existsSync(guardPidPath)) {
420
- try {
421
- const pid = parseInt(readFileSync(guardPidPath, 'utf-8').trim(), 10);
422
- process.kill(pid, 0);
423
- guardRunning = true;
424
- }
425
- catch {
426
- // not running
427
- }
428
- }
625
+ const guardInfo = isGuardRunning();
626
+ const guardRunning = guardInfo.running;
429
627
  const statusText = guardRunning
430
628
  ? c.safe(currentLang === 'zh-TW' ? '\u904B\u884C\u4E2D' : 'Running')
431
629
  : c.caution(currentLang === 'zh-TW' ? '\u672A\u904B\u884C' : 'Not running');
432
630
  console.log(` ${c.dim(currentLang === 'zh-TW' ? '\u72C0\u614B' : 'Status')} ${statusText}`);
433
631
  console.log('');
434
632
  const items = [
435
- { key: '1', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u72C0\u614B' : 'Status' },
436
- { key: '2', label: currentLang === 'zh-TW' ? '\u555F\u52D5\u5F15\u64CE' : 'Start' },
437
- { key: '3', label: currentLang === 'zh-TW' ? '\u505C\u6B62\u5F15\u64CE' : 'Stop' },
438
- {
439
- key: '4',
440
- label: currentLang === 'zh-TW' ? '\u7522\u751F\u6E2C\u8A66\u91D1\u9470' : 'Generate Test Key',
441
- },
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' },
442
637
  ];
443
638
  renderCompactMenu(currentLang === 'zh-TW' ? '\u5B88\u8B77\u5F15\u64CE' : 'Guard Engine', items);
444
639
  const choice = await waitForCompactChoice(items, currentLang);
@@ -447,11 +642,22 @@ async function actionGuard() {
447
642
  console.log('');
448
643
  const { runCLI } = await import('@panguard-ai/panguard-guard');
449
644
  switch (choice.key) {
450
- case '1':
451
- await runCLI(['status']);
452
- break;
453
- case '2': {
454
- // 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
455
661
  const { tier } = getLicense();
456
662
  if (tier === 'community') {
457
663
  console.log(` ${c.sage('\u25C6')} Guard active${' '.repeat(30)}Layer 1 \u00B7 Community`);
@@ -472,8 +678,59 @@ async function actionGuard() {
472
678
  }
473
679
  console.log('');
474
680
  }
475
- await runCLI(['start']);
476
- // 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
+ }
477
734
  nextSteps(currentLang === 'zh-TW'
478
735
  ? [
479
736
  { cmd: 'scan', desc: '\u57F7\u884C\u5B89\u5168\u6383\u63CF' },
@@ -485,8 +742,7 @@ async function actionGuard() {
485
742
  ], currentLang);
486
743
  break;
487
744
  }
488
- case '3': {
489
- // Confirm before stopping protection
745
+ case '2': {
490
746
  const confirmed = await confirmDestructive(currentLang === 'zh-TW'
491
747
  ? '\u78BA\u5B9A\u8981\u505C\u6B62\u5373\u6642\u9632\u8B77\uFF1F'
492
748
  : 'Stop real-time protection?', currentLang);
@@ -498,13 +754,29 @@ async function actionGuard() {
498
754
  }
499
755
  break;
500
756
  }
501
- case '4':
502
- 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
+ }
503
774
  break;
775
+ }
504
776
  }
505
777
  }
506
778
  // ---------------------------------------------------------------------------
507
- // 4. Honeypot System
779
+ // [4] Honeypot System
508
780
  // ---------------------------------------------------------------------------
509
781
  async function actionTrap() {
510
782
  breadcrumb([
@@ -553,7 +825,7 @@ async function actionTrap() {
553
825
  }
554
826
  }
555
827
  // ---------------------------------------------------------------------------
556
- // 5. Notifications
828
+ // [5] Notifications
557
829
  // ---------------------------------------------------------------------------
558
830
  async function actionChat() {
559
831
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u901A\u77E5\u7CFB\u7D71' : 'Notifications']);
@@ -561,37 +833,36 @@ async function actionChat() {
561
833
  console.log(` ${theme.brandBold(title)}`);
562
834
  console.log('');
563
835
  const items = [
564
- { 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' },
565
837
  { key: '2', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u72C0\u614B' : 'Status' },
566
- { key: '3', label: currentLang === 'zh-TW' ? '\u67E5\u770B\u914D\u7F6E' : 'Config' },
838
+ { key: '3', label: currentLang === 'zh-TW' ? '\u6E2C\u8A66\u767C\u9001' : 'Test Send' },
567
839
  ];
568
840
  renderCompactMenu(currentLang === 'zh-TW' ? '\u901A\u77E5\u7CFB\u7D71' : 'Notifications', items);
569
841
  const choice = await waitForCompactChoice(items, currentLang);
570
842
  if (!choice)
571
843
  return;
572
844
  console.log('');
573
- const { runCLI } = await import('@panguard-ai/panguard-chat');
845
+ const { runCLI: chatRunCLI } = await import('@panguard-ai/panguard-chat');
574
846
  switch (choice.key) {
575
847
  case '1':
576
- await runCLI(['setup', '--lang', currentLang]);
848
+ await chatRunCLI(['setup', '--lang', currentLang]);
577
849
  break;
578
850
  case '2':
579
- await runCLI(['status']);
851
+ await chatRunCLI(['status']);
580
852
  break;
581
853
  case '3':
582
- await runCLI(['config']);
854
+ await chatRunCLI(['config']);
583
855
  break;
584
856
  }
585
857
  }
586
858
  // ---------------------------------------------------------------------------
587
- // 6. Threat Cloud
859
+ // [6] Threat Cloud
588
860
  // ---------------------------------------------------------------------------
589
861
  async function actionThreat() {
590
862
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5A01\u8105\u60C5\u5831' : 'Threat Cloud']);
591
863
  const title = currentLang === 'zh-TW' ? '\u5A01\u8105\u60C5\u5831' : 'Threat Cloud';
592
864
  console.log(` ${theme.brandBold(title)}`);
593
865
  console.log('');
594
- // Port availability check
595
866
  const port = 8080;
596
867
  const net = await import('node:net');
597
868
  const portAvailable = await new Promise((resolve) => {
@@ -603,9 +874,11 @@ async function actionThreat() {
603
874
  tester.listen(port, '127.0.0.1');
604
875
  });
605
876
  if (!portAvailable) {
606
- console.log(` ${c.critical(currentLang === 'zh-TW'
607
- ? `\u9023\u63A5\u57E0 ${port} \u5DF2\u88AB\u4F54\u7528\u3002\u8ACB\u91CB\u653E\u8A72\u57E0\u5F8C\u518D\u8A66\u3002`
608
- : `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`));
609
882
  return;
610
883
  }
611
884
  console.log(c.dim(currentLang === 'zh-TW'
@@ -644,7 +917,7 @@ async function actionThreat() {
644
917
  }
645
918
  }
646
919
  // ---------------------------------------------------------------------------
647
- // 7. Auto Demo
920
+ // [7] Auto Demo
648
921
  // ---------------------------------------------------------------------------
649
922
  async function actionDemo() {
650
923
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u529F\u80FD\u5C55\u793A' : 'Feature Demo']);
@@ -805,7 +1078,7 @@ async function actionDemo() {
805
1078
  });
806
1079
  }
807
1080
  console.log('');
808
- // Summary — show real results
1081
+ // Summary
809
1082
  console.log(` ${theme.brandBold(currentLang === 'zh-TW' ? '\u5C55\u793A\u5B8C\u6210' : 'Demo Complete')}`);
810
1083
  console.log('');
811
1084
  for (const r of results) {
@@ -816,7 +1089,6 @@ async function actionDemo() {
816
1089
  : c.critical('\u2717');
817
1090
  console.log(` ${icon} ${r.name}`);
818
1091
  }
819
- // Next steps
820
1092
  nextSteps(currentLang === 'zh-TW'
821
1093
  ? [
822
1094
  { cmd: 'init', desc: '\u521D\u59CB\u8A2D\u5B9A\u60A8\u7684\u74B0\u5883' },
@@ -830,17 +1102,16 @@ async function actionDemo() {
830
1102
  ], currentLang);
831
1103
  }
832
1104
  // ---------------------------------------------------------------------------
833
- // 8. Upgrade Plan
1105
+ // Prompt commands: Upgrade
834
1106
  // ---------------------------------------------------------------------------
835
1107
  async function actionUpgrade() {
836
1108
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5347\u7D1A\u65B9\u6848' : 'Upgrade Plan']);
837
- // Delegate to the upgrade command module
838
1109
  const { upgradeCommand } = await import('./commands/upgrade.js');
839
1110
  const cmd = upgradeCommand();
840
1111
  await cmd.parseAsync(['upgrade', '--lang', currentLang], { from: 'user' });
841
1112
  }
842
1113
  // ---------------------------------------------------------------------------
843
- // 9. Security Hardening
1114
+ // Prompt commands: Hardening
844
1115
  // ---------------------------------------------------------------------------
845
1116
  async function actionHardening() {
846
1117
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5B89\u5168\u52A0\u56FA' : 'Security Hardening']);
@@ -872,39 +1143,7 @@ async function actionHardening() {
872
1143
  }
873
1144
  }
874
1145
  // ---------------------------------------------------------------------------
875
- // 10. Distributed Manager
876
- // ---------------------------------------------------------------------------
877
- async function actionManager() {
878
- breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5206\u6563\u5F0F\u7BA1\u7406' : 'Manager']);
879
- const title = currentLang === 'zh-TW' ? '\u5206\u6563\u5F0F\u7BA1\u7406' : 'Distributed Manager';
880
- console.log(` ${theme.brandBold(title)}`);
881
- console.log('');
882
- const items = [
883
- { key: '1', label: currentLang === 'zh-TW' ? '\u67E5\u770B Agent \u72C0\u614B' : 'Agent status' },
884
- { key: '2', label: currentLang === 'zh-TW' ? '\u5A01\u8105\u7E3D\u89BD' : 'Threat overview' },
885
- { key: '3', label: currentLang === 'zh-TW' ? '\u653F\u7B56\u7BA1\u7406' : 'Policy management' },
886
- ];
887
- renderCompactMenu(currentLang === 'zh-TW' ? '\u7BA1\u7406\u7BC0\u9EDE' : 'Manager Node', items);
888
- const choice = await waitForCompactChoice(items, currentLang);
889
- if (!choice)
890
- return;
891
- console.log('');
892
- const { managerCommand } = await import('./commands/manager.js');
893
- const cmd = managerCommand();
894
- switch (choice.key) {
895
- case '1':
896
- await cmd.parseAsync(['manager', 'agents'], { from: 'user' });
897
- break;
898
- case '2':
899
- await cmd.parseAsync(['manager', 'threats'], { from: 'user' });
900
- break;
901
- case '3':
902
- await cmd.parseAsync(['manager', 'policies'], { from: 'user' });
903
- break;
904
- }
905
- }
906
- // ---------------------------------------------------------------------------
907
- // 11. System Status
1146
+ // Prompt commands: Status
908
1147
  // ---------------------------------------------------------------------------
909
1148
  async function actionStatus() {
910
1149
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u7CFB\u7D71\u72C0\u614B' : 'Status']);
@@ -913,7 +1152,7 @@ async function actionStatus() {
913
1152
  await cmd.parseAsync(['status'], { from: 'user' });
914
1153
  }
915
1154
  // ---------------------------------------------------------------------------
916
- // 12. Account Login
1155
+ // Prompt commands: Login
917
1156
  // ---------------------------------------------------------------------------
918
1157
  async function actionLogin() {
919
1158
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u5E33\u865F\u767B\u5165' : 'Login']);
@@ -922,7 +1161,7 @@ async function actionLogin() {
922
1161
  await cmd.parseAsync(['login'], { from: 'user' });
923
1162
  }
924
1163
  // ---------------------------------------------------------------------------
925
- // 13. Settings
1164
+ // Prompt commands: Config
926
1165
  // ---------------------------------------------------------------------------
927
1166
  async function actionConfig() {
928
1167
  breadcrumb(['Panguard', currentLang === 'zh-TW' ? '\u8A2D\u5B9A\u7BA1\u7406' : 'Settings']);