@naraya/cli 0.1.0 → 0.4.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 (83) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +394 -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,130 @@
1
+ import { PentestManager } from "./manager.js"
2
+ import type { PentestPhase, SkillLoadResult, PentestSkillManifest } from "./types.js"
3
+ import { Command } from "commander"
4
+
5
+ export async function pentestCLI(argv: string[]) {
6
+ const program = new Command()
7
+ .name("naraya pentest")
8
+ .description("Pentest orchestration for authorized engagements")
9
+ .version("0.2.0")
10
+
11
+ const manager = new PentestManager()
12
+
13
+ // naraya pentest list
14
+ program
15
+ .command("list")
16
+ .description("List all available pentest skills")
17
+ .action(() => {
18
+ const manifests = manager.discoverSkills()
19
+
20
+ if (manifests.length === 0) {
21
+ console.log("No pentest skills found.")
22
+ return
23
+ }
24
+
25
+ console.log(`Found ${manifests.length} skill(s):\n`)
26
+ for (const m of manifests) {
27
+ console.log(` * ${m.name} v${m.version} [${m.phase.join(", ")}]`)
28
+ console.log(` ${m.description}`)
29
+ console.log(` Tools: ${m.tools.join(", ")}`)
30
+ console.log(` Tags: ${m.tags.join(", ")}\n`)
31
+ }
32
+ })
33
+
34
+ // naraya pentest mode --target example.com
35
+ program
36
+ .command("mode")
37
+ .description("Auto-detect or set pentest mode")
38
+ .option("--target <target>", "Target to analyze for mode detection")
39
+ .option("--mode <mode>", "Force specific mode (auto|ctf|bug-bounty|red-team|blue-team|offensive|grey-hat)")
40
+ .action((options) => {
41
+ const result = manager.selectMode({
42
+ target: options.target,
43
+ preferred_mode: options.mode,
44
+ auto_detect: options.target !== undefined,
45
+ })
46
+
47
+ console.log(`\nMode: ${result.selected_mode}`)
48
+ console.log(`Auto-detected: ${result.auto_detected ? "Yes" : "No"}`)
49
+ console.log(`Reason: ${result.detection_reason}`)
50
+ console.log(`\nConfig:`)
51
+ console.log(` Description: ${result.config.description}`)
52
+ console.log(` Parallelism: ${result.config.parallelism}`)
53
+ console.log(` Stealth: ${result.config.stealth ? "Yes" : "No"}`)
54
+ console.log(` Report Format: ${result.config.report_format}`)
55
+ console.log(` Skill Chain: ${result.config.skill_chain.join(" -> ")}`)
56
+ console.log(` Tool Priority: ${result.config.tool_priority.join(" -> ")}\n`)
57
+ })
58
+
59
+ // naraya pentest phase --phase recon
60
+ program
61
+ .command("phase")
62
+ .description("Run a pentest phase")
63
+ .option("--target <url>", "Target domain or IP")
64
+ .option("--phase <phase>", "Phase: recon, enumeration, exploitation, reporting")
65
+ .option("--mode <mode>", "Pentest mode")
66
+ .option("--passive", "Passive-only mode")
67
+ .action((options) => {
68
+ if (!options.target) {
69
+ console.error("Error: --target is required")
70
+ process.exit(1)
71
+ }
72
+ if (!options.phase) {
73
+ console.error("Error: --phase is required")
74
+ process.exit(1)
75
+ }
76
+
77
+ const mode = options.mode || "auto"
78
+ const modeConfig = manager.getModeConfig(mode)
79
+
80
+ console.log(`Running ${options.phase} on ${options.target}...`)
81
+ console.log(`Mode: ${mode}`)
82
+ console.log(`Stealth: ${modeConfig.stealth ? "Yes" : "No"}`)
83
+ console.log(`Parallelism: ${modeConfig.parallelism}`)
84
+ console.log(`Skills: ${modeConfig.skill_chain.join(" -> ")}`)
85
+
86
+ const skills = manager.loadSkillsByPhase(options.phase)
87
+ const loaded = skills.filter((s: SkillLoadResult) => s.loaded)
88
+ if (loaded.length > 0) {
89
+ console.log(`\nLoaded ${loaded.length} skill(s):`)
90
+ for (const s of loaded) {
91
+ console.log(` * ${s.name} v${s.skill?.version}`)
92
+ }
93
+ } else {
94
+ console.log(`\nNo skills found for phase: ${options.phase}`)
95
+ }
96
+ console.log(`\n${options.phase} completed.`)
97
+ })
98
+
99
+ // naraya pentest register
100
+ program
101
+ .command("register")
102
+ .description("Register skills with the skill register")
103
+ .option("--skill <name>", "Register specific skill")
104
+ .option("--all", "Register all discovered skills")
105
+ .action((options) => {
106
+ const register = manager.getRegister()
107
+
108
+ if (options.all) {
109
+ const manifests = manager.discoverSkills()
110
+ const entries = register.registerFromFiles(manifests.map((m: PentestSkillManifest) => m.name))
111
+ console.log(`Registered ${entries.length} skill(s)`)
112
+ } else if (options.skill) {
113
+ const entry = register.registerFromFile(options.skill)
114
+ if (entry) {
115
+ console.log(`Registered: ${entry.name} v${entry.skill.version}`)
116
+ } else {
117
+ console.error(`Skill not found: ${options.skill}`)
118
+ process.exit(1)
119
+ }
120
+ } else {
121
+ console.log("Use --skill <name> or --all to register skills")
122
+ }
123
+
124
+ console.log(`\nTotal registered: ${register.count()}`)
125
+ console.log(`Enabled: ${register.enabledCount()}`)
126
+ })
127
+
128
+ program.parse(argv)
129
+ }
130
+
@@ -0,0 +1,109 @@
1
+ import type { ToolEntry, CommandBuildOptions, CommandBuildResult, FlagDefinition } from "../types.js"
2
+
3
+ export function buildCommand(tool: ToolEntry, options: CommandBuildOptions = {}): CommandBuildResult {
4
+ const args: string[] = []
5
+ const flags = options.flags ?? {}
6
+ const positional = options.positional ?? []
7
+
8
+ for (const flagDef of tool.command.flags) {
9
+ const value = flags[flagDef.name]
10
+ const argValue = buildFlagArg(flagDef, value)
11
+ if (argValue) {
12
+ args.push(...argValue)
13
+ }
14
+ }
15
+
16
+ if (positional.length > 0) {
17
+ args.push(...positional)
18
+ } else {
19
+ for (const posDef of tool.command.positional) {
20
+ const value = flags[posDef.name]
21
+ if (value !== undefined) {
22
+ args.push(String(value))
23
+ }
24
+ }
25
+ }
26
+
27
+ let fullCommand = `${tool.command.base} ${args.join(" ")}`.trim()
28
+
29
+ if (options.pipe_to && options.pipe_to.length > 0) {
30
+ fullCommand += ` | ${options.pipe_to.join(" | ")}`
31
+ }
32
+
33
+ return {
34
+ command: tool.command.base,
35
+ args,
36
+ fullCommand,
37
+ requires_root: tool.requires_root
38
+ }
39
+ }
40
+
41
+ function buildFlagArg(flag: FlagDefinition, value: unknown): string[] | null {
42
+ if (value === undefined || value === null) {
43
+ if (flag.default !== undefined && flag.type === "boolean" && flag.default === true) {
44
+ return [flag.name]
45
+ }
46
+ return null
47
+ }
48
+
49
+ switch (flag.type) {
50
+ case "boolean":
51
+ return value === true ? [flag.name] : null
52
+
53
+ case "string":
54
+ case "number":
55
+ return [flag.name, String(value)]
56
+
57
+ case "path":
58
+ return [flag.name, String(value)]
59
+
60
+ case "choice":
61
+ if (flag.choices && !flag.choices.includes(String(value))) {
62
+ throw new Error(`Invalid choice for ${flag.name}: ${value}. Valid: ${flag.choices.join(", ")}`)
63
+ }
64
+ return [flag.name, String(value)]
65
+
66
+ case "repeat":
67
+ if (Array.isArray(value)) {
68
+ return value.flatMap(v => [flag.name, String(v)])
69
+ }
70
+ return [flag.name, String(value)]
71
+
72
+ default:
73
+ return null
74
+ }
75
+ }
76
+
77
+ export function buildCommandWithSudo(tool: ToolEntry, options: CommandBuildOptions = {}): string {
78
+ const result = buildCommand(tool, options)
79
+ return result.requires_root ? `sudo ${result.fullCommand}` : result.fullCommand
80
+ }
81
+
82
+ export function buildPipeline(tools: ToolEntry[], optionsPerTool: CommandBuildOptions[]): string {
83
+ const commands = tools.map((tool, i) => buildCommand(tool, optionsPerTool[i]))
84
+ return commands.map(c => c.fullCommand).join(" | ")
85
+ }
86
+
87
+ export function validateRequiredFlags(tool: ToolEntry, options: CommandBuildOptions): string[] {
88
+ const errors: string[] = []
89
+ const flags = options.flags ?? {}
90
+
91
+ for (const flag of tool.command.flags) {
92
+ if (flag.required && flags[flag.name] === undefined) {
93
+ errors.push(`Missing required flag: ${flag.name}`)
94
+ }
95
+ }
96
+
97
+ for (const pos of tool.command.positional) {
98
+ if (pos.required) {
99
+ const hasValue = options.positional?.length
100
+ ? options.positional.length > 0
101
+ : flags[pos.name] !== undefined
102
+ if (!hasValue) {
103
+ errors.push(`Missing required positional argument: ${pos.name}`)
104
+ }
105
+ }
106
+ }
107
+
108
+ return errors
109
+ }
@@ -0,0 +1 @@
1
+ export * from "./command-builder.js"
@@ -0,0 +1,11 @@
1
+ export * from "./catalog/index.js"
2
+ export * from "./selector/index.js"
3
+ export * from "./command-builder/index.js"
4
+ export * from "./installer/index.js"
5
+ export * from "./skill-bridge/index.js"
6
+ export * from "./mode/index.js"
7
+ // skills re-exports overlap with types - only re-export the classes/functions, not types
8
+ export { discoverSkills, loadSkill, loadAllSkills, loadSkillsByPhase, loadSkillsByTools, getSkillsForTool, resolveSkillPath as resolveSkillPathFromLoader } from "./skills/loader/skill-loader.js"
9
+ export { SkillRegister, createSkillRegister } from "./skills/register/skill-register.js"
10
+ export { generateSkill, generateAndSaveSkill, generateSkillsForPhase, generateSkillsForTool, generateFullPentestSuite, saveGeneratedSkills } from "./skills/generator/skill-generator.js"
11
+ export type * from "./types.js"
@@ -0,0 +1 @@
1
+ export * from "./tool-installer.js"
@@ -0,0 +1,107 @@
1
+ import type { ToolEntry, ToolAvailability, InstallationConfig, PlatformInstallation } from "../types.js"
2
+ import { exec } from "child_process"
3
+ import { promisify } from "util"
4
+
5
+ const execAsync = promisify(exec)
6
+
7
+ export async function checkToolInstalled(tool: ToolEntry): Promise<ToolAvailability> {
8
+ try {
9
+ const { stdout } = await execAsync(tool.check_installed.command, { timeout: 10000 })
10
+
11
+ let version: string | undefined
12
+ if (tool.check_installed.parse_version) {
13
+ const match = stdout.match(new RegExp(tool.check_installed.parse_version))
14
+ version = match?.[1]
15
+ }
16
+
17
+ return {
18
+ tools_name: tool.tools_name,
19
+ installed: true,
20
+ version
21
+ }
22
+ } catch (error) {
23
+ return {
24
+ tools_name: tool.tools_name,
25
+ installed: false,
26
+ error: error instanceof Error ? error.message : "Unknown error"
27
+ }
28
+ }
29
+ }
30
+
31
+ export async function checkAllToolsInstalled(tools: readonly ToolEntry[]): Promise<ToolAvailability[]> {
32
+ return Promise.all(tools.map(checkToolInstalled))
33
+ }
34
+
35
+ export function getInstallCommand(tool: ToolEntry, platform: NodeJS.Platform = process.platform): string | null {
36
+ const config = tool.installation[platform as keyof InstallationConfig]
37
+ return config?.command ?? null
38
+ }
39
+
40
+ export function getInstallCommands(tool: ToolEntry): Partial<Record<NodeJS.Platform, PlatformInstallation>> {
41
+ const result: Partial<Record<NodeJS.Platform, PlatformInstallation>> = {}
42
+
43
+ if (tool.installation.linux) result.linux = tool.installation.linux
44
+ if (tool.installation.darwin) result.darwin = tool.installation.darwin
45
+ if (tool.installation.win32) result.win32 = tool.installation.win32
46
+
47
+ return result
48
+ }
49
+
50
+ export async function installTool(tool: ToolEntry, platform: NodeJS.Platform = process.platform): Promise<{ success: boolean; message: string }> {
51
+ const config = tool.installation[platform as keyof InstallationConfig]
52
+
53
+ if (!config) {
54
+ return {
55
+ success: false,
56
+ message: `No installation command available for platform: ${platform}`
57
+ }
58
+ }
59
+
60
+ try {
61
+ const { stdout, stderr } = await execAsync(config.command, { timeout: 300000 })
62
+ return {
63
+ success: true,
64
+ message: stdout || stderr || "Installation completed"
65
+ }
66
+ } catch (error) {
67
+ return {
68
+ success: false,
69
+ message: error instanceof Error ? error.message : "Installation failed"
70
+ }
71
+ }
72
+ }
73
+
74
+ export function getMissingTools(availability: ToolAvailability[]): ToolAvailability[] {
75
+ return availability.filter(a => !a.installed)
76
+ }
77
+
78
+ export function getInstalledTools(availability: ToolAvailability[]): ToolAvailability[] {
79
+ return availability.filter(a => a.installed)
80
+ }
81
+
82
+ export async function ensureToolsInstalled(tools: readonly ToolEntry[], platform: NodeJS.Platform = process.platform): Promise<{
83
+ installed: ToolAvailability[]
84
+ missing: ToolAvailability[]
85
+ failed: { tool: string; error: string }[]
86
+ }> {
87
+ const availability = await checkAllToolsInstalled(tools)
88
+ const missing = getMissingTools(availability)
89
+ const failed: { tool: string; error: string }[] = []
90
+
91
+ for (const tool of missing) {
92
+ const toolEntry = tools.find(t => t.tools_name === tool.tools_name)
93
+ if (!toolEntry) continue
94
+
95
+ const result = await installTool(toolEntry, platform)
96
+ if (!result.success) {
97
+ failed.push({ tool: tool.tools_name, error: result.message })
98
+ }
99
+ }
100
+
101
+ const recheck = await checkAllToolsInstalled(tools)
102
+ return {
103
+ installed: getInstalledTools(recheck),
104
+ missing: getMissingTools(recheck),
105
+ failed
106
+ }
107
+ }
@@ -0,0 +1,167 @@
1
+ import type { PentestSkill, PentestSkillManifest, SkillLoadResult, ToolsCatalog, ToolEntry, ToolSelectorOptions, PentestPhase, PentestMode, ModeConfig, ModeSelectorOptions, ModeSelectionResult, ModeLoopConfig, SafetyConfig } from "./types.js"
2
+ import { MODE_PRESETS } from "./types.js"
3
+ import { discoverSkills, loadSkill, loadAllSkills, loadSkillsByPhase } from "./skills/loader/skill-loader.js"
4
+ import { SkillRegister } from "./skills/register/skill-register.js"
5
+ import { selectTools, selectToolsByPhase, selectToolsByCategory, groupToolsByPhase, groupToolsByCategory } from "./selector/tool-selector.js"
6
+ import { buildCommand, buildCommandWithSudo, validateRequiredFlags } from "./command-builder/command-builder.js"
7
+ import { checkToolInstalled, checkAllToolsInstalled, getInstallCommand, installTool } from "./installer/tool-installer.js"
8
+ import { selectMode, detectMode, getModeConfig, getToolsForMode, getSkillsForMode, getLoopConfig, getSafetyConfig } from "./mode/mode-selector.js"
9
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs"
10
+ import { join, resolve } from "path"
11
+
12
+ export interface PentestManagerConfig {
13
+ baseDir?: string
14
+ skillDirs?: string[]
15
+ configPath?: string
16
+ catalogPath?: string
17
+ }
18
+
19
+ const DEFAULT_SKILL_DIRS = [
20
+ "assets/agents/skills",
21
+ "skills/",
22
+ "packages/pentest-skills/skills",
23
+ ]
24
+
25
+ const DEFAULT_CATALOG_PATH = "tools-catalog.json"
26
+
27
+ export class PentestManager {
28
+ private readonly config: Required<PentestManagerConfig>
29
+ private readonly cache = {
30
+ manifests: new Map<string, PentestSkillManifest[]>(),
31
+ skills: new Map<string, SkillLoadResult>(),
32
+ catalog: null as ToolsCatalog | null,
33
+ }
34
+ private skillRegister: SkillRegister | null = null
35
+
36
+ constructor(config: PentestManagerConfig = {}) {
37
+ this.config = {
38
+ baseDir: config.baseDir ?? process.cwd(),
39
+ skillDirs: config.skillDirs ?? DEFAULT_SKILL_DIRS,
40
+ configPath: config.configPath ?? join(config.baseDir ?? process.cwd(), ".pentestrc"),
41
+ catalogPath: config.catalogPath ?? join(config.baseDir ?? process.cwd(), DEFAULT_CATALOG_PATH),
42
+ }
43
+ }
44
+
45
+ // ── Mode Operations ──
46
+
47
+ selectMode(options: ModeSelectorOptions = {}): ModeSelectionResult {
48
+ return selectMode(options)
49
+ }
50
+
51
+ detectMode(target: string): { mode: PentestMode; reason: string } {
52
+ return detectMode(target)
53
+ }
54
+
55
+ getModeConfig(mode: PentestMode): ModeConfig {
56
+ return getModeConfig(mode)
57
+ }
58
+
59
+ getSkillsForMode(mode: PentestMode): string[] {
60
+ return getSkillsForMode(mode)
61
+ }
62
+
63
+ getLoopConfig(mode: PentestMode): ModeLoopConfig {
64
+ return getLoopConfig(mode)
65
+ }
66
+
67
+ getSafetyConfig(mode: PentestMode): SafetyConfig {
68
+ return getSafetyConfig(mode)
69
+ }
70
+
71
+ // ── Skill Operations ──
72
+
73
+ discoverSkills(): PentestSkillManifest[] {
74
+ return discoverSkills(this.config.baseDir)
75
+ }
76
+
77
+ loadSkill(skillName: string): SkillLoadResult {
78
+ return loadSkill(skillName, this.config.baseDir)
79
+ }
80
+
81
+ loadAllSkills(): SkillLoadResult[] {
82
+ return loadAllSkills(this.config.baseDir)
83
+ }
84
+
85
+ loadSkillsByPhase(phase: string): SkillLoadResult[] {
86
+ return loadSkillsByPhase(phase, this.config.baseDir)
87
+ }
88
+
89
+ // ── Catalog Operations ──
90
+
91
+ async loadCatalog(): Promise<ToolsCatalog> {
92
+ if (this.cache.catalog) return this.cache.catalog
93
+
94
+ const catalogPath = this.config.catalogPath
95
+ if (!existsSync(catalogPath)) {
96
+ throw new Error(`Tools catalog not found: ${catalogPath}`)
97
+ }
98
+
99
+ const content = readFileSync(catalogPath, "utf-8")
100
+ this.cache.catalog = JSON.parse(content) as ToolsCatalog
101
+ return this.cache.catalog
102
+ }
103
+
104
+ async selectTools(options: ToolSelectorOptions): Promise<ToolEntry[]> {
105
+ const catalog = await this.loadCatalog()
106
+ return selectTools(catalog, options)
107
+ }
108
+
109
+ async selectToolsByPhase(phase: PentestPhase): Promise<ToolEntry[]> {
110
+ const catalog = await this.loadCatalog()
111
+ return selectToolsByPhase(catalog, phase)
112
+ }
113
+
114
+ async selectToolsByCategory(category: "recon" | "enumeration" | "exploitation" | "reporting" | "utility"): Promise<ToolEntry[]> {
115
+ const catalog = await this.loadCatalog()
116
+ return selectToolsByCategory(catalog, category)
117
+ }
118
+
119
+ // ── Command Operations ──
120
+
121
+ buildToolCommand(tool: ToolEntry, flags: Record<string, unknown> = {}, positional: string[] = []): string {
122
+ const result = buildCommand(tool, { flags, positional })
123
+ return result.fullCommand
124
+ }
125
+
126
+ buildToolCommandWithSudo(tool: ToolEntry, flags: Record<string, unknown> = {}, positional: string[] = []): string {
127
+ return buildCommandWithSudo(tool, { flags, positional })
128
+ }
129
+
130
+ validateToolFlags(tool: ToolEntry, flags: Record<string, unknown>): string[] {
131
+ return validateRequiredFlags(tool, { flags })
132
+ }
133
+
134
+ // ── Installation Operations ──
135
+
136
+ async checkToolInstalled(tool: ToolEntry) {
137
+ return checkToolInstalled(tool)
138
+ }
139
+
140
+ async checkAllToolsInstalled(tools: readonly ToolEntry[]) {
141
+ return checkAllToolsInstalled(tools)
142
+ }
143
+
144
+ getInstallCommandForTool(tool: ToolEntry): string | null {
145
+ return getInstallCommand(tool)
146
+ }
147
+
148
+ async installTool(tool: ToolEntry) {
149
+ return installTool(tool)
150
+ }
151
+
152
+ // ── Skill Register ──
153
+
154
+ getRegister(): SkillRegister {
155
+ if (!this.skillRegister) {
156
+ this.skillRegister = new SkillRegister({
157
+ skills_dir: join(this.config.baseDir, ".agents/skills"),
158
+ search_paths: [
159
+ join(this.config.baseDir, ".agents/skills"),
160
+ join(this.config.baseDir, ".opencode/skills"),
161
+ join(this.config.baseDir, ".claude/skills"),
162
+ ],
163
+ })
164
+ }
165
+ return this.skillRegister
166
+ }
167
+ }
@@ -0,0 +1 @@
1
+ export * from "./mode-selector.js"
@@ -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"