@naraya/cli 0.1.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +184 -93
  3. package/bin/naraya-native.mjs +4 -0
  4. package/bin/naraya.mjs +1 -142
  5. package/bin/undici-timeout.mjs +1 -0
  6. package/dist/assets.pack.gz +0 -0
  7. package/dist/mcp/config-loader.js +32 -0
  8. package/dist/mcp/lifecycle.js +90 -0
  9. package/dist/mcp/tool-mapper.js +31 -0
  10. package/dist/mcp/transport.js +30 -0
  11. package/dist/pentest/catalog/catalog-loader.js +45 -0
  12. package/dist/pentest/catalog/index.js +1 -0
  13. package/dist/pentest/cli.js +117 -0
  14. package/dist/pentest/command-builder/command-builder.js +90 -0
  15. package/dist/pentest/command-builder/index.js +1 -0
  16. package/dist/pentest/index.js +10 -0
  17. package/dist/pentest/installer/index.js +1 -0
  18. package/dist/pentest/installer/tool-installer.js +90 -0
  19. package/dist/pentest/manager.js +125 -0
  20. package/dist/pentest/mode/index.js +1 -0
  21. package/dist/pentest/mode/mode-selector.js +127 -0
  22. package/dist/pentest/selector/index.js +1 -0
  23. package/dist/pentest/selector/tool-selector.js +66 -0
  24. package/dist/pentest/skill-bridge/index.js +1 -0
  25. package/dist/pentest/skill-bridge/skill-bridge.js +66 -0
  26. package/dist/pentest/skills/generator/index.js +1 -0
  27. package/dist/pentest/skills/generator/skill-generator.js +310 -0
  28. package/dist/pentest/skills/index.js +3 -0
  29. package/dist/pentest/skills/loader/index.js +1 -0
  30. package/dist/pentest/skills/loader/skill-loader.js +167 -0
  31. package/dist/pentest/skills/register/index.js +1 -0
  32. package/dist/pentest/skills/register/skill-register.js +162 -0
  33. package/dist/pentest/skills/types.js +1 -0
  34. package/dist/pentest/types.js +90 -0
  35. package/package.json +42 -14
  36. package/src/assets-pack.mjs +1 -0
  37. package/src/banner.mjs +5 -0
  38. package/src/clipboard.mjs +1 -0
  39. package/src/config.mjs +1 -40
  40. package/src/goodbye.mjs +7 -0
  41. package/src/login.mjs +7 -49
  42. package/src/mcp/config-loader.ts +50 -0
  43. package/src/mcp/lifecycle.ts +113 -0
  44. package/src/mcp/tool-mapper.ts +42 -0
  45. package/src/mcp/transport.ts +38 -0
  46. package/src/mcp-cli.mjs +5 -0
  47. package/src/pentest/catalog/catalog-loader.ts +55 -0
  48. package/src/pentest/catalog/index.ts +1 -0
  49. package/src/pentest/cli.ts +130 -0
  50. package/src/pentest/command-builder/command-builder.ts +109 -0
  51. package/src/pentest/command-builder/index.ts +1 -0
  52. package/src/pentest/index.ts +11 -0
  53. package/src/pentest/installer/index.ts +1 -0
  54. package/src/pentest/installer/tool-installer.ts +107 -0
  55. package/src/pentest/manager.ts +167 -0
  56. package/src/pentest/mode/index.ts +1 -0
  57. package/src/pentest/mode/mode-selector.ts +159 -0
  58. package/src/pentest/selector/index.ts +1 -0
  59. package/src/pentest/selector/tool-selector.ts +87 -0
  60. package/src/pentest/skill-bridge/index.ts +1 -0
  61. package/src/pentest/skill-bridge/skill-bridge.ts +86 -0
  62. package/src/pentest/skills/generator/index.ts +1 -0
  63. package/src/pentest/skills/generator/skill-generator.ts +373 -0
  64. package/src/pentest/skills/index.ts +4 -0
  65. package/src/pentest/skills/loader/index.ts +1 -0
  66. package/src/pentest/skills/loader/skill-loader.ts +206 -0
  67. package/src/pentest/skills/register/index.ts +1 -0
  68. package/src/pentest/skills/register/skill-register.ts +196 -0
  69. package/src/pentest/skills/types.ts +66 -0
  70. package/src/pentest/types.ts +341 -0
  71. package/src/seed.mjs +1 -36
  72. package/src/splash.mjs +4 -0
  73. package/src/status.mjs +2 -71
  74. package/assets/APPEND-SYSTEM.md +0 -9
  75. package/assets/extensions/naraya-brand.ts +0 -251
  76. package/assets/extensions/naraya-gate.ts +0 -23
  77. package/assets/naraya-logo.txt +0 -5
  78. package/assets/skills/narabuild/SKILL.md +0 -156
  79. package/assets/skills/naradroid/SKILL.md +0 -118
  80. package/assets/skills/naraexplore/SKILL.md +0 -71
  81. package/assets/skills/narafe/SKILL.md +0 -94
  82. package/assets/skills/naraplan/SKILL.md +0 -47
  83. package/assets/skills/narasearch/SKILL.md +0 -141
