@rafter-security/cli 0.5.5 → 0.5.9
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 +15 -3
- package/dist/commands/agent/audit-skill.js +1 -1
- package/dist/commands/agent/audit.js +96 -0
- package/dist/commands/agent/baseline.js +10 -0
- package/dist/commands/agent/exec.js +1 -1
- package/dist/commands/agent/init.js +366 -26
- package/dist/commands/agent/scan.js +157 -16
- package/dist/commands/agent/status.js +65 -4
- package/dist/commands/agent/verify.js +18 -4
- package/dist/commands/backend/run.js +69 -61
- package/dist/commands/ci/init.js +10 -3
- package/dist/commands/completion.js +21 -9
- package/dist/commands/hook/posttool.js +21 -7
- package/dist/commands/hook/pretool.js +50 -13
- package/dist/commands/issues/dedup.js +39 -0
- package/dist/commands/issues/from-scan.js +143 -0
- package/dist/commands/issues/from-text.js +185 -0
- package/dist/commands/issues/github-client.js +85 -0
- package/dist/commands/issues/index.js +25 -0
- package/dist/commands/issues/issue-builder.js +101 -0
- package/dist/commands/policy/export.js +7 -2
- package/dist/commands/scan/index.js +44 -0
- package/dist/core/config-defaults.js +24 -0
- package/dist/core/config-manager.js +19 -2
- package/dist/core/pattern-engine.js +26 -1
- package/dist/index.js +8 -2
- package/dist/scanners/gitleaks.js +5 -5
- package/dist/scanners/regex-scanner.js +12 -1
- package/dist/scanners/secret-patterns.js +3 -3
- package/dist/utils/binary-manager.js +7 -6
- package/dist/utils/skill-manager.js +5 -3
- package/package.json +2 -1
- package/resources/pre-commit-hook.sh +2 -2
- package/resources/pre-push-hook.sh +2 -2
- package/resources/rafter-security-skill.md +7 -11
package/README.md
CHANGED
|
@@ -153,17 +153,29 @@ Initialize agent security system.
|
|
|
153
153
|
|
|
154
154
|
**Options:**
|
|
155
155
|
- `--risk-level <level>` - Set risk level: `minimal`, `moderate`, or `aggressive` (default: `moderate`)
|
|
156
|
-
- `--
|
|
156
|
+
- `--with-openclaw` - Install OpenClaw integration
|
|
157
|
+
- `--with-claude-code` - Install Claude Code integration
|
|
158
|
+
- `--with-codex` - Install Codex CLI integration
|
|
159
|
+
- `--with-gemini` - Install Gemini CLI integration
|
|
160
|
+
- `--with-aider` - Install Aider integration
|
|
161
|
+
- `--with-cursor` - Install Cursor integration
|
|
162
|
+
- `--with-windsurf` - Install Windsurf integration
|
|
163
|
+
- `--with-continue` - Install Continue.dev integration
|
|
164
|
+
- `--with-gitleaks` - Download and install Gitleaks binary
|
|
165
|
+
- `--all` - Install all detected integrations and download Gitleaks
|
|
157
166
|
|
|
158
167
|
**What it does:**
|
|
159
168
|
- Creates `~/.rafter/config.json` configuration
|
|
160
169
|
- Initializes directory structure
|
|
161
|
-
- Detects
|
|
170
|
+
- Detects available agent environments
|
|
171
|
+
- Installs opted-in integrations (skills, hooks, MCP servers)
|
|
162
172
|
- Sets up audit logging
|
|
163
173
|
|
|
164
174
|
**Example:**
|
|
165
175
|
```bash
|
|
166
|
-
rafter agent init
|
|
176
|
+
rafter agent init # Config only, detect environments
|
|
177
|
+
rafter agent init --all # Install all detected integrations
|
|
178
|
+
rafter agent init --with-claude-code # Install Claude Code integration only
|
|
167
179
|
rafter agent init --risk-level aggressive
|
|
168
180
|
```
|
|
169
181
|
|
|
@@ -143,7 +143,7 @@ function displayQuickScan(scan, skillName) {
|
|
|
143
143
|
else {
|
|
144
144
|
console.log(`⚠️ Secrets: ${scan.secrets} found`);
|
|
145
145
|
console.log(" → API keys, tokens, or credentials detected");
|
|
146
|
-
console.log(" → Run: rafter
|
|
146
|
+
console.log(" → Run: rafter scan local <path> for details");
|
|
147
147
|
}
|
|
148
148
|
// URLs
|
|
149
149
|
if (scan.urls.length === 0) {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
1
5
|
import { Command } from "commander";
|
|
2
6
|
import { AuditLogger } from "../../core/audit-logger.js";
|
|
7
|
+
import { ConfigManager } from "../../core/config-manager.js";
|
|
3
8
|
import { isAgentMode } from "../../utils/formatter.js";
|
|
4
9
|
export function createAuditCommand() {
|
|
5
10
|
return new Command("audit")
|
|
@@ -8,7 +13,12 @@ export function createAuditCommand() {
|
|
|
8
13
|
.option("--event <type>", "Filter by event type")
|
|
9
14
|
.option("--agent <type>", "Filter by agent type (openclaw, claude-code)")
|
|
10
15
|
.option("--since <date>", "Show entries since date (YYYY-MM-DD)")
|
|
16
|
+
.option("--share", "Generate a redacted excerpt for issue reports")
|
|
11
17
|
.action((opts) => {
|
|
18
|
+
if (opts.share) {
|
|
19
|
+
generateShareExcerpt();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
12
22
|
const logger = new AuditLogger();
|
|
13
23
|
const filter = {
|
|
14
24
|
limit: parseInt(opts.last, 10)
|
|
@@ -53,6 +63,92 @@ export function createAuditCommand() {
|
|
|
53
63
|
}
|
|
54
64
|
});
|
|
55
65
|
}
|
|
66
|
+
export function generateShareExcerpt() {
|
|
67
|
+
const version = getPkgVersion();
|
|
68
|
+
const os = `${process.platform}/${process.arch}`;
|
|
69
|
+
const timestamp = new Date().toISOString();
|
|
70
|
+
const config = new ConfigManager().loadWithPolicy();
|
|
71
|
+
const policyHash = computePolicyHash(config);
|
|
72
|
+
const riskLevel = getRiskLevel(config);
|
|
73
|
+
const logger = new AuditLogger();
|
|
74
|
+
const entries = logger.read({ limit: 5 });
|
|
75
|
+
const lines = [
|
|
76
|
+
"Rafter Audit Excerpt",
|
|
77
|
+
`Generated: ${timestamp}`,
|
|
78
|
+
"",
|
|
79
|
+
"Environment:",
|
|
80
|
+
` CLI: ${version}`,
|
|
81
|
+
` OS: ${os}`,
|
|
82
|
+
` Policy: sha256:${policyHash} (${riskLevel})`,
|
|
83
|
+
"",
|
|
84
|
+
"Recent events (last 5):",
|
|
85
|
+
];
|
|
86
|
+
if (entries.length === 0) {
|
|
87
|
+
lines.push(" (no entries)");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const ts = entry.timestamp.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
92
|
+
const eventPad = entry.eventType.padEnd(20);
|
|
93
|
+
const riskRaw = entry.action?.riskLevel ?? "low";
|
|
94
|
+
const riskPad = riskRaw.toUpperCase().padEnd(8);
|
|
95
|
+
const detail = formatShareDetail(entry);
|
|
96
|
+
lines.push(` ${ts} ${eventPad} ${riskPad} ${detail}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
lines.push("");
|
|
100
|
+
lines.push("Share this excerpt when reporting issues at https://github.com/Raftersecurity/rafter-cli/issues");
|
|
101
|
+
console.log(lines.join("\n"));
|
|
102
|
+
}
|
|
103
|
+
export function getPkgVersion() {
|
|
104
|
+
try {
|
|
105
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
106
|
+
const __dirname = path.dirname(__filename);
|
|
107
|
+
// Walk up from dist/commands/agent/ to find package.json
|
|
108
|
+
let dir = __dirname;
|
|
109
|
+
for (let i = 0; i < 6; i++) {
|
|
110
|
+
const candidate = path.join(dir, "package.json");
|
|
111
|
+
if (fs.existsSync(candidate)) {
|
|
112
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
113
|
+
if (pkg.version)
|
|
114
|
+
return pkg.version;
|
|
115
|
+
}
|
|
116
|
+
dir = path.dirname(dir);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// fall through
|
|
121
|
+
}
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
export function computePolicyHash(config) {
|
|
125
|
+
const patterns = config?.agent?.commandPolicy?.requireApproval ?? [];
|
|
126
|
+
const payload = JSON.stringify(patterns.slice().sort());
|
|
127
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
128
|
+
}
|
|
129
|
+
export function getRiskLevel(config) {
|
|
130
|
+
return config?.agent?.riskLevel ?? "moderate";
|
|
131
|
+
}
|
|
132
|
+
export function formatShareDetail(entry) {
|
|
133
|
+
const action = entry.resolution.actionTaken;
|
|
134
|
+
const suffix = `[${action}]`;
|
|
135
|
+
if (entry.eventType === "secret_detected") {
|
|
136
|
+
const reason = entry.securityCheck.reason ?? "";
|
|
137
|
+
return `${reason} ${suffix}`;
|
|
138
|
+
}
|
|
139
|
+
if (entry.action?.command) {
|
|
140
|
+
return `${truncateCommand(entry.action.command, 60)} ${suffix}`;
|
|
141
|
+
}
|
|
142
|
+
if (entry.securityCheck.reason) {
|
|
143
|
+
return `${entry.securityCheck.reason} ${suffix}`;
|
|
144
|
+
}
|
|
145
|
+
return suffix;
|
|
146
|
+
}
|
|
147
|
+
export function truncateCommand(cmd, maxLen = 60) {
|
|
148
|
+
if (cmd.length <= maxLen)
|
|
149
|
+
return cmd;
|
|
150
|
+
return cmd.slice(0, maxLen) + "...";
|
|
151
|
+
}
|
|
56
152
|
function getEventIndicator(eventType) {
|
|
57
153
|
if (isAgentMode()) {
|
|
58
154
|
const tagMap = {
|
|
@@ -40,6 +40,12 @@ function createBaselineCreateCommand() {
|
|
|
40
40
|
.argument("[path]", "Path to scan", ".")
|
|
41
41
|
.option("--engine <engine>", "Scan engine: gitleaks or patterns", "auto")
|
|
42
42
|
.action(async (scanPath, opts) => {
|
|
43
|
+
const validEngines = ["auto", "gitleaks", "patterns"];
|
|
44
|
+
const engineValue = opts.engine || "auto";
|
|
45
|
+
if (!validEngines.includes(engineValue)) {
|
|
46
|
+
console.error(`Invalid engine: ${engineValue}. Valid values: ${validEngines.join(", ")}`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
43
49
|
const resolvedPath = path.resolve(scanPath);
|
|
44
50
|
if (!fs.existsSync(resolvedPath)) {
|
|
45
51
|
console.error(`Error: Path not found: ${resolvedPath}`);
|
|
@@ -167,6 +173,10 @@ async function selectEngine(preference) {
|
|
|
167
173
|
const g = new GitleaksScanner();
|
|
168
174
|
return (await g.isAvailable()) ? "gitleaks" : "patterns";
|
|
169
175
|
}
|
|
176
|
+
if (preference !== "auto") {
|
|
177
|
+
console.error(`Invalid engine: ${preference}. Valid values: auto, gitleaks, patterns`);
|
|
178
|
+
process.exit(2);
|
|
179
|
+
}
|
|
170
180
|
const g = new GitleaksScanner();
|
|
171
181
|
return (await g.isAvailable()) ? "gitleaks" : "patterns";
|
|
172
182
|
}
|
|
@@ -29,7 +29,7 @@ export function createExecCommand() {
|
|
|
29
29
|
if (scanResult.secretsFound) {
|
|
30
30
|
console.error(`\n${fmt.warning("Secrets detected in staged files!")}\n`);
|
|
31
31
|
console.error(`Found ${scanResult.count} secret(s) in ${scanResult.files} file(s)`);
|
|
32
|
-
console.error(`\nRun 'rafter
|
|
32
|
+
console.error(`\nRun 'rafter scan local' for details.\n`);
|
|
33
33
|
interceptor.logEvaluation(evaluation, "blocked");
|
|
34
34
|
process.exit(1);
|
|
35
35
|
}
|
|
@@ -52,6 +52,150 @@ function installClaudeCodeHooks() {
|
|
|
52
52
|
console.log(fmt.success(`Installed PreToolUse hooks to ${settingsPath}`));
|
|
53
53
|
console.log(fmt.success(`Installed PostToolUse hooks to ${settingsPath}`));
|
|
54
54
|
}
|
|
55
|
+
/** MCP server entry for rafter — shared across MCP-native clients */
|
|
56
|
+
const RAFTER_MCP_ENTRY = {
|
|
57
|
+
command: "rafter",
|
|
58
|
+
args: ["mcp", "serve"],
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Install MCP server config for Gemini CLI (~/.gemini/settings.json)
|
|
62
|
+
*/
|
|
63
|
+
function installGeminiMcp() {
|
|
64
|
+
const homeDir = os.homedir();
|
|
65
|
+
const geminiDir = path.join(homeDir, ".gemini");
|
|
66
|
+
const settingsPath = path.join(geminiDir, "settings.json");
|
|
67
|
+
if (!fs.existsSync(geminiDir)) {
|
|
68
|
+
fs.mkdirSync(geminiDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
let settings = {};
|
|
71
|
+
if (fs.existsSync(settingsPath)) {
|
|
72
|
+
try {
|
|
73
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
console.log(fmt.warning("Existing Gemini settings.json was unreadable, creating new one"));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!settings.mcpServers)
|
|
80
|
+
settings.mcpServers = {};
|
|
81
|
+
settings.mcpServers.rafter = { ...RAFTER_MCP_ENTRY };
|
|
82
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
83
|
+
console.log(fmt.success(`Installed Rafter MCP server to ${settingsPath}`));
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Install MCP server config for Cursor (~/.cursor/mcp.json)
|
|
88
|
+
*/
|
|
89
|
+
function installCursorMcp() {
|
|
90
|
+
const homeDir = os.homedir();
|
|
91
|
+
const cursorDir = path.join(homeDir, ".cursor");
|
|
92
|
+
const mcpPath = path.join(cursorDir, "mcp.json");
|
|
93
|
+
if (!fs.existsSync(cursorDir)) {
|
|
94
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
let config = {};
|
|
97
|
+
if (fs.existsSync(mcpPath)) {
|
|
98
|
+
try {
|
|
99
|
+
config = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
console.log(fmt.warning("Existing Cursor mcp.json was unreadable, creating new one"));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!config.mcpServers)
|
|
106
|
+
config.mcpServers = {};
|
|
107
|
+
config.mcpServers.rafter = { ...RAFTER_MCP_ENTRY };
|
|
108
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
109
|
+
console.log(fmt.success(`Installed Rafter MCP server to ${mcpPath}`));
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Install MCP server config for Windsurf (~/.codeium/windsurf/mcp_config.json)
|
|
114
|
+
*/
|
|
115
|
+
function installWindsurfMcp() {
|
|
116
|
+
const homeDir = os.homedir();
|
|
117
|
+
const windsurfDir = path.join(homeDir, ".codeium", "windsurf");
|
|
118
|
+
const mcpPath = path.join(windsurfDir, "mcp_config.json");
|
|
119
|
+
if (!fs.existsSync(windsurfDir)) {
|
|
120
|
+
fs.mkdirSync(windsurfDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
let config = {};
|
|
123
|
+
if (fs.existsSync(mcpPath)) {
|
|
124
|
+
try {
|
|
125
|
+
config = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
console.log(fmt.warning("Existing Windsurf mcp_config.json was unreadable, creating new one"));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!config.mcpServers)
|
|
132
|
+
config.mcpServers = {};
|
|
133
|
+
config.mcpServers.rafter = { ...RAFTER_MCP_ENTRY };
|
|
134
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
135
|
+
console.log(fmt.success(`Installed Rafter MCP server to ${mcpPath}`));
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Install MCP server config for Continue.dev (~/.continue/config.json)
|
|
140
|
+
*/
|
|
141
|
+
function installContinueDevMcp() {
|
|
142
|
+
const homeDir = os.homedir();
|
|
143
|
+
const continueDir = path.join(homeDir, ".continue");
|
|
144
|
+
const configPath = path.join(continueDir, "config.json");
|
|
145
|
+
if (!fs.existsSync(continueDir)) {
|
|
146
|
+
fs.mkdirSync(continueDir, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
let config = {};
|
|
149
|
+
if (fs.existsSync(configPath)) {
|
|
150
|
+
try {
|
|
151
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
console.log(fmt.warning("Existing Continue.dev config.json was unreadable, creating new one"));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!config.mcpServers)
|
|
158
|
+
config.mcpServers = [];
|
|
159
|
+
// Remove existing rafter entry if present (array format)
|
|
160
|
+
if (Array.isArray(config.mcpServers)) {
|
|
161
|
+
config.mcpServers = config.mcpServers.filter((s) => s.name !== "rafter");
|
|
162
|
+
config.mcpServers.push({
|
|
163
|
+
name: "rafter",
|
|
164
|
+
command: RAFTER_MCP_ENTRY.command,
|
|
165
|
+
args: RAFTER_MCP_ENTRY.args,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Object format (newer Continue.dev versions)
|
|
170
|
+
config.mcpServers.rafter = { ...RAFTER_MCP_ENTRY };
|
|
171
|
+
}
|
|
172
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
173
|
+
console.log(fmt.success(`Installed Rafter MCP server to ${configPath}`));
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Install MCP config for Aider (~/.aider.conf.yml)
|
|
178
|
+
* Aider uses YAML config with mcpServers list
|
|
179
|
+
*/
|
|
180
|
+
function installAiderMcp() {
|
|
181
|
+
const homeDir = os.homedir();
|
|
182
|
+
const configPath = path.join(homeDir, ".aider.conf.yml");
|
|
183
|
+
// Aider's YAML config is simple — we append the MCP flag if not present
|
|
184
|
+
let content = "";
|
|
185
|
+
if (fs.existsSync(configPath)) {
|
|
186
|
+
content = fs.readFileSync(configPath, "utf-8");
|
|
187
|
+
}
|
|
188
|
+
// Check if rafter MCP is already configured
|
|
189
|
+
if (content.includes("rafter mcp serve")) {
|
|
190
|
+
console.log(fmt.success("Rafter MCP already configured in Aider config"));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
// Append MCP server config
|
|
194
|
+
const mcpLine = "\n# Rafter security MCP server\nmcp-server-command: rafter mcp serve\n";
|
|
195
|
+
fs.writeFileSync(configPath, content + mcpLine, "utf-8");
|
|
196
|
+
console.log(fmt.success(`Installed Rafter MCP server to ${configPath}`));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
55
199
|
async function installClaudeCodeSkills() {
|
|
56
200
|
const homeDir = os.homedir();
|
|
57
201
|
const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
|
|
@@ -88,14 +232,52 @@ async function installClaudeCodeSkills() {
|
|
|
88
232
|
console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
|
|
89
233
|
}
|
|
90
234
|
}
|
|
235
|
+
function installCodexSkills() {
|
|
236
|
+
const homeDir = os.homedir();
|
|
237
|
+
const agentsSkillsDir = path.join(homeDir, ".agents", "skills");
|
|
238
|
+
// Install Backend Skill
|
|
239
|
+
const backendDir = path.join(agentsSkillsDir, "rafter");
|
|
240
|
+
const backendSkillPath = path.join(backendDir, "SKILL.md");
|
|
241
|
+
const backendTemplatePath = path.join(__dirname, "..", "..", "..", ".claude", "skills", "rafter", "SKILL.md");
|
|
242
|
+
if (!fs.existsSync(backendDir)) {
|
|
243
|
+
fs.mkdirSync(backendDir, { recursive: true });
|
|
244
|
+
}
|
|
245
|
+
if (fs.existsSync(backendTemplatePath)) {
|
|
246
|
+
fs.copyFileSync(backendTemplatePath, backendSkillPath);
|
|
247
|
+
console.log(fmt.success(`Installed Rafter Backend skill to ${backendSkillPath}`));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.log(fmt.warning(`Backend skill template not found at ${backendTemplatePath}`));
|
|
251
|
+
}
|
|
252
|
+
// Install Agent Security Skill
|
|
253
|
+
const agentDir = path.join(agentsSkillsDir, "rafter-agent-security");
|
|
254
|
+
const agentSkillPath = path.join(agentDir, "SKILL.md");
|
|
255
|
+
const agentTemplatePath = path.join(__dirname, "..", "..", "..", ".claude", "skills", "rafter-agent-security", "SKILL.md");
|
|
256
|
+
if (!fs.existsSync(agentDir)) {
|
|
257
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
258
|
+
}
|
|
259
|
+
if (fs.existsSync(agentTemplatePath)) {
|
|
260
|
+
fs.copyFileSync(agentTemplatePath, agentSkillPath);
|
|
261
|
+
console.log(fmt.success(`Installed Rafter Agent Security skill to ${agentSkillPath}`));
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
91
267
|
export function createInitCommand() {
|
|
92
268
|
return new Command("init")
|
|
93
269
|
.description("Initialize agent security system")
|
|
94
270
|
.option("--risk-level <level>", "Set risk level (minimal, moderate, aggressive)", "moderate")
|
|
95
|
-
.option("--
|
|
96
|
-
.option("--
|
|
97
|
-
.option("--
|
|
98
|
-
.option("--
|
|
271
|
+
.option("--with-openclaw", "Install OpenClaw integration")
|
|
272
|
+
.option("--with-claude-code", "Install Claude Code integration")
|
|
273
|
+
.option("--with-codex", "Install Codex CLI integration")
|
|
274
|
+
.option("--with-gemini", "Install Gemini CLI integration")
|
|
275
|
+
.option("--with-aider", "Install Aider integration")
|
|
276
|
+
.option("--with-cursor", "Install Cursor integration")
|
|
277
|
+
.option("--with-windsurf", "Install Windsurf integration")
|
|
278
|
+
.option("--with-continue", "Install Continue.dev integration")
|
|
279
|
+
.option("--with-gitleaks", "Download and install Gitleaks binary")
|
|
280
|
+
.option("--all", "Install all detected integrations and download Gitleaks")
|
|
99
281
|
.option("--update", "Re-download gitleaks and reinstall integrations without resetting config")
|
|
100
282
|
.action(async (opts) => {
|
|
101
283
|
console.log(fmt.header("Rafter Agent Security Setup"));
|
|
@@ -104,19 +286,64 @@ export function createInitCommand() {
|
|
|
104
286
|
const manager = new ConfigManager();
|
|
105
287
|
// Detect environments
|
|
106
288
|
const hasOpenClaw = fs.existsSync(path.join(os.homedir(), ".openclaw"));
|
|
107
|
-
const hasClaudeCode =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
289
|
+
const hasClaudeCode = fs.existsSync(path.join(os.homedir(), ".claude"));
|
|
290
|
+
const hasCodex = fs.existsSync(path.join(os.homedir(), ".codex"));
|
|
291
|
+
const hasGemini = fs.existsSync(path.join(os.homedir(), ".gemini"));
|
|
292
|
+
const hasCursor = fs.existsSync(path.join(os.homedir(), ".cursor"));
|
|
293
|
+
const hasWindsurf = fs.existsSync(path.join(os.homedir(), ".codeium", "windsurf"));
|
|
294
|
+
const hasContinueDev = fs.existsSync(path.join(os.homedir(), ".continue"));
|
|
295
|
+
const hasAider = fs.existsSync(path.join(os.homedir(), ".aider.conf.yml"));
|
|
296
|
+
// Resolve opt-in flags (--all enables all detected)
|
|
297
|
+
const wantOpenClaw = opts.withOpenclaw || opts.all;
|
|
298
|
+
const wantClaudeCode = opts.withClaudeCode || opts.all;
|
|
299
|
+
const wantCodex = opts.withCodex || opts.all;
|
|
300
|
+
const wantGemini = opts.withGemini || opts.all;
|
|
301
|
+
const wantCursor = opts.withCursor || opts.all;
|
|
302
|
+
const wantWindsurf = opts.withWindsurf || opts.all;
|
|
303
|
+
const wantContinue = opts.withContinue || opts.all;
|
|
304
|
+
const wantAider = opts.withAider || opts.all;
|
|
305
|
+
const wantGitleaks = opts.withGitleaks || opts.all;
|
|
306
|
+
// Show detected environments with opt-in hints
|
|
307
|
+
const detected = [];
|
|
308
|
+
if (hasOpenClaw)
|
|
309
|
+
detected.push("OpenClaw");
|
|
310
|
+
if (hasClaudeCode)
|
|
311
|
+
detected.push("Claude Code");
|
|
312
|
+
if (hasCodex)
|
|
313
|
+
detected.push("Codex CLI");
|
|
314
|
+
if (hasGemini)
|
|
315
|
+
detected.push("Gemini CLI");
|
|
316
|
+
if (hasCursor)
|
|
317
|
+
detected.push("Cursor");
|
|
318
|
+
if (hasWindsurf)
|
|
319
|
+
detected.push("Windsurf");
|
|
320
|
+
if (hasContinueDev)
|
|
321
|
+
detected.push("Continue.dev");
|
|
322
|
+
if (hasAider)
|
|
323
|
+
detected.push("Aider");
|
|
324
|
+
if (detected.length > 0) {
|
|
325
|
+
console.log(fmt.info(`Detected environments: ${detected.join(", ")}`));
|
|
116
326
|
}
|
|
117
327
|
else {
|
|
118
|
-
console.log(fmt.info("
|
|
328
|
+
console.log(fmt.info("No agent environments detected"));
|
|
119
329
|
}
|
|
330
|
+
// Warn about requested but undetected environments
|
|
331
|
+
if (wantOpenClaw && !hasOpenClaw)
|
|
332
|
+
console.log(fmt.warning("OpenClaw requested but not detected (~/.openclaw not found)"));
|
|
333
|
+
if (wantClaudeCode && !hasClaudeCode)
|
|
334
|
+
console.log(fmt.warning("Claude Code requested but not detected (~/.claude not found)"));
|
|
335
|
+
if (wantCodex && !hasCodex)
|
|
336
|
+
console.log(fmt.warning("Codex CLI requested but not detected (~/.codex not found)"));
|
|
337
|
+
if (wantGemini && !hasGemini)
|
|
338
|
+
console.log(fmt.warning("Gemini CLI requested but not detected (~/.gemini not found)"));
|
|
339
|
+
if (wantCursor && !hasCursor)
|
|
340
|
+
console.log(fmt.warning("Cursor requested but not detected (~/.cursor not found)"));
|
|
341
|
+
if (wantWindsurf && !hasWindsurf)
|
|
342
|
+
console.log(fmt.warning("Windsurf requested but not detected (~/.codeium/windsurf not found)"));
|
|
343
|
+
if (wantContinue && !hasContinueDev)
|
|
344
|
+
console.log(fmt.warning("Continue.dev requested but not detected (~/.continue not found)"));
|
|
345
|
+
if (wantAider && !hasAider)
|
|
346
|
+
console.log(fmt.warning("Aider requested but not detected (~/.aider.conf.yml not found)"));
|
|
120
347
|
// Initialize directory structure
|
|
121
348
|
try {
|
|
122
349
|
await manager.initialize();
|
|
@@ -135,8 +362,8 @@ export function createInitCommand() {
|
|
|
135
362
|
}
|
|
136
363
|
manager.set("agent.riskLevel", opts.riskLevel);
|
|
137
364
|
console.log(fmt.success(`Set risk level: ${opts.riskLevel}`));
|
|
138
|
-
// Check / download Gitleaks binary (
|
|
139
|
-
if (
|
|
365
|
+
// Check / download Gitleaks binary (opt-in via --with-gitleaks or --all)
|
|
366
|
+
if (wantGitleaks) {
|
|
140
367
|
const binaryManager = new BinaryManager();
|
|
141
368
|
const platformInfo = binaryManager.getPlatformInfo();
|
|
142
369
|
// Helper: show diagnostics for a failing binary (mirrors Python's agent init)
|
|
@@ -204,10 +431,12 @@ export function createInitCommand() {
|
|
|
204
431
|
}
|
|
205
432
|
}
|
|
206
433
|
}
|
|
207
|
-
// Install OpenClaw skill if
|
|
208
|
-
|
|
434
|
+
// Install OpenClaw skill if opted in
|
|
435
|
+
let openclawOk = false;
|
|
436
|
+
if (hasOpenClaw && wantOpenClaw) {
|
|
209
437
|
const skillManager = new SkillManager();
|
|
210
438
|
const result = await skillManager.installRafterSkillVerbose();
|
|
439
|
+
openclawOk = result.ok;
|
|
211
440
|
if (result.ok) {
|
|
212
441
|
console.log(fmt.success("Installed Rafter Security skill to ~/.openclaw/skills/rafter-security.md"));
|
|
213
442
|
manager.set("agent.environments.openclaw.enabled", true);
|
|
@@ -221,28 +450,139 @@ export function createInitCommand() {
|
|
|
221
450
|
}
|
|
222
451
|
}
|
|
223
452
|
}
|
|
224
|
-
// Install Claude Code skills + hooks if
|
|
225
|
-
|
|
453
|
+
// Install Claude Code skills + hooks if opted in
|
|
454
|
+
let claudeCodeOk = false;
|
|
455
|
+
if (hasClaudeCode && wantClaudeCode) {
|
|
226
456
|
try {
|
|
227
457
|
await installClaudeCodeSkills();
|
|
228
458
|
installClaudeCodeHooks();
|
|
229
459
|
manager.set("agent.environments.claudeCode.enabled", true);
|
|
460
|
+
claudeCodeOk = true;
|
|
230
461
|
}
|
|
231
462
|
catch (e) {
|
|
232
463
|
console.error(fmt.error(`Failed to install Claude Code integration: ${e}`));
|
|
233
464
|
}
|
|
234
465
|
}
|
|
466
|
+
// Install Codex CLI skills if opted in
|
|
467
|
+
let codexOk = false;
|
|
468
|
+
if (hasCodex && wantCodex) {
|
|
469
|
+
try {
|
|
470
|
+
installCodexSkills();
|
|
471
|
+
manager.set("agent.environments.codex.enabled", true);
|
|
472
|
+
codexOk = true;
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
console.error(fmt.error(`Failed to install Codex CLI integration: ${e}`));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Install Gemini CLI MCP if opted in
|
|
479
|
+
let geminiOk = false;
|
|
480
|
+
if (hasGemini && wantGemini) {
|
|
481
|
+
try {
|
|
482
|
+
geminiOk = installGeminiMcp();
|
|
483
|
+
if (geminiOk)
|
|
484
|
+
manager.set("agent.environments.gemini.enabled", true);
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
console.error(fmt.error(`Failed to install Gemini CLI integration: ${e}`));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// Install Cursor MCP if opted in
|
|
491
|
+
let cursorOk = false;
|
|
492
|
+
if (hasCursor && wantCursor) {
|
|
493
|
+
try {
|
|
494
|
+
cursorOk = installCursorMcp();
|
|
495
|
+
if (cursorOk)
|
|
496
|
+
manager.set("agent.environments.cursor.enabled", true);
|
|
497
|
+
}
|
|
498
|
+
catch (e) {
|
|
499
|
+
console.error(fmt.error(`Failed to install Cursor integration: ${e}`));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Install Windsurf MCP if opted in
|
|
503
|
+
let windsurfOk = false;
|
|
504
|
+
if (hasWindsurf && wantWindsurf) {
|
|
505
|
+
try {
|
|
506
|
+
windsurfOk = installWindsurfMcp();
|
|
507
|
+
if (windsurfOk)
|
|
508
|
+
manager.set("agent.environments.windsurf.enabled", true);
|
|
509
|
+
}
|
|
510
|
+
catch (e) {
|
|
511
|
+
console.error(fmt.error(`Failed to install Windsurf integration: ${e}`));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Install Continue.dev MCP if opted in
|
|
515
|
+
let continueOk = false;
|
|
516
|
+
if (hasContinueDev && wantContinue) {
|
|
517
|
+
try {
|
|
518
|
+
continueOk = installContinueDevMcp();
|
|
519
|
+
if (continueOk)
|
|
520
|
+
manager.set("agent.environments.continueDev.enabled", true);
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
console.error(fmt.error(`Failed to install Continue.dev integration: ${e}`));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// Install Aider MCP if opted in
|
|
527
|
+
let aiderOk = false;
|
|
528
|
+
if (hasAider && wantAider) {
|
|
529
|
+
try {
|
|
530
|
+
aiderOk = installAiderMcp();
|
|
531
|
+
if (aiderOk)
|
|
532
|
+
manager.set("agent.environments.aider.enabled", true);
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
console.error(fmt.error(`Failed to install Aider integration: ${e}`));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
235
538
|
console.log();
|
|
236
539
|
console.log(fmt.success("Agent security initialized!"));
|
|
237
540
|
console.log();
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
console.log("
|
|
541
|
+
const anyIntegration = openclawOk || claudeCodeOk || codexOk || geminiOk || cursorOk || windsurfOk || continueOk || aiderOk;
|
|
542
|
+
if (anyIntegration) {
|
|
543
|
+
console.log("Next steps:");
|
|
544
|
+
if (openclawOk)
|
|
545
|
+
console.log(" - Restart OpenClaw to load skill");
|
|
546
|
+
if (claudeCodeOk)
|
|
547
|
+
console.log(" - Restart Claude Code to load skills");
|
|
548
|
+
if (codexOk)
|
|
549
|
+
console.log(" - Restart Codex CLI to load skills");
|
|
550
|
+
if (geminiOk)
|
|
551
|
+
console.log(" - Restart Gemini CLI to load MCP server");
|
|
552
|
+
if (cursorOk)
|
|
553
|
+
console.log(" - Restart Cursor to load MCP server");
|
|
554
|
+
if (windsurfOk)
|
|
555
|
+
console.log(" - Restart Windsurf to load MCP server");
|
|
556
|
+
if (continueOk)
|
|
557
|
+
console.log(" - Restart Continue.dev to load MCP server");
|
|
558
|
+
if (aiderOk)
|
|
559
|
+
console.log(" - Restart Aider to load MCP server");
|
|
241
560
|
}
|
|
242
|
-
if (
|
|
243
|
-
console.log("
|
|
561
|
+
else if (detected.length > 0) {
|
|
562
|
+
console.log("No integrations were installed. To install, re-run with opt-in flags:");
|
|
563
|
+
console.log(" rafter agent init --all # Install all detected");
|
|
564
|
+
if (hasClaudeCode)
|
|
565
|
+
console.log(" rafter agent init --with-claude-code # Claude Code only");
|
|
566
|
+
if (hasOpenClaw)
|
|
567
|
+
console.log(" rafter agent init --with-openclaw # OpenClaw only");
|
|
568
|
+
if (hasCodex)
|
|
569
|
+
console.log(" rafter agent init --with-codex # Codex CLI only");
|
|
570
|
+
if (hasGemini)
|
|
571
|
+
console.log(" rafter agent init --with-gemini # Gemini CLI only");
|
|
572
|
+
if (hasCursor)
|
|
573
|
+
console.log(" rafter agent init --with-cursor # Cursor only");
|
|
574
|
+
if (hasWindsurf)
|
|
575
|
+
console.log(" rafter agent init --with-windsurf # Windsurf only");
|
|
576
|
+
if (hasContinueDev)
|
|
577
|
+
console.log(" rafter agent init --with-continue # Continue.dev only");
|
|
578
|
+
if (hasAider)
|
|
579
|
+
console.log(" rafter agent init --with-aider # Aider only");
|
|
244
580
|
}
|
|
245
|
-
|
|
581
|
+
else {
|
|
582
|
+
console.log("No agent environments detected. Install an agent tool and re-run with --with-<tool>.");
|
|
583
|
+
}
|
|
584
|
+
console.log();
|
|
585
|
+
console.log(" - Run: rafter scan local . (test secret scanning)");
|
|
246
586
|
console.log(" - Configure: rafter agent config show");
|
|
247
587
|
console.log();
|
|
248
588
|
});
|