cyberaudit-skill 3.1.4 → 3.2.0

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 (91) hide show
  1. package/README.md +84 -25
  2. package/dist/cli.d.ts +10 -0
  3. package/dist/cli.js +555 -170
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +100 -0
  6. package/dist/mcp-server.d.ts +135 -1
  7. package/dist/mcp-server.js +185 -96
  8. package/dist/mcp-server.test.d.ts +1 -0
  9. package/dist/mcp-server.test.js +77 -0
  10. package/dist/report/generator.d.ts +2 -0
  11. package/dist/report/generator.js +260 -0
  12. package/dist/report/generator.test.d.ts +1 -0
  13. package/dist/report/generator.test.js +73 -0
  14. package/dist/report/types.d.ts +31 -0
  15. package/dist/report/types.js +1 -0
  16. package/dist/scanners/secrets.d.ts +32 -0
  17. package/dist/scanners/secrets.js +437 -0
  18. package/dist/scanners/secrets.test.d.ts +1 -0
  19. package/dist/scanners/secrets.test.js +96 -0
  20. package/dist/scanners/types.d.ts +22 -0
  21. package/dist/scanners/types.js +1 -0
  22. package/dist/scanners/web.d.ts +37 -0
  23. package/dist/scanners/web.js +327 -0
  24. package/dist/scanners/web.test.d.ts +1 -0
  25. package/dist/scanners/web.test.js +67 -0
  26. package/package.json +15 -3
  27. package/skills/cyberaudit/AGENT-BOOT.md +1 -1
  28. package/skills/cyberaudit/COMMANDS.md +11 -1
  29. package/skills/cyberaudit/INSTALL.md +7 -6
  30. package/skills/cyberaudit/README.md +20 -5
  31. package/skills/cyberaudit/SKILL.md +20 -5
  32. package/skills/cyberaudit/cloud/CLOUD-CHECKLIST.md +85 -0
  33. package/skills/cyberaudit/cloud/CLOUD-PHILOSOPHY.md +91 -0
  34. package/skills/cyberaudit/cloud/CLOUD-REMEDIATION-LIBRARY.md +145 -0
  35. package/skills/cyberaudit/commands/audit-angular.md +11 -0
  36. package/skills/cyberaudit/commands/audit-api-bola.md +11 -0
  37. package/skills/cyberaudit/commands/audit-api-bopla.md +11 -0
  38. package/skills/cyberaudit/commands/audit-api-graphql.md +11 -0
  39. package/skills/cyberaudit/commands/audit-api-inventory.md +11 -0
  40. package/skills/cyberaudit/commands/audit-api-rate-limit.md +11 -0
  41. package/skills/cyberaudit/commands/audit-api-rest.md +11 -0
  42. package/skills/cyberaudit/commands/audit-api-third-party.md +11 -0
  43. package/skills/cyberaudit/commands/audit-api-ws.md +11 -0
  44. package/skills/cyberaudit/commands/audit-auth-api.md +11 -0
  45. package/skills/cyberaudit/commands/audit-auth-mobile.md +11 -0
  46. package/skills/cyberaudit/commands/audit-auth.md +12 -0
  47. package/skills/cyberaudit/commands/audit-binary.md +11 -0
  48. package/skills/cyberaudit/commands/audit-cis.md +11 -0
  49. package/skills/cyberaudit/commands/audit-cloud-iam.md +11 -0
  50. package/skills/cyberaudit/commands/audit-cloud-network.md +11 -0
  51. package/skills/cyberaudit/commands/audit-cloud-s3.md +11 -0
  52. package/skills/cyberaudit/commands/audit-cloud.md +10 -0
  53. package/skills/cyberaudit/commands/audit-cors.md +11 -0
  54. package/skills/cyberaudit/commands/audit-crypto-mobile.md +11 -0
  55. package/skills/cyberaudit/commands/audit-crypto.md +10 -0
  56. package/skills/cyberaudit/commands/audit-csrf.md +11 -0
  57. package/skills/cyberaudit/commands/audit-deeplinks.md +11 -0
  58. package/skills/cyberaudit/commands/audit-deps.md +11 -0
  59. package/skills/cyberaudit/commands/audit-deserial.md +11 -0
  60. package/skills/cyberaudit/commands/audit-exec.md +11 -0
  61. package/skills/cyberaudit/commands/audit-expo.md +11 -0
  62. package/skills/cyberaudit/commands/audit-express.md +11 -0
  63. package/skills/cyberaudit/commands/audit-flutter.md +11 -0
  64. package/skills/cyberaudit/commands/audit-headers.md +11 -0
  65. package/skills/cyberaudit/commands/audit-hipaa.md +11 -0
  66. package/skills/cyberaudit/commands/audit-idor.md +11 -0
  67. package/skills/cyberaudit/commands/audit-injection.md +12 -0
  68. package/skills/cyberaudit/commands/audit-ionic.md +11 -0
  69. package/skills/cyberaudit/commands/audit-laravel.md +11 -0
  70. package/skills/cyberaudit/commands/audit-logic.md +11 -0
  71. package/skills/cyberaudit/commands/audit-masvs.md +11 -0
  72. package/skills/cyberaudit/commands/audit-nestjs.md +11 -0
  73. package/skills/cyberaudit/commands/audit-network.md +11 -0
  74. package/skills/cyberaudit/commands/audit-nextjs.md +11 -0
  75. package/skills/cyberaudit/commands/audit-owasp.md +11 -0
  76. package/skills/cyberaudit/commands/audit-pci.md +11 -0
  77. package/skills/cyberaudit/commands/audit-permissions.md +11 -0
  78. package/skills/cyberaudit/commands/audit-react-native.md +11 -0
  79. package/skills/cyberaudit/commands/audit-react.md +11 -0
  80. package/skills/cyberaudit/commands/audit-rgpd.md +11 -0
  81. package/skills/cyberaudit/commands/audit-runtime.md +11 -0
  82. package/skills/cyberaudit/commands/audit-secrets.md +11 -0
  83. package/skills/cyberaudit/commands/audit-ssrf.md +11 -0
  84. package/skills/cyberaudit/commands/audit-storage.md +11 -0
  85. package/skills/cyberaudit/commands/audit-vue.md +11 -0
  86. package/skills/cyberaudit/commands/audit-xss.md +11 -0
  87. package/skills/cyberaudit/commands/audit-xxe.md +11 -0
  88. package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +1 -1
  89. package/skills/cyberaudit/reports/REPORT-TEMPLATE-CLOUD.md +103 -0
  90. package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +2 -2
  91. package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +2 -2
