@rafter-security/cli 0.6.6 → 0.7.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 +29 -10
- package/dist/commands/agent/audit-skill.js +22 -20
- package/dist/commands/agent/audit.js +27 -0
- package/dist/commands/agent/components.js +800 -0
- package/dist/commands/agent/config.js +2 -1
- package/dist/commands/agent/disable.js +47 -0
- package/dist/commands/agent/enable.js +50 -0
- package/dist/commands/agent/exec.js +2 -0
- package/dist/commands/agent/index.js +6 -0
- package/dist/commands/agent/init.js +162 -163
- package/dist/commands/agent/install-hook.js +15 -14
- package/dist/commands/agent/list.js +72 -0
- package/dist/commands/agent/scan.js +4 -3
- package/dist/commands/agent/verify.js +1 -1
- package/dist/commands/backend/run.js +12 -3
- package/dist/commands/backend/scan-status.js +3 -2
- package/dist/commands/brief.js +22 -2
- package/dist/commands/ci/init.js +25 -21
- package/dist/commands/completion.js +4 -3
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/docs/list.js +37 -0
- package/dist/commands/docs/show.js +64 -0
- package/dist/commands/mcp/server.js +84 -0
- package/dist/commands/report.js +42 -41
- package/dist/commands/scan/index.js +7 -5
- package/dist/commands/skill/index.js +14 -0
- package/dist/commands/skill/install.js +89 -0
- package/dist/commands/skill/list.js +79 -0
- package/dist/commands/skill/registry.js +273 -0
- package/dist/commands/skill/remote.js +333 -0
- package/dist/commands/skill/review.js +975 -0
- package/dist/commands/skill/uninstall.js +65 -0
- package/dist/core/audit-logger.js +262 -21
- package/dist/core/config-manager.js +3 -0
- package/dist/core/docs-loader.js +148 -0
- package/dist/core/policy-loader.js +72 -1
- package/dist/core/risk-rules.js +16 -3
- package/dist/index.js +19 -9
- package/dist/scanners/gitleaks.js +6 -2
- package/package.json +1 -1
- package/resources/skills/rafter/SKILL.md +77 -97
- package/resources/skills/rafter/docs/backend.md +106 -0
- package/resources/skills/rafter/docs/cli-reference.md +199 -0
- package/resources/skills/rafter/docs/finding-triage.md +79 -0
- package/resources/skills/rafter/docs/guardrails.md +91 -0
- package/resources/skills/rafter/docs/shift-left.md +64 -0
- package/resources/skills/rafter-agent-security/SKILL.md +1 -1
- package/resources/skills/rafter-code-review/SKILL.md +91 -0
- package/resources/skills/rafter-code-review/docs/api.md +90 -0
- package/resources/skills/rafter-code-review/docs/asvs.md +120 -0
- package/resources/skills/rafter-code-review/docs/cwe-top25.md +78 -0
- package/resources/skills/rafter-code-review/docs/investigation-playbook.md +101 -0
- package/resources/skills/rafter-code-review/docs/llm.md +87 -0
- package/resources/skills/rafter-code-review/docs/web-app.md +84 -0
- package/resources/skills/rafter-secure-design/SKILL.md +103 -0
- package/resources/skills/rafter-secure-design/docs/api-design.md +97 -0
- package/resources/skills/rafter-secure-design/docs/auth.md +67 -0
- package/resources/skills/rafter-secure-design/docs/data-storage.md +90 -0
- package/resources/skills/rafter-secure-design/docs/dependencies.md +101 -0
- package/resources/skills/rafter-secure-design/docs/deployment.md +104 -0
- package/resources/skills/rafter-secure-design/docs/ingestion.md +98 -0
- package/resources/skills/rafter-secure-design/docs/standards-pointers.md +102 -0
- package/resources/skills/rafter-secure-design/docs/threat-modeling.md +128 -0
- package/resources/skills/rafter-skill-review/SKILL.md +106 -0
- package/resources/skills/rafter-skill-review/docs/authorship-provenance.md +82 -0
- package/resources/skills/rafter-skill-review/docs/changelog-review.md +99 -0
- package/resources/skills/rafter-skill-review/docs/data-practices.md +88 -0
- package/resources/skills/rafter-skill-review/docs/malware-indicators.md +79 -0
- package/resources/skills/rafter-skill-review/docs/prompt-injection.md +85 -0
- package/resources/skills/rafter-skill-review/docs/telemetry.md +78 -0
|
@@ -13,21 +13,33 @@ import { fmt } from "../../utils/formatter.js";
|
|
|
13
13
|
import { injectInstructionFile } from "./instruction-block.js";
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = path.dirname(__filename);
|
|
16
|
+
/**
|
|
17
|
+
* Skills installed by `rafter agent init` for Claude Code / Codex.
|
|
18
|
+
*
|
|
19
|
+
* Sourced from `resources/skills/<name>/SKILL.md` in the shipped package.
|
|
20
|
+
* Keep this list in sync with Python's installer and the skills that actually
|
|
21
|
+
* ship in both resources/skills/ trees.
|
|
22
|
+
*/
|
|
23
|
+
const AGENT_SKILLS = [
|
|
24
|
+
{ name: "rafter", description: "Rafter Remote" },
|
|
25
|
+
{ name: "rafter-agent-security", description: "Rafter Agent Security" },
|
|
26
|
+
{ name: "rafter-secure-design", description: "Rafter Secure Design" },
|
|
27
|
+
{ name: "rafter-code-review", description: "Rafter Code Review" },
|
|
28
|
+
];
|
|
16
29
|
/**
|
|
17
30
|
* Install global instruction files for platforms that support them.
|
|
18
31
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
32
|
+
* User scope: Claude Code (~/.claude/CLAUDE.md), Cursor (~/.cursor/rules/*.mdc).
|
|
33
|
+
* Project scope: both platforms also honor <cwd>/.claude/CLAUDE.md and
|
|
34
|
+
* <cwd>/.cursor/rules/*.mdc. Other platforms (Codex, Gemini, Windsurf,
|
|
35
|
+
* Continue.dev, Aider) have project-only instruction file conventions
|
|
36
|
+
* (AGENTS.md, GEMINI.md, etc.) handled by `rafter agent init-project`.
|
|
24
37
|
*/
|
|
25
|
-
function installGlobalInstructions(platforms) {
|
|
26
|
-
|
|
27
|
-
// Claude Code — ~/.claude/CLAUDE.md (confirmed global instruction file)
|
|
38
|
+
function installGlobalInstructions(platforms, root) {
|
|
39
|
+
// Claude Code — <root>/.claude/CLAUDE.md
|
|
28
40
|
if (platforms.claudeCode) {
|
|
29
41
|
try {
|
|
30
|
-
const filePath = path.join(
|
|
42
|
+
const filePath = path.join(root, ".claude", "CLAUDE.md");
|
|
31
43
|
injectInstructionFile(filePath);
|
|
32
44
|
console.log(fmt.success(`Installed Rafter instructions to ${filePath}`));
|
|
33
45
|
}
|
|
@@ -35,10 +47,10 @@ function installGlobalInstructions(platforms) {
|
|
|
35
47
|
console.log(fmt.warning(`Failed to write Claude Code instructions: ${e}`));
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
|
-
// Cursor —
|
|
50
|
+
// Cursor — <root>/.cursor/rules/rafter-security.mdc
|
|
39
51
|
if (platforms.cursor) {
|
|
40
52
|
try {
|
|
41
|
-
const filePath = path.join(
|
|
53
|
+
const filePath = path.join(root, ".cursor", "rules", "rafter-security.mdc");
|
|
42
54
|
injectInstructionFile(filePath);
|
|
43
55
|
console.log(fmt.success(`Installed Rafter instructions to ${filePath}`));
|
|
44
56
|
}
|
|
@@ -47,10 +59,9 @@ function installGlobalInstructions(platforms) {
|
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
|
-
function installClaudeCodeHooks() {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const claudeDir = path.join(homeDir, ".claude");
|
|
62
|
+
function installClaudeCodeHooks(root) {
|
|
63
|
+
const settingsPath = path.join(root, ".claude", "settings.json");
|
|
64
|
+
const claudeDir = path.join(root, ".claude");
|
|
54
65
|
if (!fs.existsSync(claudeDir)) {
|
|
55
66
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
56
67
|
}
|
|
@@ -90,9 +101,8 @@ function installClaudeCodeHooks() {
|
|
|
90
101
|
console.log(fmt.success(`Installed PreToolUse hooks to ${settingsPath}`));
|
|
91
102
|
console.log(fmt.success(`Installed PostToolUse hooks to ${settingsPath}`));
|
|
92
103
|
}
|
|
93
|
-
function installCodexHooks() {
|
|
94
|
-
const
|
|
95
|
-
const codexDir = path.join(homeDir, ".codex");
|
|
104
|
+
function installCodexHooks(root) {
|
|
105
|
+
const codexDir = path.join(root, ".codex");
|
|
96
106
|
if (!fs.existsSync(codexDir)) {
|
|
97
107
|
fs.mkdirSync(codexDir, { recursive: true });
|
|
98
108
|
}
|
|
@@ -123,9 +133,8 @@ function installCodexHooks() {
|
|
|
123
133
|
fs.writeFileSync(hooksPath, JSON.stringify(config, null, 2), "utf-8");
|
|
124
134
|
console.log(fmt.success(`Installed hooks to ${hooksPath}`));
|
|
125
135
|
}
|
|
126
|
-
function installCursorHooks() {
|
|
127
|
-
const
|
|
128
|
-
const cursorDir = path.join(homeDir, ".cursor");
|
|
136
|
+
function installCursorHooks(root) {
|
|
137
|
+
const cursorDir = path.join(root, ".cursor");
|
|
129
138
|
if (!fs.existsSync(cursorDir)) {
|
|
130
139
|
fs.mkdirSync(cursorDir, { recursive: true });
|
|
131
140
|
}
|
|
@@ -155,9 +164,8 @@ function installCursorHooks() {
|
|
|
155
164
|
fs.writeFileSync(hooksPath, JSON.stringify(config, null, 2), "utf-8");
|
|
156
165
|
console.log(fmt.success(`Installed hooks to ${hooksPath}`));
|
|
157
166
|
}
|
|
158
|
-
function installGeminiHooks() {
|
|
159
|
-
const
|
|
160
|
-
const geminiDir = path.join(homeDir, ".gemini");
|
|
167
|
+
function installGeminiHooks(root) {
|
|
168
|
+
const geminiDir = path.join(root, ".gemini");
|
|
161
169
|
if (!fs.existsSync(geminiDir)) {
|
|
162
170
|
fs.mkdirSync(geminiDir, { recursive: true });
|
|
163
171
|
}
|
|
@@ -191,9 +199,8 @@ function installGeminiHooks() {
|
|
|
191
199
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
192
200
|
console.log(fmt.success(`Installed hooks to ${settingsPath}`));
|
|
193
201
|
}
|
|
194
|
-
function installWindsurfHooks() {
|
|
195
|
-
const
|
|
196
|
-
const windsurfDir = path.join(homeDir, ".windsurf");
|
|
202
|
+
function installWindsurfHooks(root) {
|
|
203
|
+
const windsurfDir = path.join(root, ".windsurf");
|
|
197
204
|
if (!fs.existsSync(windsurfDir)) {
|
|
198
205
|
fs.mkdirSync(windsurfDir, { recursive: true });
|
|
199
206
|
}
|
|
@@ -227,9 +234,8 @@ function installWindsurfHooks() {
|
|
|
227
234
|
fs.writeFileSync(hooksPath, JSON.stringify(config, null, 2), "utf-8");
|
|
228
235
|
console.log(fmt.success(`Installed hooks to ${hooksPath}`));
|
|
229
236
|
}
|
|
230
|
-
function installContinueDevHooks() {
|
|
231
|
-
const
|
|
232
|
-
const continueDir = path.join(homeDir, ".continue");
|
|
237
|
+
function installContinueDevHooks(root) {
|
|
238
|
+
const continueDir = path.join(root, ".continue");
|
|
233
239
|
if (!fs.existsSync(continueDir)) {
|
|
234
240
|
fs.mkdirSync(continueDir, { recursive: true });
|
|
235
241
|
}
|
|
@@ -267,9 +273,8 @@ const RAFTER_MCP_ENTRY = {
|
|
|
267
273
|
/**
|
|
268
274
|
* Install MCP server config for Gemini CLI (~/.gemini/settings.json)
|
|
269
275
|
*/
|
|
270
|
-
function installGeminiMcp() {
|
|
271
|
-
const
|
|
272
|
-
const geminiDir = path.join(homeDir, ".gemini");
|
|
276
|
+
function installGeminiMcp(root) {
|
|
277
|
+
const geminiDir = path.join(root, ".gemini");
|
|
273
278
|
const settingsPath = path.join(geminiDir, "settings.json");
|
|
274
279
|
if (!fs.existsSync(geminiDir)) {
|
|
275
280
|
fs.mkdirSync(geminiDir, { recursive: true });
|
|
@@ -293,9 +298,8 @@ function installGeminiMcp() {
|
|
|
293
298
|
/**
|
|
294
299
|
* Install MCP server config for Cursor (~/.cursor/mcp.json)
|
|
295
300
|
*/
|
|
296
|
-
function installCursorMcp() {
|
|
297
|
-
const
|
|
298
|
-
const cursorDir = path.join(homeDir, ".cursor");
|
|
301
|
+
function installCursorMcp(root) {
|
|
302
|
+
const cursorDir = path.join(root, ".cursor");
|
|
299
303
|
const mcpPath = path.join(cursorDir, "mcp.json");
|
|
300
304
|
if (!fs.existsSync(cursorDir)) {
|
|
301
305
|
fs.mkdirSync(cursorDir, { recursive: true });
|
|
@@ -319,9 +323,8 @@ function installCursorMcp() {
|
|
|
319
323
|
/**
|
|
320
324
|
* Install MCP server config for Windsurf (~/.codeium/windsurf/mcp_config.json)
|
|
321
325
|
*/
|
|
322
|
-
function installWindsurfMcp() {
|
|
323
|
-
const
|
|
324
|
-
const windsurfDir = path.join(homeDir, ".codeium", "windsurf");
|
|
326
|
+
function installWindsurfMcp(root) {
|
|
327
|
+
const windsurfDir = path.join(root, ".codeium", "windsurf");
|
|
325
328
|
const mcpPath = path.join(windsurfDir, "mcp_config.json");
|
|
326
329
|
if (!fs.existsSync(windsurfDir)) {
|
|
327
330
|
fs.mkdirSync(windsurfDir, { recursive: true });
|
|
@@ -345,9 +348,8 @@ function installWindsurfMcp() {
|
|
|
345
348
|
/**
|
|
346
349
|
* Install MCP server config for Continue.dev (~/.continue/config.json)
|
|
347
350
|
*/
|
|
348
|
-
function installContinueDevMcp() {
|
|
349
|
-
const
|
|
350
|
-
const continueDir = path.join(homeDir, ".continue");
|
|
351
|
+
function installContinueDevMcp(root) {
|
|
352
|
+
const continueDir = path.join(root, ".continue");
|
|
351
353
|
const configPath = path.join(continueDir, "config.json");
|
|
352
354
|
if (!fs.existsSync(continueDir)) {
|
|
353
355
|
fs.mkdirSync(continueDir, { recursive: true });
|
|
@@ -384,9 +386,8 @@ function installContinueDevMcp() {
|
|
|
384
386
|
* Install MCP config for Aider (~/.aider.conf.yml)
|
|
385
387
|
* Aider uses YAML config with mcpServers list
|
|
386
388
|
*/
|
|
387
|
-
function installAiderMcp() {
|
|
388
|
-
const
|
|
389
|
-
const configPath = path.join(homeDir, ".aider.conf.yml");
|
|
389
|
+
function installAiderMcp(root) {
|
|
390
|
+
const configPath = path.join(root, ".aider.conf.yml");
|
|
390
391
|
// Aider's YAML config is simple — we append the MCP flag if not present
|
|
391
392
|
let content = "";
|
|
392
393
|
if (fs.existsSync(configPath)) {
|
|
@@ -403,73 +404,31 @@ function installAiderMcp() {
|
|
|
403
404
|
console.log(fmt.success(`Installed Rafter MCP server to ${configPath}`));
|
|
404
405
|
return true;
|
|
405
406
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// Ensure .claude/skills directory exists
|
|
410
|
-
if (!fs.existsSync(claudeSkillsDir)) {
|
|
411
|
-
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
412
|
-
}
|
|
413
|
-
// Install Backend Skill
|
|
414
|
-
const backendSkillDir = path.join(claudeSkillsDir, "rafter");
|
|
415
|
-
const backendSkillPath = path.join(backendSkillDir, "SKILL.md");
|
|
416
|
-
const backendTemplatePath = path.join(__dirname, "..", "..", "..", "resources", "skills", "rafter", "SKILL.md");
|
|
417
|
-
if (!fs.existsSync(backendSkillDir)) {
|
|
418
|
-
fs.mkdirSync(backendSkillDir, { recursive: true });
|
|
419
|
-
}
|
|
420
|
-
if (fs.existsSync(backendTemplatePath)) {
|
|
421
|
-
fs.copyFileSync(backendTemplatePath, backendSkillPath);
|
|
422
|
-
console.log(fmt.success(`Installed Rafter Backend skill to ${backendSkillPath}`));
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
console.log(fmt.warning(`Backend skill template not found at ${backendTemplatePath}`));
|
|
426
|
-
}
|
|
427
|
-
// Install Agent Security Skill
|
|
428
|
-
const agentSkillDir = path.join(claudeSkillsDir, "rafter-agent-security");
|
|
429
|
-
const agentSkillPath = path.join(agentSkillDir, "SKILL.md");
|
|
430
|
-
const agentTemplatePath = path.join(__dirname, "..", "..", "..", "resources", "skills", "rafter-agent-security", "SKILL.md");
|
|
431
|
-
if (!fs.existsSync(agentSkillDir)) {
|
|
432
|
-
fs.mkdirSync(agentSkillDir, { recursive: true });
|
|
407
|
+
function installSkillsTo(skillsDir) {
|
|
408
|
+
if (!fs.existsSync(skillsDir)) {
|
|
409
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
433
410
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
411
|
+
for (const skill of AGENT_SKILLS) {
|
|
412
|
+
const destDir = path.join(skillsDir, skill.name);
|
|
413
|
+
const destPath = path.join(destDir, "SKILL.md");
|
|
414
|
+
const srcPath = path.join(__dirname, "..", "..", "..", "resources", "skills", skill.name, "SKILL.md");
|
|
415
|
+
if (!fs.existsSync(destDir)) {
|
|
416
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
417
|
+
}
|
|
418
|
+
if (fs.existsSync(srcPath)) {
|
|
419
|
+
fs.copyFileSync(srcPath, destPath);
|
|
420
|
+
console.log(fmt.success(`Installed ${skill.description} skill to ${destPath}`));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log(fmt.warning(`${skill.description} skill template not found at ${srcPath}`));
|
|
424
|
+
}
|
|
440
425
|
}
|
|
441
426
|
}
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const backendSkillPath = path.join(backendDir, "SKILL.md");
|
|
448
|
-
const backendTemplatePath = path.join(__dirname, "..", "..", "..", "resources", "skills", "rafter", "SKILL.md");
|
|
449
|
-
if (!fs.existsSync(backendDir)) {
|
|
450
|
-
fs.mkdirSync(backendDir, { recursive: true });
|
|
451
|
-
}
|
|
452
|
-
if (fs.existsSync(backendTemplatePath)) {
|
|
453
|
-
fs.copyFileSync(backendTemplatePath, backendSkillPath);
|
|
454
|
-
console.log(fmt.success(`Installed Rafter Backend skill to ${backendSkillPath}`));
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
console.log(fmt.warning(`Backend skill template not found at ${backendTemplatePath}`));
|
|
458
|
-
}
|
|
459
|
-
// Install Agent Security Skill
|
|
460
|
-
const agentDir = path.join(agentsSkillsDir, "rafter-agent-security");
|
|
461
|
-
const agentSkillPath = path.join(agentDir, "SKILL.md");
|
|
462
|
-
const agentTemplatePath = path.join(__dirname, "..", "..", "..", "resources", "skills", "rafter-agent-security", "SKILL.md");
|
|
463
|
-
if (!fs.existsSync(agentDir)) {
|
|
464
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
465
|
-
}
|
|
466
|
-
if (fs.existsSync(agentTemplatePath)) {
|
|
467
|
-
fs.copyFileSync(agentTemplatePath, agentSkillPath);
|
|
468
|
-
console.log(fmt.success(`Installed Rafter Agent Security skill to ${agentSkillPath}`));
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
|
|
472
|
-
}
|
|
427
|
+
async function installClaudeCodeSkills(root) {
|
|
428
|
+
installSkillsTo(path.join(root, ".claude", "skills"));
|
|
429
|
+
}
|
|
430
|
+
function installCodexSkills(root) {
|
|
431
|
+
installSkillsTo(path.join(root, ".agents", "skills"));
|
|
473
432
|
}
|
|
474
433
|
async function askYesNo(question, defaultYes = true) {
|
|
475
434
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -501,30 +460,43 @@ export function createInitCommand() {
|
|
|
501
460
|
.option("--all", "Install all detected integrations and download Gitleaks")
|
|
502
461
|
.option("-i, --interactive", "Guided setup — prompts for each detected integration")
|
|
503
462
|
.option("--update", "Re-download gitleaks and reinstall integrations without resetting config")
|
|
463
|
+
.option("--local", "Install integration configs project-locally (in CWD) instead of user-globally. " +
|
|
464
|
+
"Supported for Claude Code, Codex, Gemini, Cursor. Other platforms are skipped in local mode.")
|
|
504
465
|
.action(async (opts) => {
|
|
505
466
|
console.log(fmt.header("Rafter Agent Security Setup"));
|
|
506
467
|
console.log(fmt.divider());
|
|
507
468
|
console.log();
|
|
508
469
|
const manager = new ConfigManager();
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
//
|
|
519
|
-
|
|
470
|
+
const root = opts.local ? process.cwd() : os.homedir();
|
|
471
|
+
const scope = opts.local ? "project" : "user";
|
|
472
|
+
if (opts.local) {
|
|
473
|
+
console.log(fmt.info(`Project-local install — writing configs under ${root}`));
|
|
474
|
+
}
|
|
475
|
+
// Platforms supported in --local scope: Claude Code, Codex, Gemini, Cursor.
|
|
476
|
+
// Windsurf, Continue.dev, Aider are skipped in --local because their
|
|
477
|
+
// project-local config story is not established in their CLIs today.
|
|
478
|
+
// Detect environments. In local scope, don't probe user-global paths —
|
|
479
|
+
// the user must opt in explicitly via --with-<platform>.
|
|
480
|
+
const hasOpenClaw = scope === "user" && fs.existsSync(path.join(os.homedir(), ".openclaw"));
|
|
481
|
+
const hasClaudeCode = scope === "user" && fs.existsSync(path.join(os.homedir(), ".claude"));
|
|
482
|
+
const hasCodex = scope === "user" && fs.existsSync(path.join(os.homedir(), ".codex"));
|
|
483
|
+
const hasGemini = scope === "user" && fs.existsSync(path.join(os.homedir(), ".gemini"));
|
|
484
|
+
const hasCursor = scope === "user" && fs.existsSync(path.join(os.homedir(), ".cursor"));
|
|
485
|
+
const hasWindsurf = scope === "user" && fs.existsSync(path.join(os.homedir(), ".codeium", "windsurf"));
|
|
486
|
+
const hasContinueDev = scope === "user" && fs.existsSync(path.join(os.homedir(), ".continue"));
|
|
487
|
+
const hasAider = scope === "user" && fs.existsSync(path.join(os.homedir(), ".aider.conf.yml"));
|
|
488
|
+
// Resolve opt-in flags (--all enables all detected, --interactive prompts).
|
|
489
|
+
// In --local scope, --all is restricted to platforms that have a project-local
|
|
490
|
+
// config story (claudeCode, codex, gemini, cursor). The rest require user scope.
|
|
491
|
+
let wantOpenClaw = opts.withOpenclaw || (opts.all && !opts.local);
|
|
520
492
|
let wantClaudeCode = opts.withClaudeCode || opts.all;
|
|
521
493
|
let wantCodex = opts.withCodex || opts.all;
|
|
522
494
|
let wantGemini = opts.withGemini || opts.all;
|
|
523
495
|
let wantCursor = opts.withCursor || opts.all;
|
|
524
|
-
let wantWindsurf = opts.withWindsurf || opts.all;
|
|
525
|
-
let wantContinue = opts.withContinue || opts.all;
|
|
526
|
-
let wantAider = opts.withAider || opts.all;
|
|
527
|
-
let wantGitleaks = opts.withGitleaks || opts.all;
|
|
496
|
+
let wantWindsurf = opts.withWindsurf || (opts.all && !opts.local);
|
|
497
|
+
let wantContinue = opts.withContinue || (opts.all && !opts.local);
|
|
498
|
+
let wantAider = opts.withAider || (opts.all && !opts.local);
|
|
499
|
+
let wantGitleaks = opts.withGitleaks || (opts.all && !opts.local);
|
|
528
500
|
// Interactive mode: prompt for each detected integration
|
|
529
501
|
if (opts.interactive && !opts.all) {
|
|
530
502
|
console.log();
|
|
@@ -574,23 +546,26 @@ export function createInitCommand() {
|
|
|
574
546
|
else {
|
|
575
547
|
console.log(fmt.info("No agent environments detected"));
|
|
576
548
|
}
|
|
577
|
-
// Warn about requested but undetected environments
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
549
|
+
// Warn about requested but undetected environments (user scope only —
|
|
550
|
+
// in --local scope we create the directories in CWD as needed).
|
|
551
|
+
if (scope === "user") {
|
|
552
|
+
if (wantOpenClaw && !hasOpenClaw)
|
|
553
|
+
console.log(fmt.warning("OpenClaw requested but not detected (~/.openclaw not found)"));
|
|
554
|
+
if (wantClaudeCode && !hasClaudeCode)
|
|
555
|
+
console.log(fmt.warning("Claude Code requested but not detected (~/.claude not found)"));
|
|
556
|
+
if (wantCodex && !hasCodex)
|
|
557
|
+
console.log(fmt.warning("Codex CLI requested but not detected (~/.codex not found)"));
|
|
558
|
+
if (wantGemini && !hasGemini)
|
|
559
|
+
console.log(fmt.warning("Gemini CLI requested but not detected (~/.gemini not found)"));
|
|
560
|
+
if (wantCursor && !hasCursor)
|
|
561
|
+
console.log(fmt.warning("Cursor requested but not detected (~/.cursor not found)"));
|
|
562
|
+
if (wantWindsurf && !hasWindsurf)
|
|
563
|
+
console.log(fmt.warning("Windsurf requested but not detected (~/.codeium/windsurf not found)"));
|
|
564
|
+
if (wantContinue && !hasContinueDev)
|
|
565
|
+
console.log(fmt.warning("Continue.dev requested but not detected (~/.continue not found)"));
|
|
566
|
+
if (wantAider && !hasAider)
|
|
567
|
+
console.log(fmt.warning("Aider requested but not detected (~/.aider.conf.yml not found)"));
|
|
568
|
+
}
|
|
594
569
|
// Initialize directory structure
|
|
595
570
|
try {
|
|
596
571
|
await manager.initialize();
|
|
@@ -697,13 +672,20 @@ export function createInitCommand() {
|
|
|
697
672
|
}
|
|
698
673
|
}
|
|
699
674
|
}
|
|
675
|
+
// Helper: warn that a platform is not supported in --local mode.
|
|
676
|
+
const localUnsupported = (label) => {
|
|
677
|
+
console.log(fmt.warning(`${label} is not supported in --local mode yet. Skipping. ` +
|
|
678
|
+
`Re-run without --local to install for this platform user-globally.`));
|
|
679
|
+
};
|
|
700
680
|
// Install Claude Code skills + hooks if opted in
|
|
681
|
+
// When --with-claude-code is explicitly passed (or --local), install even if <root>/.claude doesn't exist yet
|
|
701
682
|
let claudeCodeOk = false;
|
|
702
|
-
if (hasClaudeCode && wantClaudeCode) {
|
|
683
|
+
if ((hasClaudeCode || opts.withClaudeCode || (opts.local && wantClaudeCode)) && wantClaudeCode) {
|
|
703
684
|
try {
|
|
704
|
-
await installClaudeCodeSkills();
|
|
705
|
-
installClaudeCodeHooks();
|
|
706
|
-
|
|
685
|
+
await installClaudeCodeSkills(root);
|
|
686
|
+
installClaudeCodeHooks(root);
|
|
687
|
+
if (scope === "user")
|
|
688
|
+
manager.set("agent.environments.claudeCode.enabled", true);
|
|
707
689
|
claudeCodeOk = true;
|
|
708
690
|
}
|
|
709
691
|
catch (e) {
|
|
@@ -712,11 +694,12 @@ export function createInitCommand() {
|
|
|
712
694
|
}
|
|
713
695
|
// Install Codex CLI skills + hooks if opted in
|
|
714
696
|
let codexOk = false;
|
|
715
|
-
if (hasCodex && wantCodex) {
|
|
697
|
+
if ((hasCodex || (opts.local && wantCodex)) && wantCodex) {
|
|
716
698
|
try {
|
|
717
|
-
installCodexSkills();
|
|
718
|
-
installCodexHooks();
|
|
719
|
-
|
|
699
|
+
installCodexSkills(root);
|
|
700
|
+
installCodexHooks(root);
|
|
701
|
+
if (scope === "user")
|
|
702
|
+
manager.set("agent.environments.codex.enabled", true);
|
|
720
703
|
codexOk = true;
|
|
721
704
|
}
|
|
722
705
|
catch (e) {
|
|
@@ -725,11 +708,11 @@ export function createInitCommand() {
|
|
|
725
708
|
}
|
|
726
709
|
// Install Gemini CLI MCP + hooks if opted in
|
|
727
710
|
let geminiOk = false;
|
|
728
|
-
if (hasGemini && wantGemini) {
|
|
711
|
+
if ((hasGemini || (opts.local && wantGemini)) && wantGemini) {
|
|
729
712
|
try {
|
|
730
|
-
geminiOk = installGeminiMcp();
|
|
731
|
-
installGeminiHooks();
|
|
732
|
-
if (geminiOk)
|
|
713
|
+
geminiOk = installGeminiMcp(root);
|
|
714
|
+
installGeminiHooks(root);
|
|
715
|
+
if (geminiOk && scope === "user")
|
|
733
716
|
manager.set("agent.environments.gemini.enabled", true);
|
|
734
717
|
}
|
|
735
718
|
catch (e) {
|
|
@@ -738,11 +721,11 @@ export function createInitCommand() {
|
|
|
738
721
|
}
|
|
739
722
|
// Install Cursor MCP + hooks if opted in
|
|
740
723
|
let cursorOk = false;
|
|
741
|
-
if (hasCursor && wantCursor) {
|
|
724
|
+
if ((hasCursor || (opts.local && wantCursor)) && wantCursor) {
|
|
742
725
|
try {
|
|
743
|
-
cursorOk = installCursorMcp();
|
|
744
|
-
installCursorHooks();
|
|
745
|
-
if (cursorOk)
|
|
726
|
+
cursorOk = installCursorMcp(root);
|
|
727
|
+
installCursorHooks(root);
|
|
728
|
+
if (cursorOk && scope === "user")
|
|
746
729
|
manager.set("agent.environments.cursor.enabled", true);
|
|
747
730
|
}
|
|
748
731
|
catch (e) {
|
|
@@ -753,8 +736,8 @@ export function createInitCommand() {
|
|
|
753
736
|
let windsurfOk = false;
|
|
754
737
|
if (hasWindsurf && wantWindsurf) {
|
|
755
738
|
try {
|
|
756
|
-
windsurfOk = installWindsurfMcp();
|
|
757
|
-
installWindsurfHooks();
|
|
739
|
+
windsurfOk = installWindsurfMcp(root);
|
|
740
|
+
installWindsurfHooks(root);
|
|
758
741
|
if (windsurfOk)
|
|
759
742
|
manager.set("agent.environments.windsurf.enabled", true);
|
|
760
743
|
}
|
|
@@ -762,12 +745,15 @@ export function createInitCommand() {
|
|
|
762
745
|
console.error(fmt.error(`Failed to install Windsurf integration: ${e}`));
|
|
763
746
|
}
|
|
764
747
|
}
|
|
748
|
+
else if (opts.local && wantWindsurf) {
|
|
749
|
+
localUnsupported("Windsurf");
|
|
750
|
+
}
|
|
765
751
|
// Install Continue.dev MCP + hooks if opted in
|
|
766
752
|
let continueOk = false;
|
|
767
753
|
if (hasContinueDev && wantContinue) {
|
|
768
754
|
try {
|
|
769
|
-
continueOk = installContinueDevMcp();
|
|
770
|
-
installContinueDevHooks();
|
|
755
|
+
continueOk = installContinueDevMcp(root);
|
|
756
|
+
installContinueDevHooks(root);
|
|
771
757
|
if (continueOk)
|
|
772
758
|
manager.set("agent.environments.continueDev.enabled", true);
|
|
773
759
|
}
|
|
@@ -775,11 +761,14 @@ export function createInitCommand() {
|
|
|
775
761
|
console.error(fmt.error(`Failed to install Continue.dev integration: ${e}`));
|
|
776
762
|
}
|
|
777
763
|
}
|
|
764
|
+
else if (opts.local && wantContinue) {
|
|
765
|
+
localUnsupported("Continue.dev");
|
|
766
|
+
}
|
|
778
767
|
// Install Aider MCP if opted in
|
|
779
768
|
let aiderOk = false;
|
|
780
769
|
if (hasAider && wantAider) {
|
|
781
770
|
try {
|
|
782
|
-
aiderOk = installAiderMcp();
|
|
771
|
+
aiderOk = installAiderMcp(root);
|
|
783
772
|
if (aiderOk)
|
|
784
773
|
manager.set("agent.environments.aider.enabled", true);
|
|
785
774
|
}
|
|
@@ -787,11 +776,14 @@ export function createInitCommand() {
|
|
|
787
776
|
console.error(fmt.error(`Failed to install Aider integration: ${e}`));
|
|
788
777
|
}
|
|
789
778
|
}
|
|
779
|
+
else if (opts.local && wantAider) {
|
|
780
|
+
localUnsupported("Aider");
|
|
781
|
+
}
|
|
790
782
|
// Install global instruction files for platforms that support them
|
|
791
783
|
installGlobalInstructions({
|
|
792
784
|
claudeCode: claudeCodeOk,
|
|
793
785
|
cursor: cursorOk,
|
|
794
|
-
});
|
|
786
|
+
}, root);
|
|
795
787
|
console.log();
|
|
796
788
|
console.log(fmt.success("Agent security initialized!"));
|
|
797
789
|
console.log();
|
|
@@ -815,6 +807,13 @@ export function createInitCommand() {
|
|
|
815
807
|
if (aiderOk)
|
|
816
808
|
console.log(" - Restart Aider to load MCP server");
|
|
817
809
|
}
|
|
810
|
+
else if (scope === "project") {
|
|
811
|
+
console.log("No integrations were installed. In --local mode, pass one or more opt-in flags:");
|
|
812
|
+
console.log(" rafter agent init --local --with-claude-code");
|
|
813
|
+
console.log(" rafter agent init --local --with-codex");
|
|
814
|
+
console.log(" rafter agent init --local --with-gemini");
|
|
815
|
+
console.log(" rafter agent init --local --with-cursor");
|
|
816
|
+
}
|
|
818
817
|
else if (detected.length > 0) {
|
|
819
818
|
console.log("No integrations were installed. To install, re-run with opt-in flags:");
|
|
820
819
|
console.log(" rafter agent init --all # Install all detected");
|
|
@@ -4,6 +4,7 @@ import os from "os";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { fmt } from "../../utils/formatter.js";
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
9
10
|
export function createInstallHookCommand() {
|
|
@@ -28,7 +29,7 @@ async function installHook(opts) {
|
|
|
28
29
|
function getTemplatePath(templateName) {
|
|
29
30
|
const templatePath = path.join(__dirname, "..", "..", "..", "resources", templateName);
|
|
30
31
|
if (!fs.existsSync(templatePath)) {
|
|
31
|
-
console.error("
|
|
32
|
+
console.error(fmt.error("Hook template not found"));
|
|
32
33
|
console.error(` Expected at: ${templatePath}`);
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
@@ -42,7 +43,7 @@ async function installLocalHook(hookName, templateName) {
|
|
|
42
43
|
execSync("git rev-parse --git-dir", { stdio: "pipe" });
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
|
-
console.error("
|
|
46
|
+
console.error(fmt.error("Not in a git repository"));
|
|
46
47
|
console.error(" Run this command from inside a git repository");
|
|
47
48
|
process.exit(1);
|
|
48
49
|
}
|
|
@@ -56,30 +57,30 @@ async function installLocalHook(hookName, templateName) {
|
|
|
56
57
|
const existing = fs.readFileSync(hookPath, "utf-8");
|
|
57
58
|
const marker = hookName === "pre-push" ? "Rafter Security Pre-Push Hook" : "Rafter Security Pre-Commit Hook";
|
|
58
59
|
if (existing.includes(marker)) {
|
|
59
|
-
console.log(
|
|
60
|
+
console.log(fmt.success(`Rafter ${hookName} hook already installed`));
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
const backupPath = `${hookPath}.backup-${Date.now()}`;
|
|
63
64
|
fs.copyFileSync(hookPath, backupPath);
|
|
64
|
-
console.log(
|
|
65
|
+
console.log(fmt.info(`Backed up existing hook to: ${path.basename(backupPath)}`));
|
|
65
66
|
}
|
|
66
67
|
const hookContent = fs.readFileSync(getTemplatePath(templateName), "utf-8");
|
|
67
68
|
fs.writeFileSync(hookPath, hookContent, "utf-8");
|
|
68
69
|
fs.chmodSync(hookPath, 0o755);
|
|
69
|
-
console.log(
|
|
70
|
+
console.log(fmt.success(`Installed Rafter ${hookName} hook`));
|
|
70
71
|
console.log(` Location: ${hookPath}`);
|
|
71
72
|
console.log();
|
|
72
73
|
if (hookName === "pre-push") {
|
|
73
74
|
console.log("The hook will:");
|
|
74
|
-
console.log("
|
|
75
|
-
console.log("
|
|
76
|
-
console.log("
|
|
75
|
+
console.log(" - Scan commits being pushed for secrets");
|
|
76
|
+
console.log(" - Block pushes if secrets are detected");
|
|
77
|
+
console.log(" - Can be bypassed with: git push --no-verify (not recommended)");
|
|
77
78
|
}
|
|
78
79
|
else {
|
|
79
80
|
console.log("The hook will:");
|
|
80
|
-
console.log("
|
|
81
|
-
console.log("
|
|
82
|
-
console.log("
|
|
81
|
+
console.log(" - Scan staged files for secrets before each commit");
|
|
82
|
+
console.log(" - Block commits if secrets are detected");
|
|
83
|
+
console.log(" - Can be bypassed with: git commit --no-verify (not recommended)");
|
|
83
84
|
}
|
|
84
85
|
console.log();
|
|
85
86
|
}
|
|
@@ -89,7 +90,7 @@ async function installLocalHook(hookName, templateName) {
|
|
|
89
90
|
async function installGlobalHook(hookName, templateName) {
|
|
90
91
|
const homeDir = os.homedir();
|
|
91
92
|
if (!homeDir) {
|
|
92
|
-
console.error("
|
|
93
|
+
console.error(fmt.error("Could not determine home directory"));
|
|
93
94
|
process.exit(1);
|
|
94
95
|
}
|
|
95
96
|
const globalHooksDir = path.join(homeDir, ".rafter", "git-hooks");
|
|
@@ -102,7 +103,7 @@ async function installGlobalHook(hookName, templateName) {
|
|
|
102
103
|
fs.chmodSync(hookPath, 0o755);
|
|
103
104
|
try {
|
|
104
105
|
execSync(`git config --global core.hooksPath "${globalHooksDir}"`, { stdio: "pipe" });
|
|
105
|
-
console.log(
|
|
106
|
+
console.log(fmt.success(`Installed Rafter ${hookName} hook globally`));
|
|
106
107
|
console.log(` Location: ${hookPath}`);
|
|
107
108
|
console.log(` Git config: core.hooksPath = ${globalHooksDir}`);
|
|
108
109
|
console.log();
|
|
@@ -116,7 +117,7 @@ async function installGlobalHook(hookName, templateName) {
|
|
|
116
117
|
console.log();
|
|
117
118
|
}
|
|
118
119
|
catch (e) {
|
|
119
|
-
console.error("
|
|
120
|
+
console.error(fmt.error("Failed to configure global git hooks"));
|
|
120
121
|
console.error(" You may need to manually set: git config --global core.hooksPath ~/.rafter/git-hooks");
|
|
121
122
|
process.exit(1);
|
|
122
123
|
}
|