@rafter-security/cli 0.7.0 → 0.7.2
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 +20 -1
- package/dist/commands/agent/audit-skill.js +2 -1
- package/dist/commands/agent/audit.js +27 -0
- package/dist/commands/agent/components.js +800 -0
- package/dist/commands/agent/disable.js +47 -0
- package/dist/commands/agent/enable.js +50 -0
- package/dist/commands/agent/index.js +6 -0
- package/dist/commands/agent/init.js +162 -164
- package/dist/commands/agent/list.js +72 -0
- package/dist/commands/brief.js +20 -0
- 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/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/index.js +6 -0
- package/package.json +1 -1
- package/resources/skills/rafter/SKILL.md +76 -96
- 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-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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolveComponent, recordComponentState, getComponentRegistry } from "./components.js";
|
|
3
|
+
import { fmt } from "../../utils/formatter.js";
|
|
4
|
+
/**
|
|
5
|
+
* `rafter agent disable <component-id>...` — uninstall one or more specific components.
|
|
6
|
+
* For hook/MCP entries, removes rafter's entries from the shared config file. For skills
|
|
7
|
+
* and our own instruction files, deletes them. Other (non-rafter) entries are preserved.
|
|
8
|
+
*
|
|
9
|
+
* Exit codes: 0 success · 1 invalid id or uninstall failure.
|
|
10
|
+
*/
|
|
11
|
+
export function createDisableCommand() {
|
|
12
|
+
return new Command("disable")
|
|
13
|
+
.description("Uninstall a specific agent component (e.g. claude-code.mcp, cursor.hooks)")
|
|
14
|
+
.argument("<components...>", "Component IDs to uninstall")
|
|
15
|
+
.action((components) => {
|
|
16
|
+
let exitCode = 0;
|
|
17
|
+
const seenIds = new Set();
|
|
18
|
+
for (const raw of components) {
|
|
19
|
+
if (seenIds.has(raw))
|
|
20
|
+
continue;
|
|
21
|
+
seenIds.add(raw);
|
|
22
|
+
const spec = resolveComponent(raw);
|
|
23
|
+
if (!spec) {
|
|
24
|
+
console.error(fmt.error(`Unknown component: ${raw}`));
|
|
25
|
+
console.error(fmt.info(`Run 'rafter agent list' to see available components. Known IDs: ${getComponentRegistry().map((c) => c.id).join(", ")}`));
|
|
26
|
+
exitCode = 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const wasInstalled = spec.isInstalled();
|
|
31
|
+
spec.uninstall();
|
|
32
|
+
recordComponentState(spec.id, false);
|
|
33
|
+
if (wasInstalled) {
|
|
34
|
+
console.log(fmt.success(`Disabled ${spec.id} (removed from ${spec.path})`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(fmt.info(`${spec.id} was not installed — no changes`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.error(fmt.error(`Failed to disable ${spec.id}: ${e}`));
|
|
42
|
+
exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
process.exit(exitCode);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { resolveComponent, recordComponentState, getComponentRegistry } from "./components.js";
|
|
4
|
+
import { fmt } from "../../utils/formatter.js";
|
|
5
|
+
/**
|
|
6
|
+
* `rafter agent enable <component-id>...` — install one or more specific components.
|
|
7
|
+
* This is the fine-grained complement to `rafter agent init --with-<platform>`; it
|
|
8
|
+
* targets a single (platform, kind) pair rather than a whole platform.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes: 0 success · 1 invalid id or install failure · 2 platform not detected
|
|
11
|
+
* (unless --force is passed).
|
|
12
|
+
*/
|
|
13
|
+
export function createEnableCommand() {
|
|
14
|
+
return new Command("enable")
|
|
15
|
+
.description("Install a specific agent component (e.g. claude-code.mcp, cursor.hooks)")
|
|
16
|
+
.argument("<components...>", "Component IDs to install")
|
|
17
|
+
.option("--force", "Install even if platform is not detected on this machine")
|
|
18
|
+
.action((components, opts) => {
|
|
19
|
+
let exitCode = 0;
|
|
20
|
+
const seenIds = new Set();
|
|
21
|
+
for (const raw of components) {
|
|
22
|
+
if (seenIds.has(raw))
|
|
23
|
+
continue;
|
|
24
|
+
seenIds.add(raw);
|
|
25
|
+
const spec = resolveComponent(raw);
|
|
26
|
+
if (!spec) {
|
|
27
|
+
console.error(fmt.error(`Unknown component: ${raw}`));
|
|
28
|
+
console.error(fmt.info(`Run 'rafter agent list' to see available components. Known IDs: ${getComponentRegistry().map((c) => c.id).join(", ")}`));
|
|
29
|
+
exitCode = 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const detected = fs.existsSync(spec.detectDir);
|
|
33
|
+
if (!detected && !opts.force) {
|
|
34
|
+
console.error(fmt.warning(`${spec.id}: platform not detected (${spec.detectDir}). Re-run with --force to install anyway.`));
|
|
35
|
+
exitCode = exitCode || 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
spec.install();
|
|
40
|
+
recordComponentState(spec.id, true);
|
|
41
|
+
console.log(fmt.success(`Enabled ${spec.id} → ${spec.path}`));
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
console.error(fmt.error(`Failed to enable ${spec.id}: ${e}`));
|
|
45
|
+
exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
process.exit(exitCode);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -11,6 +11,9 @@ import { createVerifyCommand } from "./verify.js";
|
|
|
11
11
|
import { createStatusCommand } from "./status.js";
|
|
12
12
|
import { createUpdateGitleaksCommand } from "./update-gitleaks.js";
|
|
13
13
|
import { createBaselineCommand } from "./baseline.js";
|
|
14
|
+
import { createListCommand } from "./list.js";
|
|
15
|
+
import { createEnableCommand } from "./enable.js";
|
|
16
|
+
import { createDisableCommand } from "./disable.js";
|
|
14
17
|
export function createAgentCommand() {
|
|
15
18
|
const agent = new Command("agent")
|
|
16
19
|
.description("Agent security features");
|
|
@@ -27,5 +30,8 @@ export function createAgentCommand() {
|
|
|
27
30
|
agent.addCommand(createStatusCommand());
|
|
28
31
|
agent.addCommand(createUpdateGitleaksCommand());
|
|
29
32
|
agent.addCommand(createBaselineCommand());
|
|
33
|
+
agent.addCommand(createListCommand());
|
|
34
|
+
agent.addCommand(createEnableCommand());
|
|
35
|
+
agent.addCommand(createDisableCommand());
|
|
30
36
|
return agent;
|
|
31
37
|
}
|
|
@@ -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 Remote skill to ${backendSkillPath}`));
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
console.log(fmt.warning(`Remote 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 Remote skill to ${backendSkillPath}`));
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
console.log(fmt.warning(`Remote 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,14 +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
|
|
701
|
-
// When --with-claude-code is explicitly passed, install even if
|
|
681
|
+
// When --with-claude-code is explicitly passed (or --local), install even if <root>/.claude doesn't exist yet
|
|
702
682
|
let claudeCodeOk = false;
|
|
703
|
-
if ((hasClaudeCode || opts.withClaudeCode) && wantClaudeCode) {
|
|
683
|
+
if ((hasClaudeCode || opts.withClaudeCode || (opts.local && wantClaudeCode)) && wantClaudeCode) {
|
|
704
684
|
try {
|
|
705
|
-
await installClaudeCodeSkills();
|
|
706
|
-
installClaudeCodeHooks();
|
|
707
|
-
|
|
685
|
+
await installClaudeCodeSkills(root);
|
|
686
|
+
installClaudeCodeHooks(root);
|
|
687
|
+
if (scope === "user")
|
|
688
|
+
manager.set("agent.environments.claudeCode.enabled", true);
|
|
708
689
|
claudeCodeOk = true;
|
|
709
690
|
}
|
|
710
691
|
catch (e) {
|
|
@@ -713,11 +694,12 @@ export function createInitCommand() {
|
|
|
713
694
|
}
|
|
714
695
|
// Install Codex CLI skills + hooks if opted in
|
|
715
696
|
let codexOk = false;
|
|
716
|
-
if (hasCodex && wantCodex) {
|
|
697
|
+
if ((hasCodex || (opts.local && wantCodex)) && wantCodex) {
|
|
717
698
|
try {
|
|
718
|
-
installCodexSkills();
|
|
719
|
-
installCodexHooks();
|
|
720
|
-
|
|
699
|
+
installCodexSkills(root);
|
|
700
|
+
installCodexHooks(root);
|
|
701
|
+
if (scope === "user")
|
|
702
|
+
manager.set("agent.environments.codex.enabled", true);
|
|
721
703
|
codexOk = true;
|
|
722
704
|
}
|
|
723
705
|
catch (e) {
|
|
@@ -726,11 +708,11 @@ export function createInitCommand() {
|
|
|
726
708
|
}
|
|
727
709
|
// Install Gemini CLI MCP + hooks if opted in
|
|
728
710
|
let geminiOk = false;
|
|
729
|
-
if (hasGemini && wantGemini) {
|
|
711
|
+
if ((hasGemini || (opts.local && wantGemini)) && wantGemini) {
|
|
730
712
|
try {
|
|
731
|
-
geminiOk = installGeminiMcp();
|
|
732
|
-
installGeminiHooks();
|
|
733
|
-
if (geminiOk)
|
|
713
|
+
geminiOk = installGeminiMcp(root);
|
|
714
|
+
installGeminiHooks(root);
|
|
715
|
+
if (geminiOk && scope === "user")
|
|
734
716
|
manager.set("agent.environments.gemini.enabled", true);
|
|
735
717
|
}
|
|
736
718
|
catch (e) {
|
|
@@ -739,11 +721,11 @@ export function createInitCommand() {
|
|
|
739
721
|
}
|
|
740
722
|
// Install Cursor MCP + hooks if opted in
|
|
741
723
|
let cursorOk = false;
|
|
742
|
-
if (hasCursor && wantCursor) {
|
|
724
|
+
if ((hasCursor || (opts.local && wantCursor)) && wantCursor) {
|
|
743
725
|
try {
|
|
744
|
-
cursorOk = installCursorMcp();
|
|
745
|
-
installCursorHooks();
|
|
746
|
-
if (cursorOk)
|
|
726
|
+
cursorOk = installCursorMcp(root);
|
|
727
|
+
installCursorHooks(root);
|
|
728
|
+
if (cursorOk && scope === "user")
|
|
747
729
|
manager.set("agent.environments.cursor.enabled", true);
|
|
748
730
|
}
|
|
749
731
|
catch (e) {
|
|
@@ -754,8 +736,8 @@ export function createInitCommand() {
|
|
|
754
736
|
let windsurfOk = false;
|
|
755
737
|
if (hasWindsurf && wantWindsurf) {
|
|
756
738
|
try {
|
|
757
|
-
windsurfOk = installWindsurfMcp();
|
|
758
|
-
installWindsurfHooks();
|
|
739
|
+
windsurfOk = installWindsurfMcp(root);
|
|
740
|
+
installWindsurfHooks(root);
|
|
759
741
|
if (windsurfOk)
|
|
760
742
|
manager.set("agent.environments.windsurf.enabled", true);
|
|
761
743
|
}
|
|
@@ -763,12 +745,15 @@ export function createInitCommand() {
|
|
|
763
745
|
console.error(fmt.error(`Failed to install Windsurf integration: ${e}`));
|
|
764
746
|
}
|
|
765
747
|
}
|
|
748
|
+
else if (opts.local && wantWindsurf) {
|
|
749
|
+
localUnsupported("Windsurf");
|
|
750
|
+
}
|
|
766
751
|
// Install Continue.dev MCP + hooks if opted in
|
|
767
752
|
let continueOk = false;
|
|
768
753
|
if (hasContinueDev && wantContinue) {
|
|
769
754
|
try {
|
|
770
|
-
continueOk = installContinueDevMcp();
|
|
771
|
-
installContinueDevHooks();
|
|
755
|
+
continueOk = installContinueDevMcp(root);
|
|
756
|
+
installContinueDevHooks(root);
|
|
772
757
|
if (continueOk)
|
|
773
758
|
manager.set("agent.environments.continueDev.enabled", true);
|
|
774
759
|
}
|
|
@@ -776,11 +761,14 @@ export function createInitCommand() {
|
|
|
776
761
|
console.error(fmt.error(`Failed to install Continue.dev integration: ${e}`));
|
|
777
762
|
}
|
|
778
763
|
}
|
|
764
|
+
else if (opts.local && wantContinue) {
|
|
765
|
+
localUnsupported("Continue.dev");
|
|
766
|
+
}
|
|
779
767
|
// Install Aider MCP if opted in
|
|
780
768
|
let aiderOk = false;
|
|
781
769
|
if (hasAider && wantAider) {
|
|
782
770
|
try {
|
|
783
|
-
aiderOk = installAiderMcp();
|
|
771
|
+
aiderOk = installAiderMcp(root);
|
|
784
772
|
if (aiderOk)
|
|
785
773
|
manager.set("agent.environments.aider.enabled", true);
|
|
786
774
|
}
|
|
@@ -788,11 +776,14 @@ export function createInitCommand() {
|
|
|
788
776
|
console.error(fmt.error(`Failed to install Aider integration: ${e}`));
|
|
789
777
|
}
|
|
790
778
|
}
|
|
779
|
+
else if (opts.local && wantAider) {
|
|
780
|
+
localUnsupported("Aider");
|
|
781
|
+
}
|
|
791
782
|
// Install global instruction files for platforms that support them
|
|
792
783
|
installGlobalInstructions({
|
|
793
784
|
claudeCode: claudeCodeOk,
|
|
794
785
|
cursor: cursorOk,
|
|
795
|
-
});
|
|
786
|
+
}, root);
|
|
796
787
|
console.log();
|
|
797
788
|
console.log(fmt.success("Agent security initialized!"));
|
|
798
789
|
console.log();
|
|
@@ -816,6 +807,13 @@ export function createInitCommand() {
|
|
|
816
807
|
if (aiderOk)
|
|
817
808
|
console.log(" - Restart Aider to load MCP server");
|
|
818
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
|
+
}
|
|
819
817
|
else if (detected.length > 0) {
|
|
820
818
|
console.log("No integrations were installed. To install, re-run with opt-in flags:");
|
|
821
819
|
console.log(" rafter agent init --all # Install all detected");
|