package/dist/cli.js CHANGED
@@ -1,268 +1,653 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { existsSync, mkdirSync, cpSync, readdirSync, writeFileSync, readFileSync, unlinkSync } from "fs";
4
- import { createInterface } from "readline";
3
+ import { existsSync, mkdirSync, cpSync, readdirSync, writeFileSync, readFileSync, rmSync, unlinkSync, copyFileSync } from "fs";
5
4
  import { homedir } from "os";
6
- import { join, dirname } from "path";
5
+ import { join, dirname, resolve, relative } from "path";
7
6
  import { fileURLToPath } from "url";
7
+ import { startMcpServer as startMcpServerImpl } from "./mcp-server.js";
8
+ import { scanSecrets, formatFindingsText } from "./scanners/secrets.js";
9
+ import { scanWeb, formatWebFindingsText } from "./scanners/web.js";
8
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
11
  const PKG_ROOT = join(__dirname, "..");
10
12
  const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
11
13
  const SKILL_SRC = join(PKG_ROOT, "skills", "cyberaudit");
12
- const AGENT_TARGETS = {
13
- "opencode": [join(homedir(), ".agents", "skills", "cyberaudit")],
14
- "claude-code": [join(homedir(), ".claude", "skills", "cyberaudit")],
15
- "kiro": [join(homedir(), ".kiro", "skills", "cyberaudit")],
16
- "cursor": [join(homedir(), ".cursor", "mcp.json")],
17
- "gemini": [join(homedir(), ".gemini", "skills", "cyberaudit")],
18
- "antigravity": [join(homedir(), ".gemini", "antigravity", "skills", "cyberaudit")],
19
- "antigravity-cli": [join(homedir(), ".gemini", "antigravity-cli", "skills", "cyberaudit")],
14
+ const COMMANDS_SRC = join(SKILL_SRC, "commands");
15
+ const VERSION = PKG.version || "3.1.5";
16
+ const MAIN_COMMANDS = [
17
+ 'audit.md',
18
+ 'audit-web.md',
19
+ 'audit-mobile.md',
20
+ 'audit-api.md',
21
+ 'audit-cloud.md',
22
+ 'audit-quick.md',
23
+ 'audit-report.md',
24
+ 'audit-help.md',
25
+ ];
26
+ const H = homedir();
27
+ function j(...parts) {
28
+ return join(H, ...parts);
29
+ }
30
+ export const AGENT_CONFIG = {
31
+ "opencode": {
32
+ displayName: "OpenCode",
33
+ skillPaths: [j(".agents", "skills", "cyberaudit"), j(".config", "opencode", "skills", "cyberaudit")],
34
+ commandPaths: [j(".config", "opencode", "commands")],
35
+ },
36
+ "claude-code": {
37
+ displayName: "Claude Code",
38
+ skillPaths: [j(".claude", "skills", "cyberaudit")],
39
+ commandPaths: [j(".claude", "commands")],
40
+ },
41
+ "cursor": {
42
+ displayName: "Cursor",
43
+ skillPaths: [j(".cursor", "skills", "cyberaudit")],
44
+ commandPaths: [j(".cursor", "commands")],
45
+ mcpPath: j(".cursor", "mcp.json"),
46
+ },
47
+ "windsurf": {
48
+ displayName: "Windsurf",
49
+ skillPaths: [j(".windsurf", "skills", "cyberaudit"), j(".codeium", "windsurf", "skills", "cyberaudit")],
50
+ commandPaths: [j(".windsurf", "commands")],
51
+ workflowPaths: [j(".windsurf", "workflows")],
52
+ },
53
+ "antigravity": {
54
+ displayName: "Antigravity",
55
+ skillPaths: [j(".gemini", "antigravity", "skills", "cyberaudit"), j(".agent", "skills", "cyberaudit")],
56
+ commandPaths: [j(".gemini", "antigravity", "commands"), j(".agent", "commands")],
57
+ workflowPaths: [j(".agent", "workflows")],
58
+ },
59
+ "antigravity-cli": {
60
+ displayName: "Antigravity CLI",
61
+ skillPaths: [j(".gemini", "antigravity-cli", "skills", "cyberaudit")],
62
+ commandPaths: [j(".gemini", "antigravity-cli", "commands")],
63
+ },
64
+ "copilot": {
65
+ displayName: "GitHub Copilot",
66
+ skillPaths: [j(".copilot", "skills", "cyberaudit"), j(".github", "copilot", "skills", "cyberaudit")],
67
+ commandPaths: [j(".copilot", "commands"), j(".github", "copilot", "commands")],
68
+ },
69
+ "kiro": {
70
+ displayName: "Kiro",
71
+ skillPaths: [j(".kiro", "skills", "cyberaudit")],
72
+ commandPaths: [j(".kiro", "commands")],
73
+ },
74
+ "codex": {
75
+ displayName: "Codex CLI",
76
+ skillPaths: [j(".codex", "skills", "cyberaudit")],
77
+ commandPaths: [j(".codex", "commands")],
78
+ },
79
+ "qoder": {
80
+ displayName: "Qoder",
81
+ skillPaths: [j(".qoder", "skills", "cyberaudit")],
82
+ commandPaths: [j(".qoder", "commands")],
83
+ },
84
+ "roocode": {
85
+ displayName: "Roo Code",
86
+ skillPaths: [j(".roo", "skills", "cyberaudit"), j(".roocode", "skills", "cyberaudit")],
87
+ commandPaths: [j(".roo", "commands"), j(".roocode", "commands")],
88
+ },
89
+ "gemini": {
90
+ displayName: "Gemini CLI",
91
+ skillPaths: [j(".gemini", "skills", "cyberaudit")],
92
+ commandPaths: [j(".gemini", "commands")],
93
+ },
94
+ "trae": {
95
+ displayName: "Trae",
96
+ skillPaths: [j(".trae", "skills", "cyberaudit")],
97
+ commandPaths: [j(".trae", "commands")],
98
+ },
99
+ "continue": {
100
+ displayName: "Continue",
101
+ skillPaths: [j(".continue", "skills", "cyberaudit")],
102
+ commandPaths: [j(".continue", "commands")],
103
+ },
104
+ "codebuddy": {
105
+ displayName: "CodeBuddy",
106
+ skillPaths: [j(".codebuddy", "skills", "cyberaudit")],
107
+ commandPaths: [j(".codebuddy", "commands")],
108
+ },
109
+ "droid": {
110
+ displayName: "Droid (Factory)",
111
+ skillPaths: [j(".factory", "skills", "cyberaudit"), j(".factory", "droid", "skills", "cyberaudit")],
112
+ commandPaths: [j(".factory", "commands"), j(".factory", "droid", "commands")],
113
+ },
114
+ "kilocode": {
115
+ displayName: "KiloCode",
116
+ skillPaths: [j(".kilocode", "skills", "cyberaudit"), j(".kilo", "skills", "cyberaudit")],
117
+ commandPaths: [j(".kilocode", "commands"), j(".kilo", "commands")],
118
+ },
119
+ "warp": {
120
+ displayName: "Warp",
121
+ skillPaths: [j(".warp", "skills", "cyberaudit")],
122
+ commandPaths: [j(".warp", "commands")],
123
+ },
124
+ "augment": {
125
+ displayName: "Augment",
126
+ skillPaths: [j(".augment", "skills", "cyberaudit")],
127
+ commandPaths: [j(".augment", "commands")],
128
+ },
129
+ "codewhale": {
130
+ displayName: "CodeWhale",
131
+ skillPaths: [j(".codewhale", "skills", "cyberaudit")],
132
+ commandPaths: [j(".codewhale", "commands")],
133
+ },
134
+ "cline": {
135
+ displayName: "Cline",
136
+ skillPaths: [j(".cline", "skills", "cyberaudit")],
137
+ commandPaths: [j(".cline", "commands")],
138
+ },
139
+ "aider": {
140
+ displayName: "Aider",
141
+ skillPaths: [j(".aider", "skills", "cyberaudit")],
142
+ commandPaths: [j(".aider", "commands")],
143
+ },
20
144
  };
145
+ export const AGENT_TARGETS = Object.fromEntries(Object.entries(AGENT_CONFIG).map(([k, v]) => [k, v.skillPaths]));
146
+ function isSafeInsideHome(p) {
147
+ const home = resolve(H);
148
+ const resolved = resolve(p);
149
+ return resolved.startsWith(home);
150
+ }
151
+ function isSafeInsideCwd(p, cwd) {
152
+ return resolve(p).startsWith(resolve(cwd));
153
+ }
154
+ function isSafeSkillPath(p, mode = 'global', cwd = process.cwd()) {
155
+ if (mode === 'local') {
156
+ return isSafeInsideCwd(p, cwd) && p.includes("cyberaudit");
157
+ }
158
+ return isSafeInsideHome(p) && p.includes("cyberaudit");
159
+ }
160
+ function isSafeCommandPath(p, mode = 'global', cwd = process.cwd()) {
161
+ const inside = mode === 'local' ? isSafeInsideCwd(p, cwd) : isSafeInsideHome(p);
162
+ if (!inside)
163
+ return false;
164
+ return p.includes("commands") || p.includes("workflows") || p.includes("skills");
165
+ }
166
+ function toLocalPath(globalPath, cwd) {
167
+ // ~/.claude/skills/cyberaudit -> ./.claude/skills/cyberaudit
168
+ try {
169
+ const rel = relative(H, globalPath);
170
+ if (rel.startsWith('..'))
171
+ return join(cwd, globalPath.split('/').pop() || '');
172
+ return join(cwd, rel);
173
+ }
174
+ catch {
175
+ return join(cwd, '.' + globalPath.split('/').pop());
176
+ }
177
+ }
178
+ function getPathsForMode(agent, mode, cwd = process.cwd()) {
179
+ const cfg = AGENT_CONFIG[agent];
180
+ if (mode === 'global')
181
+ return cfg;
182
+ // local: transform each path to be relative to cwd
183
+ return {
184
+ displayName: cfg.displayName,
185
+ skillPaths: cfg.skillPaths.map(p => toLocalPath(p, cwd)),
186
+ commandPaths: cfg.commandPaths.map(p => toLocalPath(p, cwd)),
187
+ workflowPaths: cfg.workflowPaths?.map(p => toLocalPath(p, cwd)),
188
+ mcpPath: cfg.mcpPath ? toLocalPath(cfg.mcpPath, cwd) : undefined,
189
+ };
190
+ }
21
191
  function detectInstalledAgents() {
22
192
  const found = [];
23
- const skipDetection = new Set(["cursor", "gemini", "antigravity", "antigravity-cli"]);
24
- for (const [agent, paths] of Object.entries(AGENT_TARGETS)) {
25
- if (skipDetection.has(agent))
26
- continue;
27
- if (paths.some((p) => existsSync(dirname(p)))) {
193
+ for (const [agent, cfg] of Object.entries(AGENT_CONFIG)) {
194
+ const checkPaths = [
195
+ ...cfg.skillPaths.map(p => dirname(p)),
196
+ ...cfg.commandPaths.map(p => p),
197
+ ...(cfg.workflowPaths || []).map(p => p),
198
+ ...(cfg.mcpPath ? [dirname(cfg.mcpPath)] : []),
199
+ ];
200
+ if (checkPaths.some(parent => existsSync(parent))) {
28
201
  found.push(agent);
29
202
  }
30
203
  }
31
- if (existsSync(join(homedir(), ".cursor")))
204
+ if (existsSync(j(".agents")) && !found.includes("opencode"))
205
+ found.push("opencode");
206
+ if (existsSync(j(".claude")) && !found.includes("claude-code"))
207
+ found.push("claude-code");
208
+ if (existsSync(j(".cursor")) && !found.includes("cursor"))
32
209
  found.push("cursor");
33
- if (existsSync(join(homedir(), ".gemini")))
34
- found.push("gemini");
35
- if (existsSync(join(homedir(), ".gemini", "antigravity")))
36
- found.push("antigravity");
37
- if (existsSync(join(homedir(), ".gemini", "antigravity-cli")))
38
- found.push("antigravity-cli");
210
+ return [...new Set(found)];
211
+ }
212
+ function detectByHomeFolders() {
213
+ const mapping = {
214
+ ".agents": "opencode",
215
+ ".claude": "claude-code",
216
+ ".cursor": "cursor",
217
+ ".windsurf": "windsurf",
218
+ ".codeium": "windsurf",
219
+ ".agent": "antigravity",
220
+ ".copilot": "copilot",
221
+ ".kiro": "kiro",
222
+ ".codex": "codex",
223
+ ".qoder": "qoder",
224
+ ".roo": "roocode",
225
+ ".roocode": "roocode",
226
+ ".gemini": "gemini",
227
+ ".trae": "trae",
228
+ ".continue": "continue",
229
+ ".codebuddy": "codebuddy",
230
+ ".factory": "droid",
231
+ ".kilocode": "kilocode",
232
+ ".kilo": "kilocode",
233
+ ".warp": "warp",
234
+ ".augment": "augment",
235
+ ".codewhale": "codewhale",
236
+ ".cline": "cline",
237
+ ".aider": "aider",
238
+ };
239
+ const found = [];
240
+ for (const [folder, agent] of Object.entries(mapping)) {
241
+ if (existsSync(j(folder))) {
242
+ if (!found.includes(agent))
243
+ found.push(agent);
244
+ }
245
+ }
246
+ if (existsSync(j(".config", "opencode")) && !found.includes("opencode"))
247
+ found.push("opencode");
39
248
  return found;
40
249
  }
41
- function installSkill(targetDir, agent, dryRun) {
42
- if (!existsSync(SKILL_SRC)) {
43
- console.error(`āœ— Skill source not found at ${SKILL_SRC}.`);
44
- return false;
250
+ function installDir(src, dst, mode = 'global', cwd = process.cwd(), allowCommandPath = false) {
251
+ if (!existsSync(src))
252
+ throw new Error(`Source not found: ${src}`);
253
+ const safeCheck = allowCommandPath
254
+ ? (p) => isSafeCommandPath(p, mode, cwd)
255
+ : (p) => isSafeSkillPath(p, mode, cwd);
256
+ if (!safeCheck(dst)) {
257
+ console.error(`āœ— Unsafe destination blocked: ${dst} (mode=${mode})`);
258
+ throw new Error(`Unsafe path: ${dst}`);
259
+ }
260
+ if (existsSync(dst)) {
261
+ console.log(` ā™»ļø Cleaning previous install at ${dst}`);
262
+ rmSync(dst, { recursive: true, force: true });
263
+ }
264
+ mkdirSync(dirname(dst), { recursive: true });
265
+ cpSync(src, dst, { recursive: true });
266
+ }
267
+ function installCommands(commandFiles, targetDir, dryRun, agentName, mode = 'global', cwd = process.cwd()) {
268
+ if (!isSafeCommandPath(targetDir, mode, cwd)) {
269
+ console.error(` āœ— Unsafe command destination blocked: ${targetDir}`);
270
+ return;
45
271
  }
46
272
  if (dryRun) {
47
- console.log(` → Would install to ${targetDir} (--dry-run)`);
48
- return true;
273
+ console.log(` → Would install ${commandFiles.length} commands to ${targetDir} (--dry-run) [${mode}]`);
274
+ return;
49
275
  }
50
- // Install/update skill files
51
276
  mkdirSync(targetDir, { recursive: true });
52
- cpSync(SKILL_SRC, targetDir, { recursive: true });
53
- console.log(` āœ“ Installed at ${targetDir}`);
54
- // Install command + skill files for opencode
55
- if (agent === "opencode") {
56
- const opencodeConf = join(homedir(), ".config", "opencode");
57
- const cmdDir = join(opencodeConf, "commands");
58
- const cmdSrc = join(PKG_ROOT, "skills", "cyberaudit", "commands");
59
- if (existsSync(cmdSrc)) {
60
- mkdirSync(cmdDir, { recursive: true });
61
- // Remove stale colon-named files (pre-v3.1.3)
62
- for (const f of readdirSync(cmdDir)) {
63
- if (typeof f === "string" && f.startsWith("audit:") && f.endsWith(".md")) {
277
+ try {
278
+ if (existsSync(targetDir)) {
279
+ const stale = readdirSync(targetDir).filter(f => typeof f === "string" && f.startsWith("audit") && f.endsWith(".md"));
280
+ if (stale.length > 0) {
281
+ console.log(` ā™»ļø Removing ${stale.length} stale audit commands from ${agentName}: ${stale.join(", ")}`);
282
+ for (const f of stale) {
64
283
  try {
65
- unlinkSync(join(cmdDir, f));
284
+ unlinkSync(join(targetDir, f));
66
285
  }
67
286
  catch { }
68
287
  }
69
288
  }
70
- cpSync(cmdSrc, cmdDir, { recursive: true });
71
- console.log(` āœ“ Commands installed to opencode`);
72
289
  }
73
- const skillDir = join(opencodeConf, "skills", "cyberaudit");
74
- mkdirSync(skillDir, { recursive: true });
75
- cpSync(SKILL_SRC, skillDir, { recursive: true });
76
- console.log(` āœ“ Skill installed to opencode`);
77
290
  }
78
- return true;
291
+ catch { }
292
+ for (const fileName of commandFiles) {
293
+ const src = join(COMMANDS_SRC, fileName);
294
+ if (!existsSync(src))
295
+ continue;
296
+ const dst = join(targetDir, fileName);
297
+ try {
298
+ mkdirSync(dirname(dst), { recursive: true });
299
+ copyFileSync(src, dst);
300
+ }
301
+ catch (e) {
302
+ console.error(` āœ— Failed to copy command ${fileName} to ${targetDir}: ${e.message}`);
303
+ }
304
+ }
305
+ console.log(` āœ“ Commands (${commandFiles.length}) installed to ${agentName} (${targetDir}) [${mode}] — "/" will show ${commandFiles.length} main commands`);
306
+ }
307
+ function installSkillForAgent(agent, dryRun, mode = 'global', cwd = process.cwd()) {
308
+ const cfg = getPathsForMode(agent, mode, cwd);
309
+ if (!cfg)
310
+ return false;
311
+ if (!existsSync(SKILL_SRC)) {
312
+ console.error(`āœ— Skill source not found at ${SKILL_SRC}.`);
313
+ return false;
314
+ }
315
+ if (dryRun) {
316
+ for (const sp of cfg.skillPaths)
317
+ console.log(` → Would install skill to ${sp} (--dry-run) [${mode}]`);
318
+ for (const cp of cfg.commandPaths)
319
+ console.log(` → Would install ${MAIN_COMMANDS.length} main commands to ${cp} (--dry-run) [${mode}]`);
320
+ if (cfg.workflowPaths)
321
+ for (const wp of cfg.workflowPaths)
322
+ console.log(` → Would install ${MAIN_COMMANDS.length} workflows to ${wp} (--dry-run) [${mode}]`);
323
+ if (cfg.mcpPath)
324
+ console.log(` → Would configure MCP at ${cfg.mcpPath} (--dry-run) [${mode}]`);
325
+ return true;
326
+ }
327
+ let ok = false;
328
+ for (const skillPath of cfg.skillPaths) {
329
+ try {
330
+ installDir(SKILL_SRC, skillPath, mode, cwd, false);
331
+ console.log(` āœ“ Skill installed at ${skillPath} [${mode}]`);
332
+ ok = true;
333
+ }
334
+ catch (e) {
335
+ console.error(` āœ— Failed skill install at ${skillPath}: ${e.message}`);
336
+ }
337
+ }
338
+ const isOpenCode = agent === "opencode";
339
+ const commandsToInstall = isOpenCode ? readdirSync(COMMANDS_SRC).filter(f => f.endsWith('.md')) : MAIN_COMMANDS;
340
+ for (const cmdPath of cfg.commandPaths) {
341
+ try {
342
+ installCommands(commandsToInstall, cmdPath, dryRun, agent, mode, cwd);
343
+ ok = true;
344
+ }
345
+ catch (e) {
346
+ console.error(` āœ— Failed commands install at ${cmdPath}: ${e.message}`);
347
+ }
348
+ }
349
+ if (cfg.workflowPaths) {
350
+ for (const wfPath of cfg.workflowPaths) {
351
+ try {
352
+ installCommands(commandsToInstall, wfPath, dryRun, agent, mode, cwd);
353
+ ok = true;
354
+ }
355
+ catch { }
356
+ }
357
+ }
358
+ if (agent === "cursor") {
359
+ const cursorOk = installForCursor(dryRun, mode, cwd);
360
+ ok = ok || cursorOk;
361
+ }
362
+ return ok;
79
363
  }
80
- function installForCursor(dryRun) {
81
- const cursorDir = join(homedir(), ".cursor");
82
- if (!existsSync(cursorDir)) {
83
- console.log(" ~ Cursor not found (no ~/.cursor/)");
364
+ function installForCursor(dryRun, mode = 'global', cwd = process.cwd()) {
365
+ const baseDir = mode === 'local' ? cwd : H;
366
+ const cursorDir = mode === 'local' ? join(cwd, ".cursor") : join(H, ".cursor");
367
+ // For local, .cursor folder may not exist, but we should still create mcp.json
368
+ if (mode === 'global' && !existsSync(cursorDir)) {
369
+ console.log(" ~ Cursor not found (no ~/.cursor/) — skipping MCP, but commands may still install");
84
370
  return false;
85
371
  }
86
372
  const mcpPath = join(cursorDir, "mcp.json");
87
373
  let mcpConfig = { mcpServers: {} };
88
374
  if (existsSync(mcpPath)) {
89
- mcpConfig = JSON.parse(readFileSync(mcpPath, "utf-8"));
375
+ try {
376
+ const raw = readFileSync(mcpPath, "utf-8");
377
+ if (raw.trim().length > 0)
378
+ mcpConfig = JSON.parse(raw);
379
+ }
380
+ catch (e) {
381
+ console.error(` āœ— Cursor mcp.json is corrupted (${e.message}) — backing up`);
382
+ if (!dryRun) {
383
+ try {
384
+ const bak = `${mcpPath}.bak.${Date.now()}`;
385
+ copyFileSync(mcpPath, bak);
386
+ console.log(` ↳ Backup saved to ${bak}`);
387
+ }
388
+ catch { }
389
+ }
390
+ mcpConfig = { mcpServers: {} };
391
+ }
90
392
  }
91
393
  if (mcpConfig.mcpServers?.["cyberaudit-skill"]) {
92
- console.log(` āœ“ Cursor MCP already configured`);
394
+ console.log(` āœ“ Cursor MCP already configured (${mcpPath}) [${mode}]`);
93
395
  return true;
94
396
  }
95
397
  if (dryRun) {
96
- console.log(` → Would add Cursor MCP entry to ${mcpPath}`);
398
+ console.log(` → Would add Cursor MCP entry to ${mcpPath} [${mode}]`);
97
399
  return true;
98
400
  }
401
+ if (existsSync(mcpPath)) {
402
+ try {
403
+ copyFileSync(mcpPath, `${mcpPath}.bak`);
404
+ }
405
+ catch { }
406
+ }
99
407
  mcpConfig.mcpServers = mcpConfig.mcpServers || {};
100
408
  mcpConfig.mcpServers["cyberaudit-skill"] = {
101
409
  command: "npx",
102
410
  args: ["-y", "cyberaudit-skill", "serve"],
103
411
  };
104
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
105
- console.log(` āœ“ Added Cursor MCP entry to ${mcpPath}`);
106
- return true;
107
- }
108
- function installForAgent(agent, dryRun) {
109
- switch (agent) {
110
- case "cursor":
111
- return installForCursor(dryRun);
112
- default: {
113
- const paths = AGENT_TARGETS[agent];
114
- if (!paths?.length)
115
- return false;
116
- return installSkill(paths[0], agent, dryRun);
117
- }
412
+ try {
413
+ mkdirSync(dirname(mcpPath), { recursive: true });
414
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
415
+ console.log(` āœ“ Added Cursor MCP entry to ${mcpPath} [${mode}]`);
416
+ return true;
417
+ }
418
+ catch (e) {
419
+ console.error(` āœ— Failed to write Cursor MCP config: ${e.message}`);
420
+ return false;
118
421
  }
119
422
  }
120
423
  async function main() {
121
424
  const program = new Command();
122
425
  program
123
426
  .name("cyberaudit-skill")
124
- .description("CyberAudit Skill — universal security audit skill for AI agents")
125
- .version(PKG.version);
427
+ .description("CyberAudit Skill — 22 agents like ui-ux-pro-max-skill, 8 main commands on '/'")
428
+ .version(VERSION);
126
429
  program
127
430
  .command("install")
128
- .description("Install CyberAudit Skill for AI agent(s)")
129
- .option("-a, --agent <agent>", "Target: opencode, claude-code, kiro, cursor, gemini, or all", "all")
431
+ .description("Install CyberAudit Skill for AI agent(s) — supports 22 agents like ui-ux-pro-max-skill, with --global and --local modes")
432
+ .option("-a, --agent <agent>", "Target agent or all", "all")
130
433
  .option("--dry-run", "Show changes without applying", false)
434
+ .option("--global", "Install globally to ~/ (default)", false)
435
+ .option("--local", "Install locally to ./ (project) like ui-ux-pro — ensures '/' shows commands", false)
131
436
  .action((options) => {
132
437
  const dryRun = !!options.dryRun;
133
438
  const agentOpt = (options.agent || "all").toLowerCase();
439
+ // Determine mode: if --local flag, local; else if --global or no flag, global (backward compat)
440
+ let mode = 'global';
441
+ if (options.local)
442
+ mode = 'local';
443
+ else if (options.global)
444
+ mode = 'global';
445
+ // If neither, default global for backward compat, but hint about local
446
+ const cwd = process.cwd();
134
447
  let agents;
135
448
  if (agentOpt === "all") {
136
- agents = detectInstalledAgents();
137
- if (agents.length === 0) {
138
- console.log("No supported AI agents detected.\nTry: --agent opencode");
139
- process.exit(1);
449
+ if (mode === 'local') {
450
+ // For local, install to all agents in project (like ui-ux-pro init --ai all)
451
+ agents = Object.keys(AGENT_CONFIG);
452
+ console.log(`Installing locally to project ${cwd} for all ${agents.length} agents (like ui-ux-pro)`);
453
+ }
454
+ else {
455
+ const byFolder = detectByHomeFolders();
456
+ const byLegacy = detectInstalledAgents();
457
+ agents = [...new Set([...byFolder, ...byLegacy])];
458
+ if (agents.length === 0) {
459
+ console.log("No supported AI agents detected globally. Checked:");
460
+ console.log(" ~/.claude, ~/.cursor, ~/.windsurf, ~/.agent, ~/.copilot, ~/.kiro, ~/.codex, ~/.qoder, ~/.roo, ~/.gemini, ~/.trae, ~/.agents, ~/.continue, ~/.codebuddy, ~/.factory, ~/.kilocode, ~/.warp, ~/.augment, ~/.codewhale, ~/.cline, ~/.aider");
461
+ console.log("\nTry: --agent claude-code --local (project-local, ensures '/' shows)");
462
+ console.log("Or: --agent all --local (like ui-ux-pro init --ai all)");
463
+ process.exit(1);
464
+ }
465
+ console.log(`Detected agents (${agents.length}): ${agents.join(", ")} [global mode]`);
140
466
  }
141
467
  }
142
468
  else {
143
- const valid = ["opencode", "claude-code", "kiro", "cursor", "gemini", "antigravity", "antigravity-cli"];
469
+ const valid = Object.keys(AGENT_CONFIG);
144
470
  if (!valid.includes(agentOpt)) {
145
- console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}`);
471
+ console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}, all`);
146
472
  process.exit(1);
147
473
  }
148
474
  agents = [agentOpt];
149
475
  }
150
476
  const label = dryRun ? " (dry run)" : "";
151
- console.log(`\n═══ CyberAudit Installation${label} ═══\n`);
477
+ console.log(`\n═══ CyberAudit v${VERSION} Installation${label} — ${agents.length} agents [${mode}] — like ui-ux-pro ═══\n`);
478
+ console.log(`Main commands for "/" menu: ${MAIN_COMMANDS.map(f => f.replace('.md', '')).join(', ')}\n`);
479
+ if (mode === 'local')
480
+ console.log(`Project: ${cwd}\n`);
152
481
  let ok = 0, fail = 0;
153
482
  for (const agent of agents) {
154
- const name = agent.charAt(0).toUpperCase() + agent.slice(1);
155
- if (installForAgent(agent, dryRun))
483
+ const cfg = getPathsForMode(agent, mode, cwd);
484
+ console.log(`\n→ ${cfg.displayName} (${agent}) [${mode}]:`);
485
+ if (installSkillForAgent(agent, dryRun, mode, cwd))
156
486
  ok++;
157
487
  else
158
488
  fail++;
159
489
  }
160
- console.log(`\nDone. ${ok} configured, ${fail} skipped.\n`);
161
- if (!dryRun)
162
- console.log("Verify: cyberaudit-skill list\n");
490
+ console.log(`\nDone. ${ok} configured, ${fail} skipped/failed [${mode}].\n`);
491
+ if (!dryRun && ok > 0) {
492
+ console.log(`Verify: npx -y cyberaudit-skill list${mode === 'local' ? ' --local' : ''}`);
493
+ console.log(`Tip: Type "/" in your agent — you should see: ${MAIN_COMMANDS.slice(0, 4).map(f => '/' + f.replace('.md', '').replace('audit-', 'audit:')).join(', ')}... (${MAIN_COMMANDS.length} commands) [${mode}]\n`);
494
+ if (mode === 'global') {
495
+ console.log(`For 100% guarantee like ui-ux-pro, also run: npx -y cyberaudit-skill install --agent all --local\n`);
496
+ }
497
+ }
163
498
  });
164
499
  program
165
500
  .command("list")
166
- .description("List audits and installed agents")
167
- .action(() => {
168
- console.log("\n═══ CyberAudit — Available Audits ═══\n");
501
+ .description("List audits and installed agents (22 agents)")
502
+ .option("--local", "List project-local installation (./) instead of global (~/)", false)
503
+ .action((opts) => {
504
+ const mode = opts.local ? 'local' : 'global';
505
+ const cwd = process.cwd();
506
+ console.log(`\n═══ CyberAudit v${VERSION} — Available Audits [${mode}] ═══\n`);
169
507
  console.log(" cyberaudit-web OWASP web app security audit");
170
508
  console.log(" cyberaudit-mobile OWASP mobile app security audit");
171
509
  console.log(" cyberaudit-api API security audit");
172
510
  console.log(" cyberaudit-cloud Cloud config audit");
173
- console.log(" cyberaudit-full Full stack (web + API + cloud)");
174
- console.log(" cyberaudit-quick Quick vulnerability scan\n");
175
- console.log("═══ Installed Agents ═══\n");
176
- for (const [agent] of Object.entries(AGENT_TARGETS)) {
177
- if (agent === "cursor") {
178
- const mcpPath = join(homedir(), ".cursor", "mcp.json");
179
- const ok = existsSync(mcpPath) && readFileSync(mcpPath, "utf-8").includes("cyberaudit-skill");
180
- console.log(` ${ok ? "āœ“" : "āœ—"} ${agent}`);
511
+ console.log(" cyberaudit-full Full stack");
512
+ console.log(" cyberaudit-quick Quick scan (deterministic)\n");
513
+ console.log(`═══ Supported Agents (${Object.keys(AGENT_CONFIG).length}) [${mode}] ═══\n`);
514
+ for (const [agentKey, cfgGlobal] of Object.entries(AGENT_CONFIG)) {
515
+ const cfg = getPathsForMode(agentKey, mode, cwd);
516
+ let installed = false;
517
+ let loc = "";
518
+ for (const sp of cfg.skillPaths) {
519
+ if (existsSync(sp)) {
520
+ installed = true;
521
+ loc = sp;
522
+ break;
523
+ }
181
524
  }
182
- else {
183
- const paths = AGENT_TARGETS[agent];
184
- const ok = paths.some((p) => existsSync(p));
185
- console.log(` ${ok ? "āœ“" : "āœ—"} ${agent}`);
525
+ if (!installed) {
526
+ for (const cp of cfg.commandPaths) {
527
+ if (existsSync(cp)) {
528
+ try {
529
+ const files = readdirSync(cp).filter(f => f.startsWith('audit'));
530
+ if (files.length > 0) {
531
+ installed = true;
532
+ loc = `${cp} (${files.length} cmds)`;
533
+ break;
534
+ }
535
+ }
536
+ catch { }
537
+ }
538
+ }
186
539
  }
540
+ if (!installed && cfg.mcpPath && existsSync(cfg.mcpPath)) {
541
+ try {
542
+ if (readFileSync(cfg.mcpPath, 'utf-8').includes('cyberaudit-skill')) {
543
+ installed = true;
544
+ loc = cfg.mcpPath;
545
+ }
546
+ }
547
+ catch { }
548
+ }
549
+ console.log(` ${installed ? "āœ“" : "āœ—"} ${cfg.displayName.padEnd(20)} (${agentKey})${installed ? ` — ${loc}` : ""}`);
187
550
  }
188
- console.log();
551
+ console.log(`\nMain "/" commands: ${MAIN_COMMANDS.map(f => f.replace('.md', '')).join(', ')} [${mode}]`);
552
+ console.log(`Total commands available: ${readdirSync(COMMANDS_SRC).length} (8 main shown on "/")\n`);
189
553
  });
190
554
  program
191
- .command("serve")
192
- .description("Start CyberAudit MCP server (stdio)")
193
- .action(() => {
194
- console.error("[CyberAudit] MCP server starting...");
195
- startMcpServer();
196
- });
197
- program.parse(process.argv);
198
- }
199
- function startMcpServer() {
200
- const rl = createInterface({ input: process.stdin });
201
- const capabilities = {
202
- tools: [
203
- {
204
- name: "cyberaudit-web",
205
- description: "Web app security audit (OWASP Top 10)",
206
- inputSchema: {
207
- type: "object",
208
- properties: {
209
- target: { type: "string", description: "URL or project path" },
210
- depth: { type: "string", enum: ["quick", "standard", "deep"], default: "standard" },
211
- },
212
- },
213
- },
214
- {
215
- name: "cyberaudit-mobile",
216
- description: "Mobile app security audit (OWASP MASVS)",
217
- inputSchema: {
218
- type: "object",
219
- properties: {
220
- target: { type: "string", description: "App package or project path" },
221
- platform: { type: "string", enum: ["android", "ios", "both"], default: "both" },
222
- },
223
- },
224
- },
225
- {
226
- name: "cyberaudit-api",
227
- description: "API security audit",
228
- inputSchema: {
229
- type: "object",
230
- properties: {
231
- target: { type: "string", description: "API endpoint or spec file" },
232
- },
233
- },
234
- },
235
- {
236
- name: "cyberaudit-list",
237
- description: "List all available audit types",
238
- inputSchema: { type: "object", properties: {} },
239
- },
240
- ],
241
- };
242
- function write(id, result) {
243
- process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, ...result }) + "\n");
244
- }
245
- rl.on("line", (line) => {
555
+ .command("scan")
556
+ .description("Deterministic security scan (secrets, web vulns)")
557
+ .argument("[target]", "Path to scan", ".")
558
+ .option("--json", "Output JSON", false)
559
+ .option("--type <type>", "Scan type: secrets, web, all", "all")
560
+ .option("--ignore <patterns>", "Comma-separated ignore", "")
561
+ .action((target, options) => {
562
+ const scanTarget = resolve(target || ".");
563
+ const ignore = options.ignore ? options.ignore.split(',').map((s) => s.trim()).filter(Boolean) : [];
564
+ const type = (options.type || 'all').toLowerCase();
565
+ console.error(`[CyberAudit] Scanning ${scanTarget} [type=${type}]... (${VERSION})`);
246
566
  try {
247
- const msg = JSON.parse(line.trim());
248
- const id = msg.id;
249
- if (msg.method === "initialize") {
250
- write(id, { protocolVersion: "2024-11-05", capabilities, serverInfo: { name: "cyberaudit-skill", version: "3.0.0" } });
567
+ let allFindings = [];
568
+ let secretFindings = [];
569
+ let webFindings = [];
570
+ if (type === 'secrets' || type === 'all') {
571
+ secretFindings = scanSecrets(scanTarget, { ignore });
572
+ allFindings.push(...secretFindings.map(f => ({ ...f, scanner: 'secrets' })));
251
573
  }
252
- else if (msg.method === "tools/list") {
253
- write(id, capabilities);
574
+ if (type === 'web' || type === 'all') {
575
+ webFindings = scanWeb(scanTarget);
576
+ allFindings.push(...webFindings.map(f => ({ ...f, scanner: 'web' })));
254
577
  }
255
- else if (msg.method === "tools/call") {
256
- write(id, {
257
- content: [{ type: "text", text: `[CyberAudit] "${msg.params.name}" called with ${JSON.stringify(msg.params.arguments)}\n\nRun the audit by loading the CyberAudit Skill.` }],
258
- });
578
+ if (options.json) {
579
+ console.log(JSON.stringify({ target: scanTarget, version: VERSION, type, findings: allFindings }, null, 2));
259
580
  }
260
581
  else {
261
- write(id, { error: { code: -32601, message: `Unknown method: ${msg.method}` } });
582
+ if (type === 'secrets' || type === 'all')
583
+ console.log(formatFindingsText(secretFindings, scanTarget));
584
+ if (type === 'web' || type === 'all')
585
+ console.log(formatWebFindingsText(webFindings, scanTarget));
586
+ if (allFindings.length > 0)
587
+ console.log(`\nšŸ’” Next: /audit:secrets, /audit:web or /audit`);
588
+ else
589
+ console.log(`\nāœ… No findings for type=${type} in ${scanTarget}`);
262
590
  }
591
+ if (allFindings.some(f => f.severity === 'CRITICAL'))
592
+ process.exitCode = 1;
593
+ }
594
+ catch (e) {
595
+ console.error(`[CyberAudit] Scan failed: ${e.message}`);
596
+ process.exit(1);
263
597
  }
264
- catch { /* skip malformed */ }
265
598
  });
599
+ program
600
+ .command("report")
601
+ .description("Generate deterministic security report")
602
+ .argument("[target]", "Target path or app name", ".")
603
+ .option("--input <file>", "JSON file from scan --json")
604
+ .option("--type <type>", "Report type: web|api|mobile|cloud|full", "web")
605
+ .option("--framework <fw>", "Framework name")
606
+ .option("--output <file>", "Output markdown file")
607
+ .option("--json", " Also emit JSON summary", false)
608
+ .action(async (target, options) => {
609
+ const { generateReport } = await import("./report/generator.js");
610
+ let findings = [];
611
+ let scanTarget = target || ".";
612
+ const reportType = (options.type || 'web').toLowerCase();
613
+ if (options.input) {
614
+ const inputPath = resolve(options.input);
615
+ if (!existsSync(inputPath)) {
616
+ console.error(`Input file not found: ${inputPath}`);
617
+ process.exit(1);
618
+ }
619
+ const raw = readFileSync(inputPath, 'utf-8');
620
+ const parsed = JSON.parse(raw);
621
+ findings = parsed.findings || parsed;
622
+ scanTarget = parsed.target || scanTarget;
623
+ }
624
+ else {
625
+ console.error(`[CyberAudit] No input file, running scan on ${scanTarget}...`);
626
+ const secrets = scanSecrets(resolve(scanTarget));
627
+ const web = scanWeb(resolve(scanTarget));
628
+ findings = [...secrets.map(f => ({ ...f, scanner: 'secrets' })), ...web.map(f => ({ ...f, scanner: 'web' }))];
629
+ }
630
+ const report = generateReport({ target: scanTarget, version: VERSION, type: reportType, findings, framework: options.framework });
631
+ if (options.output) {
632
+ const outPath = resolve(options.output);
633
+ writeFileSync(outPath, report.markdown, 'utf-8');
634
+ console.error(`[CyberAudit] Report written to ${outPath} | Score ${report.score}/100 ${report.verdict}`);
635
+ }
636
+ else
637
+ console.log(report.markdown);
638
+ if (options.json)
639
+ console.error(JSON.stringify({ score: report.score, verdict: report.verdict, dashboard: report.dashboard, owasp: report.owaspCompliance }, null, 2));
640
+ if (report.verdict === 'CRITICAL' || report.verdict === 'HIGH')
641
+ process.exitCode = 1;
642
+ });
643
+ program
644
+ .command("serve")
645
+ .description("Start CyberAudit MCP server (stdio)")
646
+ .action(() => {
647
+ console.error(`[CyberAudit] MCP server starting v${VERSION}...`);
648
+ startMcpServerImpl();
649
+ });
650
+ program.parse(process.argv);
266
651
  }
267
652
  main().catch((err) => {
268
653
  console.error("[CyberAudit] Fatal error:", err);