@@ -0,0 +1,159 @@
1
+ import type {
2
+ PentestMode,
3
+ ModeConfig,
4
+ ModeSelectorOptions,
5
+ ModeSelectionResult,
6
+ } from "../types.js"
7
+ import { MODE_PRESETS } from "../types.js"
8
+
9
+ const WEB_EXTENSIONS = [".php", ".asp", ".aspx", ".jsp", ".cgi", ".pl", ".py", ".rb"]
10
+ const NETWORK_PORTS = [22, 23, 3389, 445, 139, 135, 1433, 3306, 5432, 27017]
11
+ const CTF_INDICATORS = ["ctf", "chall", "pwn", "crypto", "forensics", "rev", "misc", "web"]
12
+ const RED_TEAM_INDICATORS = ["ad", "active-directory", "kerberos", "ldap", "smb", "rdp"]
13
+
14
+ export function detectMode(target: string): { mode: PentestMode; reason: string } {
15
+ const lower = target.toLowerCase()
16
+
17
+ for (const indicator of CTF_INDICATORS) {
18
+ if (lower.includes(indicator)) {
19
+ return { mode: "ctf", reason: `CTF indicator detected: ${indicator}` }
20
+ }
21
+ }
22
+
23
+ for (const indicator of RED_TEAM_INDICATORS) {
24
+ if (lower.includes(indicator)) {
25
+ return { mode: "red-team", reason: `Red team indicator detected: ${indicator}` }
26
+ }
27
+ }
28
+
29
+ for (const ext of WEB_EXTENSIONS) {
30
+ if (lower.endsWith(ext)) {
31
+ return { mode: "bug-bounty", reason: `Web extension detected: ${ext}` }
32
+ }
33
+ }
34
+
35
+ const portMatch = lower.match(/:(\d+)/)
36
+ if (portMatch) {
37
+ const port = parseInt(portMatch[1], 10)
38
+ if (NETWORK_PORTS.includes(port)) {
39
+ return { mode: "red-team", reason: `Network service port detected: ${port}` }
40
+ }
41
+ }
42
+
43
+ if (lower.includes("http://") || lower.includes("https://")) {
44
+ return { mode: "bug-bounty", reason: "Web target detected (HTTP/HTTPS)" }
45
+ }
46
+
47
+ const ipMatch = lower.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
48
+ if (ipMatch) {
49
+ return { mode: "red-team", reason: `IP address target: ${ipMatch[0]}` }
50
+ }
51
+
52
+ return { mode: "auto", reason: "No specific indicators detected, using adaptive mode" }
53
+ }
54
+
55
+ export function selectMode(options: ModeSelectorOptions = {}): ModeSelectionResult {
56
+ if (options.preferred_mode && options.preferred_mode !== "auto") {
57
+ const config = MODE_PRESETS[options.preferred_mode]
58
+ return {
59
+ selected_mode: options.preferred_mode,
60
+ config,
61
+ auto_detected: false,
62
+ detection_reason: `User selected mode: ${options.preferred_mode}`,
63
+ }
64
+ }
65
+
66
+ if (options.auto_detect !== false && options.target) {
67
+ const detection = detectMode(options.target)
68
+ if (detection.mode !== "auto") {
69
+ const config = MODE_PRESETS[detection.mode]
70
+ return {
71
+ selected_mode: detection.mode,
72
+ config,
73
+ auto_detected: true,
74
+ detection_reason: detection.reason,
75
+ }
76
+ }
77
+ }
78
+
79
+ const config = MODE_PRESETS["auto"]
80
+ return {
81
+ selected_mode: "auto",
82
+ config,
83
+ auto_detected: false,
84
+ detection_reason: "No specific mode detected, using adaptive auto mode",
85
+ }
86
+ }
87
+
88
+ export function getModeConfig(mode: PentestMode): ModeConfig {
89
+ return MODE_PRESETS[mode]
90
+ }
91
+
92
+ export function getToolsForMode(
93
+ mode: PentestMode,
94
+ availableTools: readonly string[],
95
+ ): string[] {
96
+ const config = MODE_PRESETS[mode]
97
+ const priority = config.tool_priority
98
+
99
+ const prioritized: string[] = []
100
+ const remaining: string[] = []
101
+
102
+ for (const tool of availableTools) {
103
+ const toolCategory = getToolCategory(tool)
104
+ if (toolCategory && (priority as readonly string[]).includes(toolCategory)) {
105
+ prioritized.push(tool)
106
+ } else {
107
+ remaining.push(tool)
108
+ }
109
+ }
110
+
111
+ prioritized.sort((a, b) => {
112
+ const catA = getToolCategory(a)
113
+ const catB = getToolCategory(b)
114
+ if (!catA || !catB) return 0
115
+ return (priority as readonly string[]).indexOf(catA) - (priority as readonly string[]).indexOf(catB)
116
+ })
117
+
118
+ return [...prioritized, ...remaining]
119
+ }
120
+
121
+ function getToolCategory(toolName: string): string | undefined {
122
+ const categoryMap: Record<string, string> = {
123
+ subfinder: "recon", amass: "recon", assetfinder: "recon", httpx: "recon",
124
+ naabu: "recon", massdns: "recon", nmap: "enumeration", nuclei: "enumeration",
125
+ ffuf: "enumeration", dirsearch: "enumeration", gobuster: "enumeration",
126
+ feroxbuster: "enumeration", whatweb: "enumeration", wafw00f: "enumeration",
127
+ nikto: "enumeration", burpsuite: "enumeration", owasp_zap: "enumeration",
128
+ sqlmap: "exploitation", commix: "exploitation", hydra: "exploitation",
129
+ hashcat: "exploitation", john: "exploitation", metasploit: "exploitation",
130
+ pwntools: "exploitation",
131
+ curl: "utility", jq: "utility", anew: "utility", grep: "utility",
132
+ sort: "utility", uniq: "utility", notify: "reporting",
133
+ }
134
+ return categoryMap[toolName]
135
+ }
136
+
137
+ export function getSkillsForMode(mode: PentestMode): string[] {
138
+ return [...MODE_PRESETS[mode].skill_chain]
139
+ }
140
+
141
+ export function getLoopConfig(mode: PentestMode) {
142
+ return MODE_PRESETS[mode].loop_config
143
+ }
144
+
145
+ export function getSafetyConfig(mode: PentestMode) {
146
+ return MODE_PRESETS[mode].safety_constraints
147
+ }
148
+
149
+ export function getReportFormat(mode: PentestMode) {
150
+ return MODE_PRESETS[mode].report_format
151
+ }
152
+
153
+ export function isModeStealth(mode: PentestMode): boolean {
154
+ return MODE_PRESETS[mode].stealth
155
+ }
156
+
157
+ export function getModeParallelism(mode: PentestMode): number {
158
+ return MODE_PRESETS[mode].parallelism
159
+ }
@@ -0,0 +1 @@
1
+ export * from "./tool-selector.js"
@@ -0,0 +1,87 @@
1
+ import type { ToolsCatalog, ToolEntry, ToolSelectorOptions, PentestPhase } from "../types.js"
2
+
3
+ export function selectTools(catalog: ToolsCatalog, options: ToolSelectorOptions): ToolEntry[] {
4
+ let results = [...catalog.tools]
5
+
6
+ if (options.category) {
7
+ results = results.filter(t => t.category === options.category)
8
+ }
9
+
10
+ if (options.phase) {
11
+ results = results.filter(t => t.phase.includes(options.phase!))
12
+ }
13
+
14
+ if (options.tags && options.tags.length > 0) {
15
+ results = results.filter(t =>
16
+ options.tags!.some(tag => t.tags.includes(tag))
17
+ )
18
+ }
19
+
20
+ if (options.requires_root !== undefined) {
21
+ results = results.filter(t => t.requires_root === options.requires_root)
22
+ }
23
+
24
+ return results
25
+ }
26
+
27
+ export function selectToolsByPhase(catalog: ToolsCatalog, phase: "recon" | "enumeration" | "exploitation" | "reporting"): ToolEntry[] {
28
+ return selectTools(catalog, { phase })
29
+ }
30
+
31
+ export function selectToolsByCategory(catalog: ToolsCatalog, category: "recon" | "enumeration" | "exploitation" | "reporting" | "utility"): ToolEntry[] {
32
+ return selectTools(catalog, { category })
33
+ }
34
+
35
+ export function selectToolsByTags(catalog: ToolsCatalog, tags: readonly string[]): ToolEntry[] {
36
+ return selectTools(catalog, { tags })
37
+ }
38
+
39
+ export function groupToolsByPhase(catalog: ToolsCatalog): Record<string, ToolEntry[]> {
40
+ const groups: Record<string, ToolEntry[]> = {
41
+ recon: [],
42
+ enumeration: [],
43
+ exploitation: [],
44
+ reporting: [],
45
+ utility: []
46
+ }
47
+
48
+ for (const tool of catalog.tools) {
49
+ for (const phase of tool.phase) {
50
+ if (groups[phase]) {
51
+ groups[phase].push(tool)
52
+ }
53
+ }
54
+ }
55
+
56
+ return groups
57
+ }
58
+
59
+ export function groupToolsByCategory(catalog: ToolsCatalog): Record<string, ToolEntry[]> {
60
+ const groups: Record<string, ToolEntry[]> = {}
61
+
62
+ for (const category of catalog.categories) {
63
+ groups[category] = []
64
+ }
65
+
66
+ for (const tool of catalog.tools) {
67
+ if (groups[tool.category]) {
68
+ groups[tool.category].push(tool)
69
+ }
70
+ }
71
+
72
+ return groups
73
+ }
74
+
75
+ export function findAlternatives(catalog: ToolsCatalog, toolName: string): ToolEntry[] {
76
+ const tool = catalog.tools.find(t => t.tools_name === toolName)
77
+ if (!tool?.alternatives?.length) return []
78
+
79
+ return catalog.tools.filter(t => tool.alternatives!.includes(t.tools_name))
80
+ }
81
+
82
+ export function getToolPipeline(catalog: ToolsCatalog, toolName: string): ToolEntry[] {
83
+ const tool = catalog.tools.find(t => t.tools_name === toolName)
84
+ if (!tool?.command.pipes?.length) return []
85
+
86
+ return catalog.tools.filter(t => tool.command.pipes!.includes(t.tools_name))
87
+ }
@@ -0,0 +1 @@
1
+ export * from "./skill-bridge.js"
@@ -0,0 +1,86 @@
1
+ import type { ToolEntry, SkillBridgeResult } from "../types.js"
2
+ import { existsSync } from "fs"
3
+ import { join } from "path"
4
+
5
+ const SKILL_DIRS = [
6
+ ".agents/skills",
7
+ ".opencode/skills",
8
+ ".claude/skills"
9
+ ]
10
+
11
+ export function resolveSkillPath(skillName: string, baseDir: string = process.cwd()): string | null {
12
+ for (const dir of SKILL_DIRS) {
13
+ const skillPath = join(baseDir, dir, skillName, "SKILL.md")
14
+ if (existsSync(skillPath)) {
15
+ return skillPath
16
+ }
17
+ }
18
+ return null
19
+ }
20
+
21
+ export function loadToolSkill(tool: ToolEntry, baseDir: string = process.cwd()): SkillBridgeResult {
22
+ const skillPath = resolveSkillPath(tool.skills_loader, baseDir)
23
+
24
+ if (!skillPath) {
25
+ return {
26
+ skill_name: tool.skills_loader,
27
+ loaded: false,
28
+ error: `Skill not found: ${tool.skills_loader}`
29
+ }
30
+ }
31
+
32
+ return {
33
+ skill_name: tool.skills_loader,
34
+ skill_path: skillPath,
35
+ loaded: true
36
+ }
37
+ }
38
+
39
+ export function loadToolSkills(tools: readonly ToolEntry[], baseDir: string = process.cwd()): SkillBridgeResult[] {
40
+ return tools.map(tool => loadToolSkill(tool, baseDir))
41
+ }
42
+
43
+ export function getSkillForTool(tools: readonly ToolEntry[], toolName: string): SkillBridgeResult | null {
44
+ const tool = tools.find(t => t.tools_name === toolName)
45
+ if (!tool) return null
46
+ return loadToolSkill(tool)
47
+ }
48
+
49
+ export function groupToolsBySkill(tools: readonly ToolEntry[]): Record<string, ToolEntry[]> {
50
+ const groups: Record<string, ToolEntry[]> = {}
51
+
52
+ for (const tool of tools) {
53
+ const skill = tool.skills_loader
54
+ if (!groups[skill]) {
55
+ groups[skill] = []
56
+ }
57
+ groups[skill].push(tool)
58
+ }
59
+
60
+ return groups
61
+ }
62
+
63
+ export function getToolsForSkill(tools: readonly ToolEntry[], skillName: string): ToolEntry[] {
64
+ return tools.filter(t => t.skills_loader === skillName)
65
+ }
66
+
67
+ export interface SkillManifest {
68
+ readonly skill_name: string
69
+ readonly skill_path: string | null
70
+ readonly tools: readonly ToolEntry[]
71
+ readonly available: boolean
72
+ }
73
+
74
+ export function buildSkillManifest(tools: readonly ToolEntry[], baseDir: string = process.cwd()): SkillManifest[] {
75
+ const grouped = groupToolsBySkill(tools)
76
+
77
+ return Object.entries(grouped).map(([skillName, skillTools]) => {
78
+ const skillPath = resolveSkillPath(skillName, baseDir)
79
+ return {
80
+ skill_name: skillName,
81
+ skill_path: skillPath,
82
+ tools: skillTools,
83
+ available: skillPath !== null
84
+ }
85
+ })
86
+ }
@@ -0,0 +1 @@
1
+ export * from "./skill-generator.js"
@@ -0,0 +1,373 @@
1
+ import type { PentestSkill, GeneratedSkill, SkillGenerationOptions } from "../types.js"
2
+ import type { ToolEntry, ToolsCatalog, PentestPhase, ToolCategory } from "../../types.js"
3
+ import { existsSync, mkdirSync, writeFileSync } from "fs"
4
+ import { join, dirname } from "path"
5
+
6
+ const DEFAULT_OUTPUT_DIR = ".agents/skills"
7
+
8
+ const TEMPLATES: Record<string, (opts: SkillGenerationOptions, tools: readonly ToolEntry[]) => string> = {
9
+ basic: generateBasicTemplate,
10
+ recon: generateReconTemplate,
11
+ exploitation: generateExploitationTemplate,
12
+ reporting: generateReportingTemplate,
13
+ custom: generateBasicTemplate,
14
+ }
15
+
16
+ export function generateSkill(
17
+ options: SkillGenerationOptions,
18
+ catalog?: ToolsCatalog,
19
+ ): GeneratedSkill {
20
+ const templateName = options.template ?? "basic"
21
+ const templateFn = TEMPLATES[templateName] ?? TEMPLATES.basic
22
+
23
+ const catalogTools = catalog
24
+ ? catalog.tools.filter(t => options.tools.includes(t.tools_name))
25
+ : []
26
+
27
+ const content = templateFn(options, catalogTools)
28
+ const outputDir = options.output_dir ?? DEFAULT_OUTPUT_DIR
29
+ const skillDir = join(outputDir, options.name)
30
+ const skillPath = join(skillDir, "SKILL.md")
31
+
32
+ return {
33
+ name: options.name,
34
+ content,
35
+ path: skillPath,
36
+ tools_referenced: options.tools,
37
+ phase: options.phase,
38
+ }
39
+ }
40
+
41
+ export function generateAndSaveSkill(
42
+ options: SkillGenerationOptions,
43
+ catalog?: ToolsCatalog,
44
+ ): string {
45
+ const skill = generateSkill(options, catalog)
46
+ const dir = dirname(skill.path)
47
+
48
+ if (!existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true })
50
+ }
51
+
52
+ writeFileSync(skill.path, skill.content)
53
+ return skill.path
54
+ }
55
+
56
+ export function generateSkillsForPhase(
57
+ phase: PentestPhase,
58
+ catalog: ToolsCatalog,
59
+ outputDir?: string,
60
+ ): GeneratedSkill[] {
61
+ const phaseTools = catalog.tools.filter(t => t.phase.includes(phase))
62
+ const skills: GeneratedSkill[] = []
63
+
64
+ for (const tool of phaseTools) {
65
+ const template = phaseToTemplate(phase)
66
+ const skill = generateSkill({
67
+ name: `${tool.tools_name}-${phase}`,
68
+ description: `Automated ${phase} skill using ${tool.tools_name}: ${tool.description}`,
69
+ phase: [phase],
70
+ category: [tool.category],
71
+ tools: [tool.tools_name],
72
+ tags: [...tool.tags],
73
+ output_dir: outputDir,
74
+ template,
75
+ }, catalog)
76
+
77
+ skills.push(skill)
78
+ }
79
+
80
+ return skills
81
+ }
82
+
83
+ export function generateSkillsForTool(
84
+ toolName: string,
85
+ catalog: ToolsCatalog,
86
+ outputDir?: string,
87
+ ): GeneratedSkill[] {
88
+ const tool = catalog.tools.find(t => t.tools_name === toolName)
89
+ if (!tool) return []
90
+
91
+ return tool.phase.map(phase => {
92
+ return generateSkill({
93
+ name: `${toolName}-${phase}`,
94
+ description: `${tool.description} — ${phase} phase skill`,
95
+ phase: [phase],
96
+ category: [tool.category],
97
+ tools: [toolName],
98
+ tags: [...tool.tags, phase],
99
+ output_dir: outputDir,
100
+ template: phaseToTemplate(phase),
101
+ }, catalog)
102
+ })
103
+ }
104
+
105
+ export function generateFullPentestSuite(
106
+ catalog: ToolsCatalog,
107
+ outputDir?: string,
108
+ ): GeneratedSkill[] {
109
+ const allSkills: GeneratedSkill[] = []
110
+
111
+ for (const category of catalog.categories) {
112
+ const skills = generateSkillsForPhase(category as PentestPhase, catalog, outputDir)
113
+ allSkills.push(...skills)
114
+ }
115
+
116
+ return allSkills
117
+ }
118
+
119
+ export function saveGeneratedSkills(skills: readonly GeneratedSkill[]): string[] {
120
+ const paths: string[] = []
121
+
122
+ for (const skill of skills) {
123
+ const dir = dirname(skill.path)
124
+ if (!existsSync(dir)) {
125
+ mkdirSync(dir, { recursive: true })
126
+ }
127
+ writeFileSync(skill.path, skill.content)
128
+ paths.push(skill.path)
129
+ }
130
+
131
+ return paths
132
+ }
133
+
134
+ function phaseToTemplate(phase: PentestPhase): "basic" | "recon" | "exploitation" | "reporting" {
135
+ switch (phase) {
136
+ case "recon":
137
+ case "enumeration":
138
+ return "recon"
139
+ case "exploitation":
140
+ return "exploitation"
141
+ case "reporting":
142
+ return "reporting"
143
+ default:
144
+ return "basic"
145
+ }
146
+ }
147
+
148
+ function buildFrontmatter(options: SkillGenerationOptions): string {
149
+ const lines = [
150
+ "---",
151
+ `name: ${options.name}`,
152
+ `description: "${options.description}"`,
153
+ `version: 1.0.0`,
154
+ `phase: [${options.phase.map(p => `"${p}"`).join(", ")}]`,
155
+ `category: [${options.category.map(c => `"${c}"`).join(", ")}]`,
156
+ `tools: [${options.tools.map(t => `"${t}"`).join(", ")}]`,
157
+ `tags: [${(options.tags ?? []).map(t => `"${t}"`).join(", ")}]`,
158
+ ]
159
+
160
+ if (options.author) {
161
+ lines.push(`author: "${options.author}"`)
162
+ }
163
+
164
+ lines.push("---")
165
+ return lines.join("\n")
166
+ }
167
+
168
+ function buildToolReferenceSection(tools: readonly ToolEntry[]): string {
169
+ if (tools.length === 0) return ""
170
+
171
+ const lines = ["\n## Tools Reference\n"]
172
+
173
+ for (const tool of tools) {
174
+ lines.push(`### ${tool.tools_name}`)
175
+ lines.push(`${tool.description}\n`)
176
+ lines.push(`**Command:** \`${tool.command.base}\``)
177
+ lines.push(`**Category:** ${tool.category}`)
178
+ lines.push(`**Phase:** ${tool.phase.join(", ")}`)
179
+ lines.push(`**Requires root:** ${tool.requires_root ? "Yes" : "No"}`)
180
+
181
+ if (tool.command.flags.length > 0) {
182
+ lines.push("\n**Key flags:**")
183
+ for (const flag of tool.command.flags.slice(0, 5)) {
184
+ const req = flag.required ? " (required)" : ""
185
+ lines.push(`- \`${flag.name}\` — ${flag.description}${req}`)
186
+ }
187
+ }
188
+
189
+ if (tool.homepage) {
190
+ lines.push(`\n**Homepage:** ${tool.homepage}`)
191
+ }
192
+
193
+ lines.push("")
194
+ }
195
+
196
+ return lines.join("\n")
197
+ }
198
+
199
+ function generateBasicTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
200
+ const fm = buildFrontmatter(options)
201
+ const toolRef = buildToolReferenceSection(tools)
202
+
203
+ return `${fm}
204
+
205
+ # ${options.name}
206
+
207
+ ${options.description}
208
+
209
+ ## Prerequisites
210
+
211
+ ${tools.map(t => `- ${t.tools_name} must be installed and accessible`).join("\n")}
212
+
213
+ ## Usage
214
+
215
+ \`\`\`bash
216
+ # Step 1: Run the tool(s)
217
+ ${tools.map(t => `${t.command.base} ${t.command.flags.filter(f => f.required).map(f => `${f.name} <value>`).join(" ")}`).join("\n")}
218
+ \`\`\`
219
+
220
+ ## Expected Output
221
+
222
+ Describe expected output and how to interpret results.
223
+
224
+ ## Next Steps
225
+
226
+ Describe follow-up actions based on findings.
227
+ ${toolRef}`
228
+ }
229
+
230
+ function generateReconTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
231
+ const fm = buildFrontmatter(options)
232
+ const toolRef = buildToolReferenceSection(tools)
233
+
234
+ return `${fm}
235
+
236
+ # ${options.name} — Reconnaissance Skill
237
+
238
+ ${options.description}
239
+
240
+ ## Phase: Reconnaissance
241
+
242
+ ### Objectives
243
+ - Discover target attack surface
244
+ - Enumerate subdomains, ports, and services
245
+ - Identify technologies and potential entry points
246
+
247
+ ### Prerequisites
248
+ ${tools.map(t => `- **${t.tools_name}**: ${t.description}`).join("\n")}
249
+
250
+ ### Step 1: Subdomain Discovery
251
+ \`\`\`bash
252
+ ${tools.filter(t => t.tags.includes("subdomain")).map(t =>
253
+ `${t.command.base} -d <target-domain> -o subdomains.txt`
254
+ ).join("\n")}
255
+ \`\`\`
256
+
257
+ ### Step 2: Port Scanning
258
+ \`\`\`bash
259
+ ${tools.filter(t => t.tags.includes("port-scan") || t.tags.includes("port")).map(t =>
260
+ `${t.command.base} -host <targets> -o ports.json`
261
+ ).join("\n")}
262
+ \`\`\`
263
+
264
+ ### Step 3: Service Detection
265
+ \`\`\`bash
266
+ ${tools.filter(t => t.tags.includes("http") || t.tags.includes("service")).map(t =>
267
+ `${t.command.base} -l subdomains.txt -json -o services.json`
268
+ ).join("\n")}
269
+ \`\`\`
270
+
271
+ ### Output Analysis
272
+ - Review discovered subdomains for interesting targets
273
+ - Correlate open ports with identified services
274
+ - Prioritize targets for exploitation phase
275
+
276
+ ### Handoff
277
+ Pass results to exploitation phase with prioritized target list.
278
+ ${toolRef}`
279
+ }
280
+
281
+ function generateExploitationTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
282
+ const fm = buildFrontmatter(options)
283
+ const toolRef = buildToolReferenceSection(tools)
284
+
285
+ return `${fm}
286
+
287
+ # ${options.name} — Exploitation Skill
288
+
289
+ ${options.description}
290
+
291
+ ## Phase: Exploitation
292
+
293
+ ### Objectives
294
+ - Validate identified vulnerabilities
295
+ - Develop proof-of-concept exploits
296
+ - Document impact and severity
297
+
298
+ ### Prerequisites
299
+ ${tools.map(t => `- **${t.tools_name}**: ${t.description}`).join("\n")}
300
+
301
+ ### Step 1: Vulnerability Validation
302
+ \`\`\`bash
303
+ # Validate each finding from reconnaissance
304
+ ${tools.map(t => `${t.command.base} <target> <parameters>`).join("\n")}
305
+ \`\`\`
306
+
307
+ ### Step 2: PoC Development
308
+ For each validated vulnerability:
309
+ 1. Document the exact attack vector
310
+ 2. Create minimal reproducible PoC
311
+ 3. Assess impact (data access, privilege escalation, etc.)
312
+
313
+ ### Step 3: Severity Assessment
314
+ | Finding | CVSS | Impact | Exploitability |
315
+ |---------|------|--------|----------------|
316
+ | _fill_ | _fill_ | _fill_ | _fill_ |
317
+
318
+ ### Safety Rules
319
+ - Only test targets within authorized scope
320
+ - Do not exfiltrate real user data
321
+ - Document all actions for the report
322
+ ${toolRef}`
323
+ }
324
+
325
+ function generateReportingTemplate(options: SkillGenerationOptions, _tools: readonly ToolEntry[]): string {
326
+ const fm = buildFrontmatter(options)
327
+
328
+ return `${fm}
329
+
330
+ # ${options.name} — Reporting Skill
331
+
332
+ ${options.description}
333
+
334
+ ## Phase: Reporting
335
+
336
+ ### Report Structure
337
+
338
+ #### Executive Summary
339
+ - Brief overview of engagement scope
340
+ - Key findings summary (critical/high count)
341
+ - Risk assessment
342
+
343
+ #### Findings Detail
344
+ For each finding:
345
+
346
+ ##### Finding: [Title]
347
+ - **Severity:** Critical / High / Medium / Low / Info
348
+ - **CVSS Score:** X.X (vector string)
349
+ - **Affected Asset:** URL / endpoint / component
350
+ - **Description:** Clear explanation of the vulnerability
351
+
352
+ **Proof of Concept:**
353
+ \`\`\`
354
+ [Step-by-step reproduction]
355
+ \`\`\`
356
+
357
+ **Impact:**
358
+ What an attacker could achieve.
359
+
360
+ **Remediation:**
361
+ Specific fix recommendation.
362
+
363
+ #### Methodology
364
+ - Tools used: ${options.tools.join(", ")}
365
+ - Phases covered: ${options.phase.join(", ")}
366
+ - Scope tested: [define scope]
367
+
368
+ #### Appendices
369
+ - Raw scan outputs
370
+ - Screenshots
371
+ - Timeline of testing
372
+ `
373
+ }