@panguard-ai/panguard-mcp 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Panguard MCP - Server
3
+ * Panguard MCP - 伺服器
4
+ *
5
+ * Main MCP server entry: registers all tools and dispatches incoming requests.
6
+ * 主要 MCP 伺服器入口:註冊所有工具並分派傳入請求。
7
+ *
8
+ * @module @panguard-ai/panguard-mcp/server
9
+ */
10
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
13
+ import { createLogger } from '@panguard-ai/core';
14
+ import { executeScan, executeScanCode } from './tools/scan-tools.js';
15
+ import { executeGuardStart, executeGuardStop, executeStatus, executeAlerts, } from './tools/guard-tools.js';
16
+ import { executeBlockIP, executeGenerateReport, executeInit, executeDeploy, } from './tools/manage-tools.js';
17
+ const logger = createLogger('panguard-mcp:server');
18
+ import { createRequire } from 'node:module';
19
+ const _require = createRequire(import.meta.url);
20
+ const _pkg = _require('../package.json');
21
+ /** MCP server version / MCP 伺服器版本 */
22
+ export const PANGUARD_MCP_VERSION = _pkg.version;
23
+ /**
24
+ * All MCP tool definitions.
25
+ * 所有 MCP 工具定義。
26
+ *
27
+ * Each tool has a name, description (bilingual EN/ZH-TW), and inputSchema.
28
+ * 每個工具都有名稱、雙語描述(EN/ZH-TW)和輸入結構。
29
+ */
30
+ const TOOL_DEFINITIONS = [
31
+ {
32
+ name: 'panguard_scan',
33
+ description: 'Run a security health check scan on the local system. Returns risk score (0-100), grade (A-F), and list of security findings. / 在本機執行資安健檢掃描,回傳風險分數、等級和安全發現清單。',
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ depth: {
38
+ type: 'string',
39
+ enum: ['quick', 'full'],
40
+ description: 'Scan depth: quick (~30s) or full (~60s)',
41
+ default: 'quick',
42
+ },
43
+ lang: {
44
+ type: 'string',
45
+ enum: ['en', 'zh-TW'],
46
+ description: 'Output language',
47
+ default: 'en',
48
+ },
49
+ },
50
+ },
51
+ },
52
+ {
53
+ name: 'panguard_scan_code',
54
+ description: 'Scan source code directory for security vulnerabilities (SAST). Detects SQL injection, XSS, hardcoded secrets, command injection, and more. / 掃描原始碼目錄的安全漏洞(SAST)。偵測 SQL 注入、XSS、硬編碼密鑰等。',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ dir: {
59
+ type: 'string',
60
+ description: 'Source code directory to scan',
61
+ default: '.',
62
+ },
63
+ lang: {
64
+ type: 'string',
65
+ enum: ['en', 'zh-TW'],
66
+ default: 'en',
67
+ },
68
+ },
69
+ required: ['dir'],
70
+ },
71
+ },
72
+ {
73
+ name: 'panguard_guard_start',
74
+ description: 'Start the Panguard Guard real-time threat monitoring daemon. / 啟動 Panguard Guard 即時威脅監控常駐程式。',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ dataDir: {
79
+ type: 'string',
80
+ description: 'Data directory (default: ~/.panguard-guard)',
81
+ },
82
+ mode: {
83
+ type: 'string',
84
+ enum: ['learning', 'protection'],
85
+ description: 'Operating mode',
86
+ },
87
+ },
88
+ },
89
+ },
90
+ {
91
+ name: 'panguard_guard_stop',
92
+ description: 'Stop the Panguard Guard daemon. / 停止 Panguard Guard 常駐程式。',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ dataDir: {
97
+ type: 'string',
98
+ description: 'Data directory (default: ~/.panguard-guard)',
99
+ },
100
+ },
101
+ },
102
+ },
103
+ {
104
+ name: 'panguard_status',
105
+ description: 'Get the current status of all Panguard services (guard, scan, manager). Returns running state, threat counts, and system info. / 取得所有 Panguard 服務的當前狀態。',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ dataDir: {
110
+ type: 'string',
111
+ description: 'Data directory (default: ~/.panguard-guard)',
112
+ },
113
+ },
114
+ },
115
+ },
116
+ {
117
+ name: 'panguard_alerts',
118
+ description: 'Get recent security alerts detected by Panguard Guard. Returns the latest threat events with severity and details. / 取得 Panguard Guard 偵測到的近期安全告警。',
119
+ inputSchema: {
120
+ type: 'object',
121
+ properties: {
122
+ limit: {
123
+ type: 'number',
124
+ description: 'Maximum number of alerts to return',
125
+ default: 20,
126
+ },
127
+ severity: {
128
+ type: 'string',
129
+ enum: ['critical', 'high', 'medium', 'low', 'all'],
130
+ default: 'all',
131
+ },
132
+ dataDir: {
133
+ type: 'string',
134
+ description: 'Data directory (default: ~/.panguard-guard)',
135
+ },
136
+ },
137
+ },
138
+ },
139
+ {
140
+ name: 'panguard_block_ip',
141
+ description: 'Manually block an IP address from accessing the system. / 手動封鎖 IP 位址存取系統。',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ ip: {
146
+ type: 'string',
147
+ description: 'IP address to block (IPv4 or IPv6)',
148
+ },
149
+ duration: {
150
+ type: 'string',
151
+ description: 'Block duration (e.g., "1h", "24h", "permanent")',
152
+ default: '1h',
153
+ },
154
+ reason: {
155
+ type: 'string',
156
+ description: 'Reason for blocking',
157
+ },
158
+ },
159
+ required: ['ip'],
160
+ },
161
+ },
162
+ {
163
+ name: 'panguard_generate_report',
164
+ description: 'Generate a PDF compliance report from scan results. Returns the path to the generated PDF. / 從掃描結果生成 PDF 合規報告。',
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: {
168
+ output: {
169
+ type: 'string',
170
+ description: 'Output PDF path',
171
+ default: './panguard-report.pdf',
172
+ },
173
+ lang: {
174
+ type: 'string',
175
+ enum: ['en', 'zh-TW'],
176
+ default: 'en',
177
+ },
178
+ depth: {
179
+ type: 'string',
180
+ enum: ['quick', 'full'],
181
+ default: 'full',
182
+ },
183
+ },
184
+ },
185
+ },
186
+ {
187
+ name: 'panguard_init',
188
+ description: 'Initialize Panguard configuration interactively (non-interactive mode with defaults). / 以非互動模式初始化 Panguard 配置。',
189
+ inputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ dataDir: {
193
+ type: 'string',
194
+ description: 'Data directory (default: ~/.panguard-guard)',
195
+ },
196
+ lang: {
197
+ type: 'string',
198
+ enum: ['en', 'zh-TW'],
199
+ default: 'en',
200
+ },
201
+ mode: {
202
+ type: 'string',
203
+ enum: ['learning', 'protection'],
204
+ default: 'learning',
205
+ },
206
+ },
207
+ },
208
+ },
209
+ {
210
+ name: 'panguard_audit_skill',
211
+ description: 'Audit an OpenClaw/AgentSkills SKILL.md directory for security issues. Checks manifest validity, prompt injection, tool poisoning, code vulnerabilities, dependencies, and permissions. Returns risk score (0-100) and detailed findings. / 審計 OpenClaw 技能目錄的安全問題。檢查清單有效性、提示注入、工具投毒、程式碼漏洞、依賴和權限。',
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: {
215
+ path: {
216
+ type: 'string',
217
+ description: 'Path to skill directory containing SKILL.md',
218
+ },
219
+ },
220
+ required: ['path'],
221
+ },
222
+ },
223
+ {
224
+ name: 'panguard_deploy',
225
+ description: 'Deploy Panguard services: scan for vulnerabilities, start guard monitoring, and generate initial report. This is the one-click setup for protection. / 部署 Panguard 服務:掃描漏洞、啟動監控並生成初始報告。一鍵設定防護。',
226
+ inputSchema: {
227
+ type: 'object',
228
+ properties: {
229
+ dataDir: {
230
+ type: 'string',
231
+ description: 'Data directory (default: ~/.panguard-guard)',
232
+ },
233
+ lang: {
234
+ type: 'string',
235
+ enum: ['en', 'zh-TW'],
236
+ default: 'en',
237
+ },
238
+ mode: {
239
+ type: 'string',
240
+ enum: ['learning', 'protection'],
241
+ default: 'learning',
242
+ },
243
+ generateReport: {
244
+ type: 'boolean',
245
+ description: 'Generate PDF report after scan',
246
+ default: true,
247
+ },
248
+ },
249
+ },
250
+ },
251
+ ];
252
+ /**
253
+ * Returns the complete list of all MCP tool definitions.
254
+ * 返回所有 MCP 工具定義的完整清單。
255
+ */
256
+ export function getAllToolDefinitions() {
257
+ return TOOL_DEFINITIONS;
258
+ }
259
+ /**
260
+ * Dispatch an incoming tool call to the appropriate handler.
261
+ * 將傳入的工具呼叫分派給適當的處理程序。
262
+ *
263
+ * @param name - Tool name / 工具名稱
264
+ * @param args - Tool arguments / 工具參數
265
+ */
266
+ export async function dispatchTool(name, args) {
267
+ switch (name) {
268
+ case 'panguard_scan':
269
+ return executeScan(args);
270
+ case 'panguard_scan_code':
271
+ return executeScanCode(args);
272
+ case 'panguard_guard_start':
273
+ return executeGuardStart(args);
274
+ case 'panguard_guard_stop':
275
+ return executeGuardStop(args);
276
+ case 'panguard_status':
277
+ return executeStatus(args);
278
+ case 'panguard_alerts':
279
+ return executeAlerts(args);
280
+ case 'panguard_block_ip':
281
+ return executeBlockIP(args);
282
+ case 'panguard_generate_report':
283
+ return executeGenerateReport(args);
284
+ case 'panguard_init':
285
+ return executeInit(args);
286
+ case 'panguard_deploy':
287
+ return executeDeploy(args);
288
+ case 'panguard_audit_skill': {
289
+ const { auditSkill } = await import('@panguard-ai/panguard-skill-auditor');
290
+ const skillPath = args['path'] ?? '.';
291
+ const report = await auditSkill(skillPath);
292
+ return {
293
+ content: [{ type: 'text', text: JSON.stringify(report, null, 2) }],
294
+ };
295
+ }
296
+ default:
297
+ return {
298
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
299
+ isError: true,
300
+ };
301
+ }
302
+ }
303
+ /**
304
+ * Create and start the MCP server using stdio transport.
305
+ * 使用 stdio 傳輸建立並啟動 MCP 伺服器。
306
+ *
307
+ * This is the main entry point for the MCP server process.
308
+ * 這是 MCP 伺服器進程的主要入口點。
309
+ */
310
+ export async function startMCPServer() {
311
+ const server = new Server({ name: 'panguard-mcp', version: PANGUARD_MCP_VERSION }, { capabilities: { tools: {} } });
312
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
313
+ tools: getAllToolDefinitions(),
314
+ }));
315
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
316
+ const { name, arguments: args } = request.params;
317
+ return await dispatchTool(name, args ?? {});
318
+ });
319
+ const transport = new StdioServerTransport();
320
+ await server.connect(transport);
321
+ logger.info('Panguard MCP server started / Panguard MCP 伺服器已啟動');
322
+ }
323
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-tools.d.ts","sourceRoot":"","sources":["../../src/tools/guard-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;GAgMpE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;GA6CnE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;GAiEhE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;GAiDhE"}
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Panguard MCP - Guard Tools
3
+ * Panguard MCP - 守護工具
4
+ *
5
+ * Implements panguard_guard_start, panguard_guard_stop, panguard_status,
6
+ * and panguard_alerts MCP tools.
7
+ * 實作 panguard_guard_start、panguard_guard_stop、panguard_status 和 panguard_alerts MCP 工具。
8
+ *
9
+ * @module @panguard-ai/panguard-mcp/tools/guard-tools
10
+ */
11
+ import { promises as fs, existsSync, readFileSync, openSync, closeSync } from 'node:fs';
12
+ import { spawn } from 'node:child_process';
13
+ import { createRequire } from 'node:module';
14
+ import { fileURLToPath } from 'node:url';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ /**
18
+ * Resolve the guard data directory from args or default.
19
+ * 從參數或預設值解析守護資料目錄。
20
+ */
21
+ function resolveDataDir(args) {
22
+ return args['dataDir'] ?? path.join(os.homedir(), '.panguard-guard');
23
+ }
24
+ /**
25
+ * Execute panguard_guard_start — start the real-time threat monitoring daemon.
26
+ * 執行 panguard_guard_start — 啟動即時威脅監控常駐程式。
27
+ *
28
+ * Spawns the guard daemon as a detached background process with stdio
29
+ * redirected to a log file, so it does not interfere with MCP stdio transport.
30
+ * 將守護常駐程式作為分離的背景進程啟動,stdio 重導向到日誌檔案。
31
+ */
32
+ export async function executeGuardStart(args) {
33
+ const dataDir = resolveDataDir(args);
34
+ const mode = args['mode'] ?? 'learning';
35
+ try {
36
+ await fs.mkdir(dataDir, { recursive: true });
37
+ }
38
+ catch {
39
+ // Directory may already exist
40
+ }
41
+ // Check if guard is already running
42
+ const pidFile = path.join(dataDir, 'panguard-guard.pid');
43
+ try {
44
+ const pidContent = await fs.readFile(pidFile, 'utf-8');
45
+ const existingPid = parseInt(pidContent.trim(), 10);
46
+ if (!isNaN(existingPid)) {
47
+ try {
48
+ process.kill(existingPid, 0);
49
+ // Process exists — guard is already running
50
+ return {
51
+ content: [
52
+ {
53
+ type: 'text',
54
+ text: JSON.stringify({
55
+ status: 'already_running',
56
+ pid: existingPid,
57
+ dataDir,
58
+ mode,
59
+ message: `Guard engine is already running (PID: ${existingPid}).`,
60
+ }, null, 2),
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ catch {
66
+ // Process not found — stale PID file, continue to start
67
+ await fs.unlink(pidFile).catch(() => undefined);
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // No PID file — proceed to start
73
+ }
74
+ // Resolve the panguard-guard CLI script path
75
+ let guardCliScript;
76
+ try {
77
+ const _require = createRequire(import.meta.url);
78
+ const guardMainPath = _require.resolve('@panguard-ai/panguard-guard');
79
+ guardCliScript = path.join(path.dirname(guardMainPath), 'cli', 'index.js');
80
+ }
81
+ catch {
82
+ // Fallback: try resolving via import.meta.resolve
83
+ try {
84
+ const guardMainUrl = import.meta.resolve('@panguard-ai/panguard-guard');
85
+ guardCliScript = path.join(fileURLToPath(guardMainUrl), '..', 'cli', 'index.js');
86
+ }
87
+ catch {
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: JSON.stringify({
93
+ status: 'error',
94
+ message: 'Could not resolve @panguard-ai/panguard-guard package. Is it installed?',
95
+ }, null, 2),
96
+ },
97
+ ],
98
+ isError: true,
99
+ };
100
+ }
101
+ }
102
+ // Spawn guard as a detached background process
103
+ const logPath = path.join(dataDir, 'guard.log');
104
+ let logFd;
105
+ try {
106
+ logFd = openSync(logPath, 'a');
107
+ }
108
+ catch (err) {
109
+ return {
110
+ content: [
111
+ {
112
+ type: 'text',
113
+ text: JSON.stringify({
114
+ status: 'error',
115
+ message: `Failed to open log file: ${err instanceof Error ? err.message : String(err)}`,
116
+ }, null, 2),
117
+ },
118
+ ],
119
+ isError: true,
120
+ };
121
+ }
122
+ try {
123
+ const child = spawn(process.execPath, [guardCliScript, 'start'], {
124
+ detached: true,
125
+ stdio: ['ignore', logFd, logFd],
126
+ env: { ...process.env },
127
+ });
128
+ child.unref();
129
+ closeSync(logFd);
130
+ // Wait for PID file to confirm startup (up to 5 seconds)
131
+ let started = false;
132
+ let newPid = null;
133
+ const deadline = Date.now() + 5000;
134
+ while (Date.now() < deadline) {
135
+ if (existsSync(pidFile)) {
136
+ try {
137
+ const content = readFileSync(pidFile, 'utf-8').trim();
138
+ const parsed = parseInt(content, 10);
139
+ if (!isNaN(parsed)) {
140
+ process.kill(parsed, 0);
141
+ started = true;
142
+ newPid = parsed;
143
+ break;
144
+ }
145
+ }
146
+ catch {
147
+ // Not ready yet
148
+ }
149
+ }
150
+ await new Promise((r) => setTimeout(r, 300));
151
+ }
152
+ if (started) {
153
+ return {
154
+ content: [
155
+ {
156
+ type: 'text',
157
+ text: JSON.stringify({
158
+ status: 'started',
159
+ pid: newPid,
160
+ dataDir,
161
+ mode,
162
+ logFile: logPath,
163
+ message: `Guard engine started successfully (PID: ${newPid}).`,
164
+ }, null, 2),
165
+ },
166
+ ],
167
+ };
168
+ }
169
+ else {
170
+ return {
171
+ content: [
172
+ {
173
+ type: 'text',
174
+ text: JSON.stringify({
175
+ status: 'timeout',
176
+ dataDir,
177
+ mode,
178
+ logFile: logPath,
179
+ message: 'Guard engine was spawned but did not confirm startup within 5 seconds. Check the log file for details.',
180
+ }, null, 2),
181
+ },
182
+ ],
183
+ };
184
+ }
185
+ }
186
+ catch (err) {
187
+ closeSync(logFd);
188
+ return {
189
+ content: [
190
+ {
191
+ type: 'text',
192
+ text: JSON.stringify({
193
+ status: 'error',
194
+ message: `Failed to spawn guard process: ${err instanceof Error ? err.message : String(err)}`,
195
+ }, null, 2),
196
+ },
197
+ ],
198
+ isError: true,
199
+ };
200
+ }
201
+ }
202
+ /**
203
+ * Execute panguard_guard_stop — stop the real-time threat monitoring daemon.
204
+ * 執行 panguard_guard_stop — 停止即時威脅監控常駐程式。
205
+ */
206
+ export async function executeGuardStop(args) {
207
+ const dataDir = resolveDataDir(args);
208
+ const pidFile = path.join(dataDir, 'guard.pid');
209
+ let pid = null;
210
+ let stopped = false;
211
+ try {
212
+ const pidContent = await fs.readFile(pidFile, 'utf-8');
213
+ pid = parseInt(pidContent.trim(), 10);
214
+ if (!isNaN(pid)) {
215
+ try {
216
+ process.kill(pid, 'SIGTERM');
217
+ stopped = true;
218
+ // Remove stale PID file
219
+ await fs.unlink(pidFile).catch(() => undefined);
220
+ }
221
+ catch {
222
+ // Process not running — clean up stale PID file
223
+ await fs.unlink(pidFile).catch(() => undefined);
224
+ stopped = false;
225
+ }
226
+ }
227
+ }
228
+ catch {
229
+ // No PID file found
230
+ }
231
+ return {
232
+ content: [
233
+ {
234
+ type: 'text',
235
+ text: JSON.stringify({
236
+ status: stopped ? 'stopped' : 'not_running',
237
+ pid,
238
+ message: stopped
239
+ ? `Guard engine (PID: ${pid}) has been sent SIGTERM.`
240
+ : 'Guard engine was not running (no PID file found).',
241
+ }, null, 2),
242
+ },
243
+ ],
244
+ };
245
+ }
246
+ /**
247
+ * Execute panguard_status — get current status of all Panguard services.
248
+ * 執行 panguard_status — 取得所有 Panguard 服務的當前狀態。
249
+ */
250
+ export async function executeStatus(args) {
251
+ const dataDir = resolveDataDir(args);
252
+ const pidFile = path.join(dataDir, 'guard.pid');
253
+ let isRunning = false;
254
+ let pid = null;
255
+ try {
256
+ const pidContent = await fs.readFile(pidFile, 'utf-8');
257
+ pid = parseInt(pidContent.trim(), 10);
258
+ if (!isNaN(pid)) {
259
+ // Sending signal 0 checks if process exists without killing it
260
+ process.kill(pid, 0);
261
+ isRunning = true;
262
+ }
263
+ }
264
+ catch {
265
+ isRunning = false;
266
+ }
267
+ // Try to read guard config
268
+ let config = {};
269
+ try {
270
+ const configPath = path.join(dataDir, 'config.json');
271
+ const configContent = await fs.readFile(configPath, 'utf-8');
272
+ config = JSON.parse(configContent);
273
+ }
274
+ catch {
275
+ // No config file yet
276
+ }
277
+ // Count recent events as a proxy for threat activity
278
+ let eventCount = 0;
279
+ try {
280
+ const eventsFile = path.join(dataDir, 'events.jsonl');
281
+ const content = await fs.readFile(eventsFile, 'utf-8');
282
+ eventCount = content.trim().split('\n').filter(Boolean).length;
283
+ }
284
+ catch {
285
+ // No events file
286
+ }
287
+ return {
288
+ content: [
289
+ {
290
+ type: 'text',
291
+ text: JSON.stringify({
292
+ guard: {
293
+ running: isRunning,
294
+ pid,
295
+ dataDir,
296
+ mode: config['mode'] ?? 'unknown',
297
+ lang: config['lang'] ?? 'en',
298
+ },
299
+ events: {
300
+ total_logged: eventCount,
301
+ },
302
+ summary: isRunning
303
+ ? `Panguard Guard is RUNNING (PID: ${pid}, mode: ${config['mode'] ?? 'unknown'}).`
304
+ : 'Panguard Guard is NOT running. Use panguard_guard_start to start it.',
305
+ }, null, 2),
306
+ },
307
+ ],
308
+ };
309
+ }
310
+ /**
311
+ * Execute panguard_alerts — get recent security alerts from guard event log.
312
+ * 執行 panguard_alerts — 從守護事件日誌取得近期安全告警。
313
+ */
314
+ export async function executeAlerts(args) {
315
+ const limit = args['limit'] ?? 20;
316
+ const severity = args['severity'] ?? 'all';
317
+ const dataDir = resolveDataDir(args);
318
+ const eventsFile = path.join(dataDir, 'events.jsonl');
319
+ const alerts = [];
320
+ try {
321
+ const content = await fs.readFile(eventsFile, 'utf-8');
322
+ const lines = content.trim().split('\n').filter(Boolean);
323
+ for (const line of lines) {
324
+ try {
325
+ const event = JSON.parse(line);
326
+ if (severity === 'all' || event['severity'] === severity) {
327
+ alerts.push(event);
328
+ }
329
+ }
330
+ catch {
331
+ // Skip malformed JSONL lines
332
+ }
333
+ }
334
+ }
335
+ catch {
336
+ // No events file yet — guard may not have started
337
+ }
338
+ // Return the most recent `limit` alerts
339
+ const recentAlerts = alerts.slice(-Math.max(1, limit));
340
+ return {
341
+ content: [
342
+ {
343
+ type: 'text',
344
+ text: JSON.stringify({
345
+ total_alerts: recentAlerts.length,
346
+ filter: { severity, limit },
347
+ alerts: recentAlerts,
348
+ summary: recentAlerts.length === 0
349
+ ? 'No recent alerts. System appears clean.'
350
+ : `${recentAlerts.length} recent alert(s) detected.`,
351
+ }, null, 2),
352
+ },
353
+ ],
354
+ };
355
+ }
356
+ //# sourceMappingURL=guard-tools.js.map