@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.
Files changed (56) hide show
  1. package/README.md +20 -1
  2. package/dist/commands/agent/audit-skill.js +2 -1
  3. package/dist/commands/agent/audit.js +27 -0
  4. package/dist/commands/agent/components.js +800 -0
  5. package/dist/commands/agent/disable.js +47 -0
  6. package/dist/commands/agent/enable.js +50 -0
  7. package/dist/commands/agent/index.js +6 -0
  8. package/dist/commands/agent/init.js +162 -164
  9. package/dist/commands/agent/list.js +72 -0
  10. package/dist/commands/brief.js +20 -0
  11. package/dist/commands/docs/index.js +18 -0
  12. package/dist/commands/docs/list.js +37 -0
  13. package/dist/commands/docs/show.js +64 -0
  14. package/dist/commands/mcp/server.js +84 -0
  15. package/dist/commands/skill/index.js +14 -0
  16. package/dist/commands/skill/install.js +89 -0
  17. package/dist/commands/skill/list.js +79 -0
  18. package/dist/commands/skill/registry.js +273 -0
  19. package/dist/commands/skill/remote.js +333 -0
  20. package/dist/commands/skill/review.js +975 -0
  21. package/dist/commands/skill/uninstall.js +65 -0
  22. package/dist/core/audit-logger.js +262 -21
  23. package/dist/core/config-manager.js +3 -0
  24. package/dist/core/docs-loader.js +148 -0
  25. package/dist/core/policy-loader.js +72 -1
  26. package/dist/index.js +6 -0
  27. package/package.json +1 -1
  28. package/resources/skills/rafter/SKILL.md +76 -96
  29. package/resources/skills/rafter/docs/backend.md +106 -0
  30. package/resources/skills/rafter/docs/cli-reference.md +199 -0
  31. package/resources/skills/rafter/docs/finding-triage.md +79 -0
  32. package/resources/skills/rafter/docs/guardrails.md +91 -0
  33. package/resources/skills/rafter/docs/shift-left.md +64 -0
  34. package/resources/skills/rafter-code-review/SKILL.md +91 -0
  35. package/resources/skills/rafter-code-review/docs/api.md +90 -0
  36. package/resources/skills/rafter-code-review/docs/asvs.md +120 -0
  37. package/resources/skills/rafter-code-review/docs/cwe-top25.md +78 -0
  38. package/resources/skills/rafter-code-review/docs/investigation-playbook.md +101 -0
  39. package/resources/skills/rafter-code-review/docs/llm.md +87 -0
  40. package/resources/skills/rafter-code-review/docs/web-app.md +84 -0
  41. package/resources/skills/rafter-secure-design/SKILL.md +103 -0
  42. package/resources/skills/rafter-secure-design/docs/api-design.md +97 -0
  43. package/resources/skills/rafter-secure-design/docs/auth.md +67 -0
  44. package/resources/skills/rafter-secure-design/docs/data-storage.md +90 -0
  45. package/resources/skills/rafter-secure-design/docs/dependencies.md +101 -0
  46. package/resources/skills/rafter-secure-design/docs/deployment.md +104 -0
  47. package/resources/skills/rafter-secure-design/docs/ingestion.md +98 -0
  48. package/resources/skills/rafter-secure-design/docs/standards-pointers.md +102 -0
  49. package/resources/skills/rafter-secure-design/docs/threat-modeling.md +128 -0
  50. package/resources/skills/rafter-skill-review/SKILL.md +106 -0
  51. package/resources/skills/rafter-skill-review/docs/authorship-provenance.md +82 -0
  52. package/resources/skills/rafter-skill-review/docs/changelog-review.md +99 -0
  53. package/resources/skills/rafter-skill-review/docs/data-practices.md +88 -0
  54. package/resources/skills/rafter-skill-review/docs/malware-indicators.md +79 -0
  55. package/resources/skills/rafter-skill-review/docs/prompt-injection.md +85 -0
  56. 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
