99//
1010// 设计目标:30 秒、零配置、出一张可截图的「你的项目」合规风险报告。
1111
12- import { resolve } from 'path'
12+ import { resolve , join } from 'path'
1313import { writeFileSync } from 'fs'
14+ import { tmpdir } from 'os'
15+ import { createServer } from 'http'
16+ import { spawn } from 'child_process'
1417import { ShellWard } from './core/engine.js'
1518import { runProjectComplianceAudit } from './compliance/audit.js'
1619import { renderComplianceReport , renderProjectFindings } from './compliance/report.js'
@@ -48,6 +51,8 @@ function runScan(args: string[]) {
4851 const ci = args . includes ( '--ci' )
4952 const outPath = flagValue ( args , '--out' )
5053 const htmlPath = flagValue ( args , '--html' )
54+ const open = args . includes ( '--open' )
55+ const serve = args . includes ( '--serve' )
5156 const dirArg = args . find ( a => ! a . startsWith ( '-' ) )
5257 const root = resolve ( dirArg || process . cwd ( ) )
5358
@@ -62,6 +67,39 @@ function runScan(args: string[]) {
6267
6368 const { report, scan } = runProjectComplianceAudit ( guard . config , root )
6469
70+ // ===== 本地 web 视图(方便看;数据全程不出本机)=====
71+ if ( serve ) {
72+ const html = renderHtmlReport ( report , scan , locale , { root } )
73+ const port = Number ( flagValue ( args , '--serve' ) || flagValue ( args , '--port' ) ) || 7777
74+ const server = createServer ( ( _req , res ) => {
75+ res . writeHead ( 200 , { 'Content-Type' : 'text/html; charset=utf-8' } )
76+ res . end ( html )
77+ } )
78+ server . on ( 'error' , ( e : any ) => {
79+ console . error ( zh ? `无法启动本地服务: ${ e ?. message } ` : `Failed to start server: ${ e ?. message } ` )
80+ process . exit ( 1 )
81+ } )
82+ server . listen ( port , '127.0.0.1' , ( ) => {
83+ const url = `http://localhost:${ port } `
84+ process . stdout . write ( zh
85+ ? `🌐 本地合规报告: ${ url } (得分 ${ report . score } /100 [${ report . grade } ],Ctrl+C 退出;数据不出本机)\n`
86+ : `🌐 Local compliance report: ${ url } (score ${ report . score } /100 [${ report . grade } ]; Ctrl+C to stop; nothing leaves your machine)\n` )
87+ openBrowser ( url )
88+ } )
89+ return
90+ }
91+
92+ if ( open ) {
93+ const html = renderHtmlReport ( report , scan , locale , { root } )
94+ const file = join ( tmpdir ( ) , `shellward-report-${ report . score } -${ report . grade } .html` )
95+ writeFileSync ( file , html , 'utf-8' )
96+ process . stdout . write ( zh
97+ ? `🌐 已在浏览器打开合规报告: ${ file } (得分 ${ report . score } /100 [${ report . grade } ];数据不出本机)\n`
98+ : `🌐 Opened compliance report in browser: ${ file } (score ${ report . score } /100 [${ report . grade } ])\n` )
99+ openBrowser ( file )
100+ return
101+ }
102+
65103 if ( json ) {
66104 process . stdout . write ( JSON . stringify ( {
67105 root,
@@ -117,6 +155,19 @@ function runScan(args: string[]) {
117155 }
118156}
119157
158+ /** 跨平台在默认浏览器打开 URL 或本地文件(失败静默,不影响主流程) */
159+ function openBrowser ( target : string ) : void {
160+ const cmd = process . platform === 'darwin' ? 'open'
161+ : process . platform === 'win32' ? 'cmd'
162+ : 'xdg-open'
163+ const cmdArgs = process . platform === 'win32' ? [ '/c' , 'start' , '' , target ] : [ target ]
164+ try {
165+ const child = spawn ( cmd , cmdArgs , { stdio : 'ignore' , detached : true } )
166+ child . on ( 'error' , ( ) => { } )
167+ child . unref ( )
168+ } catch { /* 打开失败不影响 */ }
169+ }
170+
120171/** 取 `--flag value` 或 `--flag=value` 的值 */
121172function flagValue ( args : string [ ] , flag : string ) : string | undefined {
122173 const i = args . indexOf ( flag )
@@ -136,6 +187,8 @@ Usage:
136187 shellward scan --ci Exit non-zero if critical findings
137188 shellward scan --out f Export the full report to a Markdown file
138189 shellward scan --html f Export a self-contained HTML report (print to PDF)
190+ shellward scan --open Scan and open the report in your browser (local)
191+ shellward scan --serve Scan and serve the report at http://localhost (local)
139192 shellward mcp Start MCP server (stdio)
140193 shellward --help
141194
@@ -150,6 +203,8 @@ PII in files, .env permissions. Maps to CSL / PIPL / MLPS / cross-border / label
150203 shellward scan --ci 有 critical 发现时非零退出
151204 shellward scan --out 文件 导出完整报告为 Markdown(合规存档)
152205 shellward scan --html 文件 导出自包含 HTML 报告(浏览器可打印成 PDF)
206+ shellward scan --open 扫描并在浏览器打开报告(本地,方便看)
207+ shellward scan --serve 扫描并在 http://localhost 提供报告(本地服务)
153208 shellward mcp 启动 MCP 服务器(stdio)
154209 shellward --help
155210
0 commit comments