@rafter-security/cli 0.4.1 → 0.5.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/README.md +100 -0
- package/dist/commands/agent/audit.js +15 -3
- package/dist/commands/agent/exec.js +9 -8
- package/dist/commands/agent/init.js +62 -26
- package/dist/commands/agent/scan.js +113 -101
- package/dist/commands/ci/index.js +8 -0
- package/dist/commands/ci/init.js +191 -0
- package/dist/commands/hook/index.js +8 -0
- package/dist/commands/hook/pretool.js +122 -0
- package/dist/commands/mcp/index.js +8 -0
- package/dist/commands/mcp/server.js +205 -0
- package/dist/commands/policy/export.js +81 -0
- package/dist/commands/policy/index.js +8 -0
- package/dist/core/audit-logger.js +2 -33
- package/dist/core/command-interceptor.js +6 -50
- package/dist/core/config-defaults.js +4 -15
- package/dist/core/config-manager.js +52 -0
- package/dist/core/policy-loader.js +167 -0
- package/dist/core/risk-rules.js +67 -0
- package/dist/index.js +23 -2
- package/dist/scanners/gitleaks.js +7 -6
- package/dist/scanners/regex-scanner.js +22 -2
- package/dist/utils/formatter.js +52 -0
- package/package.json +7 -3
- package/resources/pre-commit-hook.sh +45 -0
- package/resources/rafter-security-skill.md +316 -0
package/README.md
CHANGED
|
@@ -73,6 +73,14 @@ rafter agent audit
|
|
|
73
73
|
rafter agent config show
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
## Global Options
|
|
77
|
+
|
|
78
|
+
| Flag | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `-a, --agent` | Plain output for AI agents (no colors, no emoji) |
|
|
81
|
+
| `-V, --version` | Print version |
|
|
82
|
+
| `-h, --help` | Show help |
|
|
83
|
+
|
|
76
84
|
## Commands
|
|
77
85
|
|
|
78
86
|
### `rafter run [options]`
|
|
@@ -169,6 +177,7 @@ Scan files or directories for secrets.
|
|
|
169
177
|
**Options:**
|
|
170
178
|
- `-q, --quiet` - Only output if secrets found
|
|
171
179
|
- `--json` - Output results as JSON
|
|
180
|
+
- `--diff <ref>` - Scan files changed since a git ref (e.g., `HEAD~1`, `main`, `v1.0.0`)
|
|
172
181
|
|
|
173
182
|
**Features:**
|
|
174
183
|
- Detects 21+ secret types (AWS, GitHub, Stripe, Google, Slack, etc.)
|
|
@@ -185,6 +194,10 @@ rafter agent scan
|
|
|
185
194
|
# Scan specific file
|
|
186
195
|
rafter agent scan ./config.js
|
|
187
196
|
|
|
197
|
+
# Scan files changed since a ref
|
|
198
|
+
rafter agent scan --diff HEAD~1
|
|
199
|
+
rafter agent scan --diff main
|
|
200
|
+
|
|
188
201
|
# Scan for CI (quiet mode)
|
|
189
202
|
rafter agent scan --quiet
|
|
190
203
|
|
|
@@ -433,6 +446,93 @@ Claude Code skills can:
|
|
|
433
446
|
|
|
434
447
|
Always audit skills from untrusted sources before installation. The skill-auditor provides systematic analysis to identify security risks.
|
|
435
448
|
|
|
449
|
+
### `rafter mcp serve [options]`
|
|
450
|
+
|
|
451
|
+
Start an MCP server exposing Rafter security tools over stdio transport. Any MCP-compatible client (Cursor, Windsurf, Claude Desktop, Cline, etc.) can connect.
|
|
452
|
+
|
|
453
|
+
**Options:**
|
|
454
|
+
- `--transport <type>` - Transport type (default: `stdio`)
|
|
455
|
+
|
|
456
|
+
**MCP client config:**
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"rafter": {
|
|
460
|
+
"command": "rafter",
|
|
461
|
+
"args": ["mcp", "serve"]
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Tools provided:**
|
|
467
|
+
|
|
468
|
+
| Tool | Description |
|
|
469
|
+
|------|-------------|
|
|
470
|
+
| `scan_secrets` | Scan files/directories for hardcoded secrets. Params: `path` (required), `engine` (auto/gitleaks/patterns) |
|
|
471
|
+
| `evaluate_command` | Check if a shell command is allowed by Rafter policy. Params: `command` (required) |
|
|
472
|
+
| `read_audit_log` | Read audit log entries. Params: `limit`, `event_type`, `since` |
|
|
473
|
+
| `get_config` | Read Rafter config. Params: `key` (optional dot-path) |
|
|
474
|
+
|
|
475
|
+
**Resources provided:**
|
|
476
|
+
|
|
477
|
+
| URI | Description |
|
|
478
|
+
|-----|-------------|
|
|
479
|
+
| `rafter://config` | Current Rafter configuration (JSON) |
|
|
480
|
+
| `rafter://policy` | Active security policy — merged `.rafter.yml` + config (JSON) |
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### `rafter ci init [options]`
|
|
485
|
+
|
|
486
|
+
Generate CI/CD pipeline configuration for secret scanning.
|
|
487
|
+
|
|
488
|
+
**Options:**
|
|
489
|
+
- `--platform <platform>` - CI platform: `github`, `gitlab`, `circleci` (default: auto-detect)
|
|
490
|
+
- `--output <path>` - Output file path (default: platform-specific)
|
|
491
|
+
- `--with-backend` - Include backend security audit job (requires `RAFTER_API_KEY`)
|
|
492
|
+
|
|
493
|
+
**Auto-detection:** Checks for `.github/`, `.gitlab-ci.yml`, `.circleci/` in the current directory.
|
|
494
|
+
|
|
495
|
+
**Examples:**
|
|
496
|
+
```bash
|
|
497
|
+
# Auto-detect platform
|
|
498
|
+
rafter ci init
|
|
499
|
+
|
|
500
|
+
# Generate GitHub Actions workflow
|
|
501
|
+
rafter ci init --platform github
|
|
502
|
+
|
|
503
|
+
# Include backend scanning job
|
|
504
|
+
rafter ci init --with-backend
|
|
505
|
+
|
|
506
|
+
# Custom output path
|
|
507
|
+
rafter ci init --output .github/workflows/security.yml
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Policy File (`.rafter.yml`)
|
|
513
|
+
|
|
514
|
+
Define per-project security policies by placing a `.rafter.yml` in your project root. The CLI walks from cwd up to git root looking for it.
|
|
515
|
+
|
|
516
|
+
```yaml
|
|
517
|
+
version: "1"
|
|
518
|
+
risk_level: moderate
|
|
519
|
+
command_policy:
|
|
520
|
+
mode: approve-dangerous
|
|
521
|
+
blocked_patterns: ["rm -rf /"]
|
|
522
|
+
require_approval: ["npm publish"]
|
|
523
|
+
scan:
|
|
524
|
+
exclude_paths: ["vendor/", "third_party/"]
|
|
525
|
+
custom_patterns:
|
|
526
|
+
- name: "Internal API Key"
|
|
527
|
+
regex: "INTERNAL_[A-Z0-9]{32}"
|
|
528
|
+
severity: critical
|
|
529
|
+
audit:
|
|
530
|
+
retention_days: 90
|
|
531
|
+
log_level: info
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Precedence:** Policy file values override `~/.rafter/config.json`. Arrays replace, not append.
|
|
535
|
+
|
|
436
536
|
---
|
|
437
537
|
|
|
438
538
|
## Configuration
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { AuditLogger } from "../../core/audit-logger.js";
|
|
3
|
+
import { isAgentMode } from "../../utils/formatter.js";
|
|
3
4
|
export function createAuditCommand() {
|
|
4
5
|
return new Command("audit")
|
|
5
6
|
.description("View audit log entries")
|
|
@@ -29,8 +30,8 @@ export function createAuditCommand() {
|
|
|
29
30
|
console.log(`\nShowing ${entries.length} audit log entries:\n`);
|
|
30
31
|
for (const entry of entries) {
|
|
31
32
|
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
32
|
-
const
|
|
33
|
-
console.log(`${
|
|
33
|
+
const indicator = getEventIndicator(entry.eventType);
|
|
34
|
+
console.log(`${indicator} [${timestamp}] ${entry.eventType}`);
|
|
34
35
|
if (entry.agentType) {
|
|
35
36
|
console.log(` Agent: ${entry.agentType}`);
|
|
36
37
|
}
|
|
@@ -52,7 +53,18 @@ export function createAuditCommand() {
|
|
|
52
53
|
}
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
|
-
function
|
|
56
|
+
function getEventIndicator(eventType) {
|
|
57
|
+
if (isAgentMode()) {
|
|
58
|
+
const tagMap = {
|
|
59
|
+
command_intercepted: "[INTERCEPT]",
|
|
60
|
+
secret_detected: "[SECRET]",
|
|
61
|
+
content_sanitized: "[SANITIZE]",
|
|
62
|
+
policy_override: "[OVERRIDE]",
|
|
63
|
+
scan_executed: "[SCAN]",
|
|
64
|
+
config_changed: "[CONFIG]",
|
|
65
|
+
};
|
|
66
|
+
return tagMap[eventType] || "[EVENT]";
|
|
67
|
+
}
|
|
56
68
|
const emojiMap = {
|
|
57
69
|
command_intercepted: "🛡️",
|
|
58
70
|
secret_detected: "🔑",
|
|
@@ -3,6 +3,7 @@ import { CommandInterceptor } from "../../core/command-interceptor.js";
|
|
|
3
3
|
import { RegexScanner } from "../../scanners/regex-scanner.js";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import readline from "readline";
|
|
6
|
+
import { fmt } from "../../utils/formatter.js";
|
|
6
7
|
export function createExecCommand() {
|
|
7
8
|
return new Command("exec")
|
|
8
9
|
.description("Execute command with security validation")
|
|
@@ -15,7 +16,7 @@ export function createExecCommand() {
|
|
|
15
16
|
const evaluation = interceptor.evaluate(command);
|
|
16
17
|
// Step 2: Handle blocked commands
|
|
17
18
|
if (!evaluation.allowed && !evaluation.requiresApproval) {
|
|
18
|
-
console.error(`\n
|
|
19
|
+
console.error(`\n${fmt.error("Command BLOCKED")}\n`);
|
|
19
20
|
console.error(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
|
|
20
21
|
console.error(`Reason: ${evaluation.reason}`);
|
|
21
22
|
console.error(`Command: ${command}\n`);
|
|
@@ -26,7 +27,7 @@ export function createExecCommand() {
|
|
|
26
27
|
if (!opts.skipScan && isGitCommand(command)) {
|
|
27
28
|
const scanResult = await scanStagedFiles();
|
|
28
29
|
if (scanResult.secretsFound) {
|
|
29
|
-
console.error(`\n
|
|
30
|
+
console.error(`\n${fmt.warning("Secrets detected in staged files!")}\n`);
|
|
30
31
|
console.error(`Found ${scanResult.count} secret(s) in ${scanResult.files} file(s)`);
|
|
31
32
|
console.error(`\nRun 'rafter agent scan' for details.\n`);
|
|
32
33
|
interceptor.logEvaluation(evaluation, "blocked");
|
|
@@ -35,7 +36,7 @@ export function createExecCommand() {
|
|
|
35
36
|
}
|
|
36
37
|
// Step 4: Handle approval required
|
|
37
38
|
if (evaluation.requiresApproval && !opts.force) {
|
|
38
|
-
console.log(`\n
|
|
39
|
+
console.log(`\n${fmt.warning("Command requires approval")}\n`);
|
|
39
40
|
console.log(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
|
|
40
41
|
console.log(`Command: ${command}`);
|
|
41
42
|
if (evaluation.reason) {
|
|
@@ -44,15 +45,15 @@ export function createExecCommand() {
|
|
|
44
45
|
console.log();
|
|
45
46
|
const approved = await promptApproval();
|
|
46
47
|
if (!approved) {
|
|
47
|
-
console.log("
|
|
48
|
+
console.log(`\n${fmt.error("Command cancelled")}\n`);
|
|
48
49
|
interceptor.logEvaluation(evaluation, "blocked");
|
|
49
50
|
process.exit(1);
|
|
50
51
|
}
|
|
51
|
-
console.log("
|
|
52
|
+
console.log(`\n${fmt.success("Command approved by user")}\n`);
|
|
52
53
|
interceptor.logEvaluation(evaluation, "overridden");
|
|
53
54
|
}
|
|
54
55
|
else if (opts.force && evaluation.requiresApproval) {
|
|
55
|
-
console.log(`\n
|
|
56
|
+
console.log(`\n${fmt.warning("Forcing execution (--force flag)")}\n`);
|
|
56
57
|
interceptor.logEvaluation(evaluation, "overridden");
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
@@ -64,11 +65,11 @@ export function createExecCommand() {
|
|
|
64
65
|
stdio: "inherit",
|
|
65
66
|
encoding: "utf-8"
|
|
66
67
|
});
|
|
67
|
-
console.log(`\n
|
|
68
|
+
console.log(`\n${fmt.success("Command executed successfully")}\n`);
|
|
68
69
|
process.exit(0);
|
|
69
70
|
}
|
|
70
71
|
catch (e) {
|
|
71
|
-
console.error(`\n
|
|
72
|
+
console.error(`\n${fmt.error(`Command failed with exit code ${e.status}`)}\n`);
|
|
72
73
|
process.exit(e.status || 1);
|
|
73
74
|
}
|
|
74
75
|
});
|
|
@@ -6,8 +6,43 @@ import fs from "fs";
|
|
|
6
6
|
import path from "path";
|
|
7
7
|
import os from "os";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
+
import { fmt } from "../../utils/formatter.js";
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
12
|
+
function installClaudeCodeHooks() {
|
|
13
|
+
const homeDir = os.homedir();
|
|
14
|
+
const settingsPath = path.join(homeDir, ".claude", "settings.json");
|
|
15
|
+
const claudeDir = path.join(homeDir, ".claude");
|
|
16
|
+
if (!fs.existsSync(claudeDir)) {
|
|
17
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
// Read existing settings or start fresh
|
|
20
|
+
let settings = {};
|
|
21
|
+
if (fs.existsSync(settingsPath)) {
|
|
22
|
+
try {
|
|
23
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Corrupted file — start fresh but warn
|
|
27
|
+
console.log(fmt.warning("Existing settings.json was unreadable, creating new one"));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Merge hooks — don't overwrite existing non-Rafter hooks
|
|
31
|
+
if (!settings.hooks)
|
|
32
|
+
settings.hooks = {};
|
|
33
|
+
if (!settings.hooks.PreToolUse)
|
|
34
|
+
settings.hooks.PreToolUse = [];
|
|
35
|
+
const rafterHook = { type: "command", command: "rafter hook pretool" };
|
|
36
|
+
// Remove any existing Rafter hooks to avoid duplicates
|
|
37
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => {
|
|
38
|
+
const hooks = entry.hooks || [];
|
|
39
|
+
return !hooks.some((h) => h.command === "rafter hook pretool");
|
|
40
|
+
});
|
|
41
|
+
// Add Rafter hooks
|
|
42
|
+
settings.hooks.PreToolUse.push({ matcher: "Bash", hooks: [rafterHook] }, { matcher: "Write|Edit", hooks: [rafterHook] });
|
|
43
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
44
|
+
console.log(fmt.success(`Installed PreToolUse hooks to ${settingsPath}`));
|
|
45
|
+
}
|
|
11
46
|
async function installClaudeCodeSkills() {
|
|
12
47
|
const homeDir = os.homedir();
|
|
13
48
|
const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
|
|
@@ -24,10 +59,10 @@ async function installClaudeCodeSkills() {
|
|
|
24
59
|
}
|
|
25
60
|
if (fs.existsSync(backendTemplatePath)) {
|
|
26
61
|
fs.copyFileSync(backendTemplatePath, backendSkillPath);
|
|
27
|
-
console.log(
|
|
62
|
+
console.log(fmt.success(`Installed Rafter Backend skill to ${backendSkillPath}`));
|
|
28
63
|
}
|
|
29
64
|
else {
|
|
30
|
-
console.log(
|
|
65
|
+
console.log(fmt.warning(`Backend skill template not found at ${backendTemplatePath}`));
|
|
31
66
|
}
|
|
32
67
|
// Install Agent Security Skill
|
|
33
68
|
const agentSkillDir = path.join(claudeSkillsDir, "rafter-agent-security");
|
|
@@ -38,10 +73,10 @@ async function installClaudeCodeSkills() {
|
|
|
38
73
|
}
|
|
39
74
|
if (fs.existsSync(agentTemplatePath)) {
|
|
40
75
|
fs.copyFileSync(agentTemplatePath, agentSkillPath);
|
|
41
|
-
console.log(
|
|
76
|
+
console.log(fmt.success(`Installed Rafter Agent Security skill to ${agentSkillPath}`));
|
|
42
77
|
}
|
|
43
78
|
else {
|
|
44
|
-
console.log(
|
|
79
|
+
console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
|
|
45
80
|
}
|
|
46
81
|
}
|
|
47
82
|
export function createInitCommand() {
|
|
@@ -53,58 +88,58 @@ export function createInitCommand() {
|
|
|
53
88
|
.option("--claude-code", "Force Claude Code skill installation")
|
|
54
89
|
.option("--skip-gitleaks", "Skip Gitleaks binary download")
|
|
55
90
|
.action(async (opts) => {
|
|
56
|
-
console.log("
|
|
57
|
-
console.log(
|
|
91
|
+
console.log(fmt.header("Rafter Agent Security Setup"));
|
|
92
|
+
console.log(fmt.divider());
|
|
58
93
|
console.log();
|
|
59
94
|
const manager = new ConfigManager();
|
|
60
95
|
// Detect environments
|
|
61
96
|
const hasOpenClaw = fs.existsSync(path.join(os.homedir(), ".openclaw"));
|
|
62
97
|
const hasClaudeCode = opts.claudeCode || fs.existsSync(path.join(os.homedir(), ".claude"));
|
|
63
98
|
if (hasOpenClaw) {
|
|
64
|
-
console.log("
|
|
99
|
+
console.log(fmt.success("Detected environment: OpenClaw"));
|
|
65
100
|
}
|
|
66
101
|
else {
|
|
67
|
-
console.log("
|
|
102
|
+
console.log(fmt.info("OpenClaw not detected"));
|
|
68
103
|
}
|
|
69
104
|
if (hasClaudeCode) {
|
|
70
|
-
console.log("
|
|
105
|
+
console.log(fmt.success("Detected environment: Claude Code"));
|
|
71
106
|
}
|
|
72
107
|
else {
|
|
73
|
-
console.log("
|
|
108
|
+
console.log(fmt.info("Claude Code not detected"));
|
|
74
109
|
}
|
|
75
110
|
// Initialize directory structure
|
|
76
111
|
try {
|
|
77
112
|
await manager.initialize();
|
|
78
|
-
console.log(
|
|
113
|
+
console.log(fmt.success("Created config at ~/.rafter/config.json"));
|
|
79
114
|
}
|
|
80
115
|
catch (e) {
|
|
81
|
-
console.error(`Failed to initialize: ${e}`);
|
|
116
|
+
console.error(fmt.error(`Failed to initialize: ${e}`));
|
|
82
117
|
process.exit(1);
|
|
83
118
|
}
|
|
84
119
|
// Set risk level
|
|
85
120
|
const validRiskLevels = ["minimal", "moderate", "aggressive"];
|
|
86
121
|
if (!validRiskLevels.includes(opts.riskLevel)) {
|
|
87
|
-
console.error(`Invalid risk level: ${opts.riskLevel}`);
|
|
122
|
+
console.error(fmt.error(`Invalid risk level: ${opts.riskLevel}`));
|
|
88
123
|
console.error(`Valid options: ${validRiskLevels.join(", ")}`);
|
|
89
124
|
process.exit(1);
|
|
90
125
|
}
|
|
91
126
|
manager.set("agent.riskLevel", opts.riskLevel);
|
|
92
|
-
console.log(
|
|
127
|
+
console.log(fmt.success(`Set risk level: ${opts.riskLevel}`));
|
|
93
128
|
// Download Gitleaks binary (optional)
|
|
94
129
|
if (!opts.skipGitleaks) {
|
|
95
130
|
const binaryManager = new BinaryManager();
|
|
96
131
|
const platformInfo = binaryManager.getPlatformInfo();
|
|
97
132
|
if (!platformInfo.supported) {
|
|
98
|
-
console.log(
|
|
99
|
-
console.log("
|
|
133
|
+
console.log(fmt.info(`Gitleaks not available for ${platformInfo.platform}/${platformInfo.arch}`));
|
|
134
|
+
console.log(fmt.success("Using pattern-based scanning (21 patterns)"));
|
|
100
135
|
}
|
|
101
136
|
else if (binaryManager.isGitleaksInstalled()) {
|
|
102
137
|
const version = await binaryManager.getGitleaksVersion();
|
|
103
|
-
console.log(
|
|
138
|
+
console.log(fmt.success(`Gitleaks already installed (${version})`));
|
|
104
139
|
}
|
|
105
140
|
else {
|
|
106
141
|
console.log();
|
|
107
|
-
console.log("
|
|
142
|
+
console.log(fmt.info("Downloading Gitleaks (enhanced secret detection)..."));
|
|
108
143
|
try {
|
|
109
144
|
await binaryManager.downloadGitleaks((msg) => {
|
|
110
145
|
console.log(` ${msg}`);
|
|
@@ -112,8 +147,8 @@ export function createInitCommand() {
|
|
|
112
147
|
console.log();
|
|
113
148
|
}
|
|
114
149
|
catch (e) {
|
|
115
|
-
console.log(
|
|
116
|
-
console.log("
|
|
150
|
+
console.log(fmt.warning(`Failed to download Gitleaks: ${e}`));
|
|
151
|
+
console.log(fmt.success("Falling back to pattern-based scanning"));
|
|
117
152
|
console.log();
|
|
118
153
|
}
|
|
119
154
|
}
|
|
@@ -124,29 +159,30 @@ export function createInitCommand() {
|
|
|
124
159
|
const skillManager = new SkillManager();
|
|
125
160
|
const installed = await skillManager.installRafterSkill();
|
|
126
161
|
if (installed) {
|
|
127
|
-
console.log("
|
|
162
|
+
console.log(fmt.success("Installed Rafter Security skill to ~/.openclaw/skills/rafter-security.md"));
|
|
128
163
|
manager.set("agent.environments.openclaw.enabled", true);
|
|
129
164
|
}
|
|
130
165
|
else {
|
|
131
|
-
console.log("
|
|
166
|
+
console.log(fmt.warning("Failed to install Rafter Security skill"));
|
|
132
167
|
}
|
|
133
168
|
}
|
|
134
169
|
catch (e) {
|
|
135
|
-
console.error(`Failed to install OpenClaw skill: ${e}`);
|
|
170
|
+
console.error(fmt.error(`Failed to install OpenClaw skill: ${e}`));
|
|
136
171
|
}
|
|
137
172
|
}
|
|
138
|
-
// Install Claude Code skills if applicable
|
|
173
|
+
// Install Claude Code skills + hooks if applicable
|
|
139
174
|
if (hasClaudeCode && !opts.skipClaudeCode) {
|
|
140
175
|
try {
|
|
141
176
|
await installClaudeCodeSkills();
|
|
177
|
+
installClaudeCodeHooks();
|
|
142
178
|
manager.set("agent.environments.claudeCode.enabled", true);
|
|
143
179
|
}
|
|
144
180
|
catch (e) {
|
|
145
|
-
console.error(`Failed to install Claude Code
|
|
181
|
+
console.error(fmt.error(`Failed to install Claude Code integration: ${e}`));
|
|
146
182
|
}
|
|
147
183
|
}
|
|
148
184
|
console.log();
|
|
149
|
-
console.log("
|
|
185
|
+
console.log(fmt.success("Agent security initialized!"));
|
|
150
186
|
console.log();
|
|
151
187
|
console.log("Next steps:");
|
|
152
188
|
if (hasOpenClaw && !opts.skipOpenclaw) {
|