Skip to content

Commit f66892a

Browse files
kobzevvvclaude
andcommitted
Add ScanPermissionPanel.swift (was missing from initial commit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 370424b commit f66892a

File tree

1 file changed

+114
-0
lines changed

1 file changed

+114
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import AppKit
2+
3+
struct ScanOptions {
4+
var claudeCode = true
5+
var shellHistory = true
6+
var gitRepos = true
7+
var downloads = true
8+
var clawdbot = true
9+
var browser = true
10+
var system = true
11+
12+
/// CLI flags to pass to scan-logs.mjs
13+
var skipArgs: [String] {
14+
var args: [String] = []
15+
if !claudeCode { args.append("--skip-claude") }
16+
if !shellHistory { args.append("--skip-shell") }
17+
if !gitRepos { args.append("--skip-git") }
18+
if !downloads { args.append("--skip-downloads") }
19+
if !clawdbot { args.append("--skip-clawdbot") }
20+
if !browser { args.append("--skip-browser") }
21+
if !system { args.append("--skip-system") }
22+
return args
23+
}
24+
25+
static func fromDefaults() -> ScanOptions {
26+
var opts = ScanOptions()
27+
let d = UserDefaults.standard
28+
func load(_ key: String, into value: inout Bool) {
29+
if d.object(forKey: key) != nil { value = d.bool(forKey: key) }
30+
}
31+
load("scanOpt_claudeCode", into: &opts.claudeCode)
32+
load("scanOpt_shellHistory", into: &opts.shellHistory)
33+
load("scanOpt_gitRepos", into: &opts.gitRepos)
34+
load("scanOpt_downloads", into: &opts.downloads)
35+
load("scanOpt_clawdbot", into: &opts.clawdbot)
36+
load("scanOpt_browser", into: &opts.browser)
37+
load("scanOpt_system", into: &opts.system)
38+
return opts
39+
}
40+
41+
func saveToDefaults() {
42+
let d = UserDefaults.standard
43+
d.set(claudeCode, forKey: "scanOpt_claudeCode")
44+
d.set(shellHistory, forKey: "scanOpt_shellHistory")
45+
d.set(gitRepos, forKey: "scanOpt_gitRepos")
46+
d.set(downloads, forKey: "scanOpt_downloads")
47+
d.set(clawdbot, forKey: "scanOpt_clawdbot")
48+
d.set(browser, forKey: "scanOpt_browser")
49+
d.set(system, forKey: "scanOpt_system")
50+
}
51+
}
52+
53+
enum ScanPermissionPanel {
54+
55+
// Returns nil if user cancelled, ScanOptions if confirmed.
56+
static func present() -> ScanOptions? {
57+
var opts = ScanOptions.fromDefaults()
58+
let firstTime = !UserDefaults.standard.bool(forKey: "scanPermissionsExplained")
59+
60+
let alert = NSAlert()
61+
alert.messageText = "What vibe-sec will scan"
62+
alert.informativeText = firstTime
63+
? "vibe-sec reads the following on your Mac only.\nUncheck anything you want to skip. Nothing leaves your machine."
64+
: "Choose what to include in this scan.\nNothing leaves your machine."
65+
66+
// Build checkbox list
67+
let items: [(String, WritableKeyPath<ScanOptions, Bool>)] = [
68+
("Claude Code — session logs, settings, MCP config", \.claudeCode),
69+
("Shell — command history (~/.zsh_history)", \.shellHistory),
70+
("Git repos — .env files in ~/Documents/GitHub/", \.gitRepos),
71+
("Downloads — API key files, service account JSON", \.downloads),
72+
("clawdbot — Telegram bot token (~/.clawdbot/)", \.clawdbot),
73+
("Safari/Chrome — visited cloud & financial services", \.browser),
74+
("System — open ports, firewall, screen lock", \.system),
75+
]
76+
77+
let stack = NSStackView()
78+
stack.orientation = .vertical
79+
stack.alignment = .left
80+
stack.spacing = 6
81+
82+
var buttons: [(NSButton, WritableKeyPath<ScanOptions, Bool>)] = []
83+
for (label, keyPath) in items {
84+
let btn = NSButton(checkboxWithTitle: label, target: nil, action: nil)
85+
btn.state = opts[keyPath: keyPath] ? .on : .off
86+
btn.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize + 1)
87+
stack.addArrangedSubview(btn)
88+
buttons.append((btn, keyPath))
89+
}
90+
91+
let fitting = stack.fittingSize
92+
stack.frame = NSRect(x: 0, y: 0, width: max(fitting.width, 380), height: fitting.height)
93+
alert.accessoryView = stack
94+
95+
alert.addButton(withTitle: "Scan Now")
96+
alert.addButton(withTitle: "Cancel")
97+
alert.alertStyle = .informational
98+
99+
let response = alert.runModal()
100+
guard response == .alertFirstButtonReturn else { return nil }
101+
102+
// Read back checkbox states
103+
for (btn, keyPath) in buttons {
104+
opts[keyPath: keyPath] = btn.state == .on
105+
}
106+
opts.saveToDefaults()
107+
108+
if firstTime {
109+
UserDefaults.standard.set(true, forKey: "scanPermissionsExplained")
110+
}
111+
112+
return opts
113+
}
114+
}

0 commit comments

Comments
 (0)