- * Only Claude Code (~/.claude/CLAUDE.md) and Cursor (~/.cursor/rules/*.mdc)
20
- * have confirmed global instruction file paths. Other platforms (Codex, Gemini,
21
- * Windsurf, Continue.dev, Aider) only support project-level instruction files
22
- * (AGENTS.md, GEMINI.md, .windsurfrules, .continuerules, .aider/conventions.md)
23
- * which are handled by `rafter agent init-project` (not yet implemented).
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
- const homeDir = os.homedir();
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(homeDir, ".claude", "CLAUDE.md");
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 — ~/.cursor/rules/rafter-security.mdc (global rules directory, markdown format)
50
+ // Cursor — <root>/.cursor/rules/rafter-security.mdc
39
51
  if (platforms.cursor) {
40
52
  try {
41
- const filePath = path.join(homeDir, ".cursor", "rules", "rafter-security.mdc");
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 homeDir = os.homedir();
52
- const settingsPath = path.join(homeDir, ".claude", "settings.json");
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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 homeDir = os.homedir();
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
- async function installClaudeCodeSkills() {
407
- const homeDir = os.homedir();
408
- const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
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
- if (fs.existsSync(agentTemplatePath)) {
435
- fs.copyFileSync(agentTemplatePath, agentSkillPath);
436
- console.log(fmt.success(`Installed Rafter Agent Security skill to ${agentSkillPath}`));
437
- }
438
- else {
439
- console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
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 installCodexSkills() {
443
- const homeDir = os.homedir();
444
- const agentsSkillsDir = path.join(homeDir, ".agents", "skills");
445
- // Install Backend Skill
446
- const backendDir = path.join(agentsSkillsDir, "rafter");
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
- // Detect environments
510
- const hasOpenClaw = fs.existsSync(path.join(os.homedir(), ".openclaw"));
511
- const hasClaudeCode = fs.existsSync(path.join(os.homedir(), ".claude"));
512
- const hasCodex = fs.existsSync(path.join(os.homedir(), ".codex"));
513
- const hasGemini = fs.existsSync(path.join(os.homedir(), ".gemini"));
514
- const hasCursor = fs.existsSync(path.join(os.homedir(), ".cursor"));
515
- const hasWindsurf = fs.existsSync(path.join(os.homedir(), ".codeium", "windsurf"));
516
- const hasContinueDev = fs.existsSync(path.join(os.homedir(), ".continue"));
517
- const hasAider = fs.existsSync(path.join(os.homedir(), ".aider.conf.yml"));
518
- // Resolve opt-in flags (--all enables all detected, --interactive prompts)
519
- let wantOpenClaw = opts.withOpenclaw || opts.all;
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
- if (wantOpenClaw && !hasOpenClaw)
579
- console.log(fmt.warning("OpenClaw requested but not detected (~/.openclaw not found)"));
580
- if (wantClaudeCode && !hasClaudeCode)
581
- console.log(fmt.warning("Claude Code requested but not detected (~/.claude not found)"));
582
- if (wantCodex && !hasCodex)
583
- console.log(fmt.warning("Codex CLI requested but not detected (~/.codex not found)"));
584
- if (wantGemini && !hasGemini)
585
- console.log(fmt.warning("Gemini CLI requested but not detected (~/.gemini not found)"));
586
- if (wantCursor && !hasCursor)
587
- console.log(fmt.warning("Cursor requested but not detected (~/.cursor not found)"));
588
- if (wantWindsurf && !hasWindsurf)
589
- console.log(fmt.warning("Windsurf requested but not detected (~/.codeium/windsurf not found)"));
590
- if (wantContinue && !hasContinueDev)
591
- console.log(fmt.warning("Continue.dev requested but not detected (~/.continue not found)"));
592
- if (wantAider && !hasAider)
593
- console.log(fmt.warning("Aider requested but not detected (~/.aider.conf.yml not found)"));
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 .claude doesn't exist yet
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
- manager.set("agent.environments.claudeCode.enabled", true);
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
- manager.set("agent.environments.codex.enabled", true);
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");