@rafter-security/cli 0.1.0 → 0.4.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.
@@ -0,0 +1,199 @@
1
+ import { Command } from "commander";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { PatternEngine } from "../../core/pattern-engine.js";
5
+ import { DEFAULT_SECRET_PATTERNS } from "../../scanners/secret-patterns.js";
6
+ import { SkillManager } from "../../utils/skill-manager.js";
7
+ export function createAuditSkillCommand() {
8
+ return new Command("audit-skill")
9
+ .description("Security audit of a Claude Code skill file")
10
+ .argument("<skill-path>", "Path to skill file to audit")
11
+ .option("--skip-openclaw", "Skip OpenClaw integration, show manual review prompt")
12
+ .option("--json", "Output results as JSON")
13
+ .action(async (skillPath, opts) => {
14
+ await auditSkill(skillPath, opts);
15
+ });
16
+ }
17
+ async function auditSkill(skillPath, opts) {
18
+ // Validate skill file exists
19
+ if (!fs.existsSync(skillPath)) {
20
+ console.error(`Error: Skill file not found: ${skillPath}`);
21
+ process.exit(1);
22
+ }
23
+ const absolutePath = path.resolve(skillPath);
24
+ const skillContent = fs.readFileSync(absolutePath, "utf-8");
25
+ const skillName = path.basename(absolutePath);
26
+ // Run deterministic analysis
27
+ if (!opts.json) {
28
+ console.log(`\nšŸ” Auditing skill: ${skillName}\n`);
29
+ console.log("═".repeat(60));
30
+ console.log("Running quick security scan...\n");
31
+ }
32
+ const quickScan = await runQuickScan(skillContent);
33
+ // Display quick scan results
34
+ if (!opts.json) {
35
+ displayQuickScan(quickScan, skillName);
36
+ }
37
+ // Check OpenClaw availability
38
+ const skillManager = new SkillManager();
39
+ const openClawAvailable = skillManager.isOpenClawInstalled();
40
+ const rafterSkillInstalled = skillManager.isRafterSkillInstalled();
41
+ if (opts.json) {
42
+ // JSON output
43
+ const result = {
44
+ skill: skillName,
45
+ path: absolutePath,
46
+ quickScan,
47
+ openClawAvailable,
48
+ rafterSkillInstalled
49
+ };
50
+ console.log(JSON.stringify(result, null, 2));
51
+ return;
52
+ }
53
+ // Check if we can use OpenClaw
54
+ if (openClawAvailable && !opts.skipOpenclaw) {
55
+ if (!rafterSkillInstalled) {
56
+ console.log("\nāš ļø Rafter Security skill not installed in OpenClaw.");
57
+ console.log(" Run: rafter agent init\n");
58
+ }
59
+ else {
60
+ console.log("\nšŸ¤– For comprehensive security review:\n");
61
+ console.log(" 1. Open OpenClaw");
62
+ console.log(` 2. Run: /rafter-audit-skill ${absolutePath}`);
63
+ console.log("\n The auditor will analyze:");
64
+ console.log(" • Trust & attribution");
65
+ console.log(" • Network security");
66
+ console.log(" • Command execution risks");
67
+ console.log(" • File system access");
68
+ console.log(" • Credential handling");
69
+ console.log(" • Input validation & injection risks");
70
+ console.log(" • Data exfiltration patterns");
71
+ console.log(" • Obfuscation techniques");
72
+ console.log(" • Scope & intent alignment");
73
+ console.log(" • Error handling & info disclosure");
74
+ console.log(" • Dependencies & supply chain");
75
+ console.log(" • Environment manipulation\n");
76
+ }
77
+ }
78
+ else {
79
+ // OpenClaw not available or skipped - show manual review prompt
80
+ console.log("\nšŸ“‹ Manual Security Review Prompt\n");
81
+ console.log("═".repeat(60));
82
+ console.log("\nCopy the following to your AI assistant for review:\n");
83
+ console.log("─".repeat(60));
84
+ console.log(generateManualReviewPrompt(skillName, absolutePath, quickScan, skillContent));
85
+ console.log("─".repeat(60));
86
+ }
87
+ console.log();
88
+ }
89
+ async function runQuickScan(content) {
90
+ // 1. Scan for secrets
91
+ const patternEngine = new PatternEngine(DEFAULT_SECRET_PATTERNS);
92
+ const secretMatches = patternEngine.scan(content);
93
+ // 2. Extract URLs
94
+ const urlRegex = /https?:\/\/[^\s<>"]+/gi;
95
+ const urls = Array.from(new Set(content.match(urlRegex) || []));
96
+ // 3. Detect high-risk commands
97
+ const highRiskPatterns = [
98
+ { pattern: /rm\s+-rf\s+\/(?!\w)/gi, name: "rm -rf /" },
99
+ { pattern: /sudo\s+rm/gi, name: "sudo rm" },
100
+ { pattern: /curl[^|]*\|\s*(?:ba)?sh/gi, name: "curl | sh" },
101
+ { pattern: /wget[^|]*\|\s*(?:ba)?sh/gi, name: "wget | sh" },
102
+ { pattern: /eval\s*\(/gi, name: "eval()" },
103
+ { pattern: /exec\s*\(/gi, name: "exec()" },
104
+ { pattern: /chmod\s+777/gi, name: "chmod 777" },
105
+ { pattern: /:\(\)\{\s*:\|:&\s*\};:/g, name: "fork bomb" },
106
+ { pattern: /dd\s+if=\/dev\/(?:zero|random)\s+of=\/dev/gi, name: "dd to device" },
107
+ { pattern: /mkfs/gi, name: "mkfs (format)" },
108
+ { pattern: /base64\s+-d[^|]*\|\s*(?:ba)?sh/gi, name: "base64 decode | sh" }
109
+ ];
110
+ const commands = [];
111
+ const lines = content.split('\n');
112
+ for (const { pattern, name } of highRiskPatterns) {
113
+ pattern.lastIndex = 0; // Reset regex
114
+ let match;
115
+ while ((match = pattern.exec(content)) !== null) {
116
+ // Find line number
117
+ const lineNumber = content.substring(0, match.index).split('\n').length;
118
+ commands.push({
119
+ command: name,
120
+ line: lineNumber
121
+ });
122
+ }
123
+ }
124
+ return {
125
+ secrets: secretMatches.length,
126
+ urls,
127
+ highRiskCommands: commands
128
+ };
129
+ }
130
+ function displayQuickScan(scan, skillName) {
131
+ console.log("šŸ“Š Quick Scan Results");
132
+ console.log("═".repeat(60));
133
+ // Secrets
134
+ if (scan.secrets === 0) {
135
+ console.log("āœ“ Secrets: None detected");
136
+ }
137
+ else {
138
+ console.log(`āš ļø Secrets: ${scan.secrets} found`);
139
+ console.log(" → API keys, tokens, or credentials detected");
140
+ console.log(" → Run: rafter agent scan <path> for details");
141
+ }
142
+ // URLs
143
+ if (scan.urls.length === 0) {
144
+ console.log("āœ“ External URLs: None");
145
+ }
146
+ else {
147
+ console.log(`āš ļø External URLs: ${scan.urls.length} found`);
148
+ scan.urls.slice(0, 5).forEach(url => {
149
+ console.log(` • ${url}`);
150
+ });
151
+ if (scan.urls.length > 5) {
152
+ console.log(` ... and ${scan.urls.length - 5} more`);
153
+ }
154
+ }
155
+ // High-risk commands
156
+ if (scan.highRiskCommands.length === 0) {
157
+ console.log("āœ“ High-risk commands: None detected");
158
+ }
159
+ else {
160
+ console.log(`āš ļø High-risk commands: ${scan.highRiskCommands.length} found`);
161
+ scan.highRiskCommands.slice(0, 5).forEach(cmd => {
162
+ console.log(` • ${cmd.command} (line ${cmd.line})`);
163
+ });
164
+ if (scan.highRiskCommands.length > 5) {
165
+ console.log(` ... and ${scan.highRiskCommands.length - 5} more`);
166
+ }
167
+ }
168
+ console.log();
169
+ }
170
+ function generateManualReviewPrompt(skillName, skillPath, scan, content) {
171
+ return `You are reviewing a Claude Code skill for security issues. Analyze the skill below and provide:
172
+
173
+ 1. **Security Assessment**: Evaluate trustworthiness, identify risks
174
+ 2. **External Dependencies**: Review URLs, APIs, network calls - are they trustworthy?
175
+ 3. **Command Safety**: Analyze shell commands - any dangerous patterns?
176
+ 4. **Bundled Resources**: Check for suspicious scripts, images, binaries
177
+ 5. **Prompt Injection Risks**: Could malicious input exploit this skill?
178
+ 6. **Data Exfiltration**: Does it send sensitive data externally?
179
+ 7. **Credential Handling**: How are API keys/secrets managed?
180
+ 8. **Input Validation**: Is user input properly sanitized?
181
+ 9. **File System Access**: What files does it read/write?
182
+ 10. **Scope Alignment**: Does behavior match stated purpose?
183
+ 11. **Recommendations**: Should I install this? What precautions?
184
+
185
+ **Skill**: ${skillName}
186
+ **Path**: ${skillPath}
187
+
188
+ **Quick Scan Findings**:
189
+ - Secrets detected: ${scan.secrets}
190
+ - External URLs: ${scan.urls.length}${scan.urls.length > 0 ? `\n ${scan.urls.join('\n ')}` : ''}
191
+ - High-risk commands: ${scan.highRiskCommands.length}${scan.highRiskCommands.length > 0 ? `\n ${scan.highRiskCommands.map(c => `${c.command} (line ${c.line})`).join('\n ')}` : ''}
192
+
193
+ **Skill Content**:
194
+ \`\`\`markdown
195
+ ${content}
196
+ \`\`\`
197
+
198
+ Provide a clear risk rating (LOW/MEDIUM/HIGH/CRITICAL) and actionable recommendations.`;
199
+ }
@@ -0,0 +1,65 @@
1
+ import { Command } from "commander";
2
+ import { AuditLogger } from "../../core/audit-logger.js";
3
+ export function createAuditCommand() {
4
+ return new Command("audit")
5
+ .description("View audit log entries")
6
+ .option("--last <n>", "Show last N entries", "10")
7
+ .option("--event <type>", "Filter by event type")
8
+ .option("--agent <type>", "Filter by agent type (openclaw, claude-code)")
9
+ .option("--since <date>", "Show entries since date (YYYY-MM-DD)")
10
+ .action((opts) => {
11
+ const logger = new AuditLogger();
12
+ const filter = {
13
+ limit: parseInt(opts.last, 10)
14
+ };
15
+ if (opts.event) {
16
+ filter.eventType = opts.event;
17
+ }
18
+ if (opts.agent) {
19
+ filter.agentType = opts.agent;
20
+ }
21
+ if (opts.since) {
22
+ filter.since = new Date(opts.since);
23
+ }
24
+ const entries = logger.read(filter);
25
+ if (entries.length === 0) {
26
+ console.log("No audit log entries found");
27
+ return;
28
+ }
29
+ console.log(`\nShowing ${entries.length} audit log entries:\n`);
30
+ for (const entry of entries) {
31
+ const timestamp = new Date(entry.timestamp).toLocaleString();
32
+ const emoji = getEventEmoji(entry.eventType);
33
+ console.log(`${emoji} [${timestamp}] ${entry.eventType}`);
34
+ if (entry.agentType) {
35
+ console.log(` Agent: ${entry.agentType}`);
36
+ }
37
+ if (entry.action?.command) {
38
+ console.log(` Command: ${entry.action.command}`);
39
+ }
40
+ if (entry.action?.riskLevel) {
41
+ console.log(` Risk: ${entry.action.riskLevel}`);
42
+ }
43
+ console.log(` Check: ${entry.securityCheck.passed ? "PASSED" : "FAILED"}`);
44
+ if (entry.securityCheck.reason) {
45
+ console.log(` Reason: ${entry.securityCheck.reason}`);
46
+ }
47
+ console.log(` Action: ${entry.resolution.actionTaken}`);
48
+ if (entry.resolution.overrideReason) {
49
+ console.log(` Override: ${entry.resolution.overrideReason}`);
50
+ }
51
+ console.log("");
52
+ }
53
+ });
54
+ }
55
+ function getEventEmoji(eventType) {
56
+ const emojiMap = {
57
+ command_intercepted: "šŸ›”ļø",
58
+ secret_detected: "šŸ”‘",
59
+ content_sanitized: "🧹",
60
+ policy_override: "āš ļø",
61
+ scan_executed: "šŸ”",
62
+ config_changed: "āš™ļø"
63
+ };
64
+ return emojiMap[eventType] || "šŸ“";
65
+ }
@@ -0,0 +1,54 @@
1
+ import { Command } from "commander";
2
+ import { ConfigManager } from "../../core/config-manager.js";
3
+ export function createConfigCommand() {
4
+ const config = new Command("config")
5
+ .description("Manage agent configuration");
6
+ // Show all config
7
+ config
8
+ .command("show")
9
+ .description("Show current configuration")
10
+ .action(() => {
11
+ const manager = new ConfigManager();
12
+ const cfg = manager.load();
13
+ console.log(JSON.stringify(cfg, null, 2));
14
+ });
15
+ // Get specific value
16
+ config
17
+ .command("get")
18
+ .description("Get a configuration value")
19
+ .argument("<key>", "Config key (e.g., agent.riskLevel)")
20
+ .action((key) => {
21
+ const manager = new ConfigManager();
22
+ const value = manager.get(key);
23
+ if (value === undefined) {
24
+ console.error(`Key not found: ${key}`);
25
+ process.exit(1);
26
+ }
27
+ if (typeof value === "object") {
28
+ console.log(JSON.stringify(value, null, 2));
29
+ }
30
+ else {
31
+ console.log(value);
32
+ }
33
+ });
34
+ // Set specific value
35
+ config
36
+ .command("set")
37
+ .description("Set a configuration value")
38
+ .argument("<key>", "Config key (e.g., agent.riskLevel)")
39
+ .argument("<value>", "Value to set")
40
+ .action((key, value) => {
41
+ const manager = new ConfigManager();
42
+ // Try to parse value as JSON, otherwise use as string
43
+ let parsedValue = value;
44
+ try {
45
+ parsedValue = JSON.parse(value);
46
+ }
47
+ catch {
48
+ // Use as string
49
+ }
50
+ manager.set(key, parsedValue);
51
+ console.log(`āœ“ Set ${key} = ${JSON.stringify(parsedValue)}`);
52
+ });
53
+ return config;
54
+ }
@@ -0,0 +1,120 @@
1
+ import { Command } from "commander";
2
+ import { CommandInterceptor } from "../../core/command-interceptor.js";
3
+ import { RegexScanner } from "../../scanners/regex-scanner.js";
4
+ import { execSync } from "child_process";
5
+ import readline from "readline";
6
+ export function createExecCommand() {
7
+ return new Command("exec")
8
+ .description("Execute command with security validation")
9
+ .argument("<command>", "Command to execute")
10
+ .option("--skip-scan", "Skip pre-execution file scanning")
11
+ .option("--force", "Skip approval prompts (use with caution)")
12
+ .action(async (command, opts) => {
13
+ const interceptor = new CommandInterceptor();
14
+ // Step 1: Evaluate command
15
+ const evaluation = interceptor.evaluate(command);
16
+ // Step 2: Handle blocked commands
17
+ if (!evaluation.allowed && !evaluation.requiresApproval) {
18
+ console.error(`\n🚫 Command BLOCKED\n`);
19
+ console.error(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
20
+ console.error(`Reason: ${evaluation.reason}`);
21
+ console.error(`Command: ${command}\n`);
22
+ interceptor.logEvaluation(evaluation, "blocked");
23
+ process.exit(1);
24
+ }
25
+ // Step 3: Pre-execution scanning for git commands
26
+ if (!opts.skipScan && isGitCommand(command)) {
27
+ const scanResult = await scanStagedFiles();
28
+ if (scanResult.secretsFound) {
29
+ console.error(`\nāš ļø Secrets detected in staged files!\n`);
30
+ console.error(`Found ${scanResult.count} secret(s) in ${scanResult.files} file(s)`);
31
+ console.error(`\nRun 'rafter agent scan' for details.\n`);
32
+ interceptor.logEvaluation(evaluation, "blocked");
33
+ process.exit(1);
34
+ }
35
+ }
36
+ // Step 4: Handle approval required
37
+ if (evaluation.requiresApproval && !opts.force) {
38
+ console.log(`\nāš ļø Command requires approval\n`);
39
+ console.log(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
40
+ console.log(`Command: ${command}`);
41
+ if (evaluation.reason) {
42
+ console.log(`Reason: ${evaluation.reason}`);
43
+ }
44
+ console.log();
45
+ const approved = await promptApproval();
46
+ if (!approved) {
47
+ console.log("\nāŒ Command cancelled\n");
48
+ interceptor.logEvaluation(evaluation, "blocked");
49
+ process.exit(1);
50
+ }
51
+ console.log("\nāœ“ Command approved by user\n");
52
+ interceptor.logEvaluation(evaluation, "overridden");
53
+ }
54
+ else if (opts.force && evaluation.requiresApproval) {
55
+ console.log(`\nāš ļø Forcing execution (--force flag)\n`);
56
+ interceptor.logEvaluation(evaluation, "overridden");
57
+ }
58
+ else {
59
+ interceptor.logEvaluation(evaluation, "allowed");
60
+ }
61
+ // Step 5: Execute command
62
+ try {
63
+ const output = execSync(command, {
64
+ stdio: "inherit",
65
+ encoding: "utf-8"
66
+ });
67
+ console.log(`\nāœ“ Command executed successfully\n`);
68
+ process.exit(0);
69
+ }
70
+ catch (e) {
71
+ console.error(`\nāŒ Command failed with exit code ${e.status}\n`);
72
+ process.exit(e.status || 1);
73
+ }
74
+ });
75
+ }
76
+ function isGitCommand(command) {
77
+ return command.trim().startsWith("git commit") ||
78
+ command.trim().startsWith("git push");
79
+ }
80
+ async function scanStagedFiles() {
81
+ try {
82
+ // Get staged files
83
+ const stagedFiles = execSync("git diff --cached --name-only", {
84
+ encoding: "utf-8",
85
+ stdio: ["pipe", "pipe", "ignore"]
86
+ })
87
+ .trim()
88
+ .split("\n")
89
+ .filter(f => f);
90
+ if (stagedFiles.length === 0) {
91
+ return { secretsFound: false, count: 0, files: 0 };
92
+ }
93
+ // Scan staged files
94
+ const scanner = new RegexScanner();
95
+ const results = scanner.scanFiles(stagedFiles);
96
+ const totalMatches = results.reduce((sum, r) => sum + r.matches.length, 0);
97
+ return {
98
+ secretsFound: results.length > 0,
99
+ count: totalMatches,
100
+ files: results.length
101
+ };
102
+ }
103
+ catch {
104
+ // If git command fails (not in repo, etc.), skip scanning
105
+ return { secretsFound: false, count: 0, files: 0 };
106
+ }
107
+ }
108
+ async function promptApproval() {
109
+ const rl = readline.createInterface({
110
+ input: process.stdin,
111
+ output: process.stdout
112
+ });
113
+ return new Promise((resolve) => {
114
+ rl.question("Approve this command? (yes/no): ", (answer) => {
115
+ rl.close();
116
+ const normalized = answer.trim().toLowerCase();
117
+ resolve(normalized === "yes" || normalized === "y");
118
+ });
119
+ });
120
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from "commander";
2
+ import { createAuditCommand } from "./audit.js";
3
+ import { createScanCommand } from "./scan.js";
4
+ import { createInitCommand } from "./init.js";
5
+ import { createConfigCommand } from "./config.js";
6
+ import { createExecCommand } from "./exec.js";
7
+ import { createAuditSkillCommand } from "./audit-skill.js";
8
+ import { createInstallHookCommand } from "./install-hook.js";
9
+ export function createAgentCommand() {
10
+ const agent = new Command("agent")
11
+ .description("Agent security features");
12
+ // Add subcommands
13
+ agent.addCommand(createInitCommand());
14
+ agent.addCommand(createScanCommand());
15
+ agent.addCommand(createExecCommand());
16
+ agent.addCommand(createConfigCommand());
17
+ agent.addCommand(createAuditCommand());
18
+ agent.addCommand(createAuditSkillCommand());
19
+ agent.addCommand(createInstallHookCommand());
20
+ return agent;
21
+ }
@@ -0,0 +1,162 @@
1
+ import { Command } from "commander";
2
+ import { ConfigManager } from "../../core/config-manager.js";
3
+ import { BinaryManager } from "../../utils/binary-manager.js";
4
+ import { SkillManager } from "../../utils/skill-manager.js";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+ import { fileURLToPath } from "url";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ async function installClaudeCodeSkills() {
12
+ const homeDir = os.homedir();
13
+ const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
14
+ // Ensure .claude/skills directory exists
15
+ if (!fs.existsSync(claudeSkillsDir)) {
16
+ fs.mkdirSync(claudeSkillsDir, { recursive: true });
17
+ }
18
+ // Install Backend Skill
19
+ const backendSkillDir = path.join(claudeSkillsDir, "rafter");
20
+ const backendSkillPath = path.join(backendSkillDir, "SKILL.md");
21
+ const backendTemplatePath = path.join(__dirname, "..", "..", "..", ".claude", "skills", "rafter", "SKILL.md");
22
+ if (!fs.existsSync(backendSkillDir)) {
23
+ fs.mkdirSync(backendSkillDir, { recursive: true });
24
+ }
25
+ if (fs.existsSync(backendTemplatePath)) {
26
+ fs.copyFileSync(backendTemplatePath, backendSkillPath);
27
+ console.log(`āœ“ Installed Rafter Backend skill to ${backendSkillPath}`);
28
+ }
29
+ else {
30
+ console.log(`āš ļø Backend skill template not found at ${backendTemplatePath}`);
31
+ }
32
+ // Install Agent Security Skill
33
+ const agentSkillDir = path.join(claudeSkillsDir, "rafter-agent-security");
34
+ const agentSkillPath = path.join(agentSkillDir, "SKILL.md");
35
+ const agentTemplatePath = path.join(__dirname, "..", "..", "..", ".claude", "skills", "rafter-agent-security", "SKILL.md");
36
+ if (!fs.existsSync(agentSkillDir)) {
37
+ fs.mkdirSync(agentSkillDir, { recursive: true });
38
+ }
39
+ if (fs.existsSync(agentTemplatePath)) {
40
+ fs.copyFileSync(agentTemplatePath, agentSkillPath);
41
+ console.log(`āœ“ Installed Rafter Agent Security skill to ${agentSkillPath}`);
42
+ }
43
+ else {
44
+ console.log(`āš ļø Agent Security skill template not found at ${agentTemplatePath}`);
45
+ }
46
+ }
47
+ export function createInitCommand() {
48
+ return new Command("init")
49
+ .description("Initialize agent security system")
50
+ .option("--risk-level <level>", "Set risk level (minimal, moderate, aggressive)", "moderate")
51
+ .option("--skip-openclaw", "Skip OpenClaw skill installation")
52
+ .option("--skip-claude-code", "Skip Claude Code skill installation")
53
+ .option("--claude-code", "Force Claude Code skill installation")
54
+ .option("--skip-gitleaks", "Skip Gitleaks binary download")
55
+ .action(async (opts) => {
56
+ console.log("\nšŸ›”ļø Rafter Agent Security Setup");
57
+ console.log("━".repeat(40));
58
+ console.log();
59
+ const manager = new ConfigManager();
60
+ // Detect environments
61
+ const hasOpenClaw = fs.existsSync(path.join(os.homedir(), ".openclaw"));
62
+ const hasClaudeCode = opts.claudeCode || fs.existsSync(path.join(os.homedir(), ".claude"));
63
+ if (hasOpenClaw) {
64
+ console.log("āœ“ Detected environment: OpenClaw");
65
+ }
66
+ else {
67
+ console.log("ā„¹ļø OpenClaw not detected");
68
+ }
69
+ if (hasClaudeCode) {
70
+ console.log("āœ“ Detected environment: Claude Code");
71
+ }
72
+ else {
73
+ console.log("ā„¹ļø Claude Code not detected");
74
+ }
75
+ // Initialize directory structure
76
+ try {
77
+ await manager.initialize();
78
+ console.log(`āœ“ Created config at ~/.rafter/config.json`);
79
+ }
80
+ catch (e) {
81
+ console.error(`Failed to initialize: ${e}`);
82
+ process.exit(1);
83
+ }
84
+ // Set risk level
85
+ const validRiskLevels = ["minimal", "moderate", "aggressive"];
86
+ if (!validRiskLevels.includes(opts.riskLevel)) {
87
+ console.error(`Invalid risk level: ${opts.riskLevel}`);
88
+ console.error(`Valid options: ${validRiskLevels.join(", ")}`);
89
+ process.exit(1);
90
+ }
91
+ manager.set("agent.riskLevel", opts.riskLevel);
92
+ console.log(`āœ“ Set risk level: ${opts.riskLevel}`);
93
+ // Download Gitleaks binary (optional)
94
+ if (!opts.skipGitleaks) {
95
+ const binaryManager = new BinaryManager();
96
+ const platformInfo = binaryManager.getPlatformInfo();
97
+ if (!platformInfo.supported) {
98
+ console.log(`ā„¹ļø Gitleaks not available for ${platformInfo.platform}/${platformInfo.arch}`);
99
+ console.log("āœ“ Using pattern-based scanning (21 patterns)");
100
+ }
101
+ else if (binaryManager.isGitleaksInstalled()) {
102
+ const version = await binaryManager.getGitleaksVersion();
103
+ console.log(`āœ“ Gitleaks already installed (${version})`);
104
+ }
105
+ else {
106
+ console.log();
107
+ console.log("šŸ“¦ Downloading Gitleaks (enhanced secret detection)...");
108
+ try {
109
+ await binaryManager.downloadGitleaks((msg) => {
110
+ console.log(` ${msg}`);
111
+ });
112
+ console.log();
113
+ }
114
+ catch (e) {
115
+ console.log(`āš ļø Failed to download Gitleaks: ${e}`);
116
+ console.log("āœ“ Falling back to pattern-based scanning");
117
+ console.log();
118
+ }
119
+ }
120
+ }
121
+ // Install OpenClaw skill if applicable
122
+ if (hasOpenClaw && !opts.skipOpenclaw) {
123
+ try {
124
+ const skillManager = new SkillManager();
125
+ const installed = await skillManager.installRafterSkill();
126
+ if (installed) {
127
+ console.log("āœ“ Installed Rafter Security skill to ~/.openclaw/skills/rafter-security.md");
128
+ manager.set("agent.environments.openclaw.enabled", true);
129
+ }
130
+ else {
131
+ console.log("āš ļø Failed to install Rafter Security skill");
132
+ }
133
+ }
134
+ catch (e) {
135
+ console.error(`Failed to install OpenClaw skill: ${e}`);
136
+ }
137
+ }
138
+ // Install Claude Code skills if applicable
139
+ if (hasClaudeCode && !opts.skipClaudeCode) {
140
+ try {
141
+ await installClaudeCodeSkills();
142
+ manager.set("agent.environments.claudeCode.enabled", true);
143
+ }
144
+ catch (e) {
145
+ console.error(`Failed to install Claude Code skills: ${e}`);
146
+ }
147
+ }
148
+ console.log();
149
+ console.log("āœ“ Agent security initialized!");
150
+ console.log();
151
+ console.log("Next steps:");
152
+ if (hasOpenClaw && !opts.skipOpenclaw) {
153
+ console.log(" - Restart OpenClaw to load skill");
154
+ }
155
+ if (hasClaudeCode && !opts.skipClaudeCode) {
156
+ console.log(" - Restart Claude Code to load skills");
157
+ }
158
+ console.log(" - Run: rafter agent scan . (test secret scanning)");
159
+ console.log(" - Configure: rafter agent config show");
160
+ console.log();
161
+ });
162
+ }