@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,206 @@
1
+ import type { PentestSkill, PentestSkillManifest, SkillLoadResult } from "../types.js"
2
+ import type { PentestPhase, ToolCategory } from "../../types.js"
3
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs"
4
+ import { join } from "path"
5
+
6
+ const SKILL_DIRS = [
7
+ ".agents/skills",
8
+ ".opencode/skills",
9
+ ".claude/skills",
10
+ "packages/pentest-skills/skills",
11
+ ]
12
+
13
+ const SKILL_FILENAME = "SKILL.md"
14
+
15
+ export function resolveSkillPath(skillName: string, baseDir: string = process.cwd()): string | null {
16
+ for (const dir of SKILL_DIRS) {
17
+ const skillPath = join(baseDir, dir, skillName, SKILL_FILENAME)
18
+ if (existsSync(skillPath)) {
19
+ return skillPath
20
+ }
21
+ }
22
+ return null
23
+ }
24
+
25
+ export function discoverSkills(baseDir: string = process.cwd()): PentestSkillManifest[] {
26
+ const manifests: PentestSkillManifest[] = []
27
+
28
+ for (const dir of SKILL_DIRS) {
29
+ const skillsDir = join(baseDir, dir)
30
+ if (!existsSync(skillsDir)) continue
31
+
32
+ const entries = readdirSync(skillsDir)
33
+ for (const entry of entries) {
34
+ const skillDir = join(skillsDir, entry)
35
+ if (!statSync(skillDir).isDirectory()) continue
36
+
37
+ const skillFile = join(skillDir, SKILL_FILENAME)
38
+ if (!existsSync(skillFile)) continue
39
+
40
+ const manifest = parseSkillManifest(skillFile, entry, skillDir)
41
+ if (manifest) {
42
+ manifests.push(manifest)
43
+ }
44
+ }
45
+ }
46
+
47
+ return manifests
48
+ }
49
+
50
+ export function loadSkill(skillName: string, baseDir: string = process.cwd()): SkillLoadResult {
51
+ const skillPath = resolveSkillPath(skillName, baseDir)
52
+
53
+ if (!skillPath) {
54
+ return {
55
+ name: skillName,
56
+ loaded: false,
57
+ error: `Skill not found: ${skillName}`,
58
+ }
59
+ }
60
+
61
+ try {
62
+ const content = readFileSync(skillPath, "utf-8")
63
+ const skill = parseSkillContent(content, skillName, skillPath)
64
+
65
+ return {
66
+ name: skillName,
67
+ loaded: true,
68
+ skill,
69
+ }
70
+ } catch (error) {
71
+ return {
72
+ name: skillName,
73
+ loaded: false,
74
+ error: error instanceof Error ? error.message : "Failed to load skill",
75
+ }
76
+ }
77
+ }
78
+
79
+ export function loadAllSkills(baseDir: string = process.cwd()): SkillLoadResult[] {
80
+ const manifests = discoverSkills(baseDir)
81
+ return manifests.map(m => loadSkill(m.name, baseDir))
82
+ }
83
+
84
+ export function loadSkillsByPhase(
85
+ phase: string,
86
+ baseDir: string = process.cwd(),
87
+ ): SkillLoadResult[] {
88
+ const manifests = discoverSkills(baseDir)
89
+ const filtered = manifests.filter(m => m.phase.includes(phase as never))
90
+ return filtered.map(m => loadSkill(m.name, baseDir))
91
+ }
92
+
93
+ export function loadSkillsByTools(
94
+ toolNames: readonly string[],
95
+ baseDir: string = process.cwd(),
96
+ ): SkillLoadResult[] {
97
+ const manifests = discoverSkills(baseDir)
98
+ const toolSet = new Set(toolNames)
99
+ const filtered = manifests.filter(m => m.tools.some((t: string) => toolSet.has(t)))
100
+ return filtered.map(m => loadSkill(m.name, baseDir))
101
+ }
102
+
103
+ export function getSkillsForTool(
104
+ toolName: string,
105
+ baseDir: string = process.cwd(),
106
+ ): SkillLoadResult[] {
107
+ return loadSkillsByTools([toolName], baseDir)
108
+ }
109
+
110
+ function parseSkillManifest(
111
+ skillFile: string,
112
+ skillName: string,
113
+ _skillDir: string,
114
+ ): PentestSkillManifest | null {
115
+ try {
116
+ const content = readFileSync(skillFile, "utf-8")
117
+ const frontmatter = extractFrontmatter(content)
118
+
119
+ if (!frontmatter) {
120
+ return {
121
+ name: skillName,
122
+ description: "",
123
+ version: "0.0.0",
124
+ phase: [] as PentestPhase[],
125
+ category: [] as ToolCategory[],
126
+ tools: [],
127
+ tags: [],
128
+ skill_path: skillFile,
129
+ loaded: false,
130
+ }
131
+ }
132
+
133
+ return {
134
+ name: (frontmatter.name as string) ?? skillName,
135
+ description: (frontmatter.description as string) ?? "",
136
+ version: (frontmatter.version as string) ?? "0.0.0",
137
+ phase: parseStringArray(frontmatter.phase) as PentestPhase[],
138
+ category: parseStringArray(frontmatter.category) as ToolCategory[],
139
+ tools: parseStringArray(frontmatter.tools),
140
+ tags: parseStringArray(frontmatter.tags),
141
+ skill_path: skillFile,
142
+ loaded: true,
143
+ }
144
+ } catch {
145
+ return null
146
+ }
147
+ }
148
+
149
+ function parseSkillContent(
150
+ content: string,
151
+ skillName: string,
152
+ _skillPath: string,
153
+ ): PentestSkill {
154
+ const frontmatter = extractFrontmatter(content) ?? {}
155
+
156
+ return {
157
+ name: (frontmatter.name as string) ?? skillName,
158
+ description: (frontmatter.description as string) ?? "",
159
+ version: (frontmatter.version as string) ?? "0.0.0",
160
+ phase: parseStringArray(frontmatter.phase) as PentestPhase[],
161
+ category: parseStringArray(frontmatter.category) as ToolCategory[],
162
+ tools: parseStringArray(frontmatter.tools),
163
+ author: frontmatter.author as string | undefined,
164
+ homepage: frontmatter.homepage as string | undefined,
165
+ tags: parseStringArray(frontmatter.tags),
166
+ template: content,
167
+ }
168
+ }
169
+
170
+ function extractFrontmatter(content: string): Record<string, unknown> | null {
171
+ const match = /^---\s*\n([\s\S]*?)\n---/.exec(content)
172
+ if (!match) return null
173
+
174
+ const yaml = match[1]
175
+ const result: Record<string, unknown> = {}
176
+
177
+ for (const line of yaml.split("\n")) {
178
+ const colonIndex = line.indexOf(":")
179
+ if (colonIndex === -1) continue
180
+
181
+ const key = line.slice(0, colonIndex).trim()
182
+ const value = line.slice(colonIndex + 1).trim()
183
+
184
+ if (value.startsWith("[") && value.endsWith("]")) {
185
+ result[key] = value
186
+ .slice(1, -1)
187
+ .split(",")
188
+ .map(s => s.trim().replace(/^["']|["']$/g, ""))
189
+ } else {
190
+ result[key] = value.replace(/^["']|["']$/g, "")
191
+ }
192
+ }
193
+
194
+ return result
195
+ }
196
+
197
+ function parseStringArray(value: unknown): string[] {
198
+ if (Array.isArray(value)) return value.map(String)
199
+ if (typeof value === "string") {
200
+ if (value.startsWith("[") && value.endsWith("]")) {
201
+ return value.slice(1, -1).split(",").map(s => s.trim().replace(/^["']|["']$/g, ""))
202
+ }
203
+ return [value]
204
+ }
205
+ return []
206
+ }
@@ -0,0 +1 @@
1
+ export * from "./skill-register.js"
@@ -0,0 +1,196 @@
1
+ import type { PentestSkill, SkillRegisterEntry, SkillRegisterConfig } from "../types.js"
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs"
3
+ import { join, dirname } from "path"
4
+ import { loadSkill } from "../loader/skill-loader.js"
5
+
6
+ const DEFAULT_CONFIG: SkillRegisterConfig = {
7
+ skills_dir: ".agents/skills",
8
+ auto_discover: true,
9
+ search_paths: [".agents/skills", ".opencode/skills", ".claude/skills"],
10
+ }
11
+
12
+ export class SkillRegister {
13
+ private entries: Map<string, SkillRegisterEntry> = new Map()
14
+ private config: SkillRegisterConfig
15
+
16
+ constructor(config: Partial<SkillRegisterConfig> = {}) {
17
+ this.config = { ...DEFAULT_CONFIG, ...config }
18
+
19
+ if (this.config.auto_discover) {
20
+ this.discoverAndRegister()
21
+ }
22
+ }
23
+
24
+ register(skill: PentestSkill): SkillRegisterEntry {
25
+ const entry: SkillRegisterEntry = {
26
+ name: skill.name,
27
+ skill,
28
+ registered_at: new Date().toISOString(),
29
+ enabled: true,
30
+ }
31
+
32
+ this.entries.set(skill.name, entry)
33
+ return entry
34
+ }
35
+
36
+ registerFromFile(skillName: string, baseDir?: string): SkillRegisterEntry | null {
37
+ const result = loadSkill(skillName, baseDir)
38
+
39
+ if (!result.loaded || !result.skill) {
40
+ return null
41
+ }
42
+
43
+ return this.register(result.skill)
44
+ }
45
+
46
+ registerMany(skills: readonly PentestSkill[]): SkillRegisterEntry[] {
47
+ return skills.map(skill => this.register(skill))
48
+ }
49
+
50
+ registerFromFiles(skillNames: readonly string[], baseDir?: string): SkillRegisterEntry[] {
51
+ const entries: SkillRegisterEntry[] = []
52
+
53
+ for (const name of skillNames) {
54
+ const entry = this.registerFromFile(name, baseDir)
55
+ if (entry) entries.push(entry)
56
+ }
57
+
58
+ return entries
59
+ }
60
+
61
+ unregister(skillName: string): boolean {
62
+ return this.entries.delete(skillName)
63
+ }
64
+
65
+ enable(skillName: string): boolean {
66
+ const entry = this.entries.get(skillName)
67
+ if (!entry) return false
68
+ ;(entry as { enabled: boolean }).enabled = true
69
+ return true
70
+ }
71
+
72
+ disable(skillName: string): boolean {
73
+ const entry = this.entries.get(skillName)
74
+ if (!entry) return false
75
+ ;(entry as { enabled: boolean }).enabled = false
76
+ return true
77
+ }
78
+
79
+ get(skillName: string): SkillRegisterEntry | undefined {
80
+ return this.entries.get(skillName)
81
+ }
82
+
83
+ getEnabled(): SkillRegisterEntry[] {
84
+ return [...this.entries.values()].filter(e => e.enabled)
85
+ }
86
+
87
+ getAll(): SkillRegisterEntry[] {
88
+ return [...this.entries.values()]
89
+ }
90
+
91
+ getByPhase(phase: string): SkillRegisterEntry[] {
92
+ return this.getEnabled().filter(e => e.skill.phase.includes(phase as never))
93
+ }
94
+
95
+ getByCategory(category: string): SkillRegisterEntry[] {
96
+ return this.getEnabled().filter(e => e.skill.category.includes(category as never))
97
+ }
98
+
99
+ getByTool(toolName: string): SkillRegisterEntry[] {
100
+ return this.getEnabled().filter(e => e.skill.tools.includes(toolName))
101
+ }
102
+
103
+ getByTag(tag: string): SkillRegisterEntry[] {
104
+ return this.getEnabled().filter(e => e.skill.tags.includes(tag))
105
+ }
106
+
107
+ has(skillName: string): boolean {
108
+ return this.entries.has(skillName)
109
+ }
110
+
111
+ count(): number {
112
+ return this.entries.size
113
+ }
114
+
115
+ enabledCount(): number {
116
+ return this.getEnabled().length
117
+ }
118
+
119
+ clear(): void {
120
+ this.entries.clear()
121
+ }
122
+
123
+ saveToFile(outputPath?: string): string {
124
+ const path = outputPath ?? join(this.config.skills_dir, ".skill-register.json")
125
+ const dir = dirname(path)
126
+
127
+ if (!existsSync(dir)) {
128
+ mkdirSync(dir, { recursive: true })
129
+ }
130
+
131
+ const data = {
132
+ version: "1.0.0",
133
+ saved_at: new Date().toISOString(),
134
+ entries: this.getAll().map(e => ({
135
+ name: e.name,
136
+ description: e.skill.description,
137
+ version: e.skill.version,
138
+ phase: e.skill.phase,
139
+ category: e.skill.category,
140
+ tools: e.skill.tools,
141
+ tags: e.skill.tags,
142
+ enabled: e.enabled,
143
+ registered_at: e.registered_at,
144
+ })),
145
+ }
146
+
147
+ writeFileSync(path, JSON.stringify(data, null, 2))
148
+ return path
149
+ }
150
+
151
+ loadFromFile(inputPath?: string): number {
152
+ const path = inputPath ?? join(this.config.skills_dir, ".skill-register.json")
153
+
154
+ if (!existsSync(path)) return 0
155
+
156
+ try {
157
+ const content = readFileSync(path, "utf-8")
158
+ const data = JSON.parse(content) as { entries: Array<{ name: string; enabled: boolean }> }
159
+
160
+ let loaded = 0
161
+ for (const entry of data.entries ?? []) {
162
+ const result = loadSkill(entry.name)
163
+ if (result.loaded && result.skill) {
164
+ const registered = this.register(result.skill)
165
+ ;(registered as { enabled: boolean }).enabled = entry.enabled
166
+ loaded++
167
+ }
168
+ }
169
+
170
+ return loaded
171
+ } catch {
172
+ return 0
173
+ }
174
+ }
175
+
176
+ private discoverAndRegister(): void {
177
+ for (const searchPath of this.config.search_paths) {
178
+ if (!existsSync(searchPath)) continue
179
+
180
+ const entries = readdirSync(searchPath)
181
+ for (const entry of entries) {
182
+ const skillPath = join(searchPath, entry, "SKILL.md")
183
+ if (existsSync(skillPath)) {
184
+ const result = loadSkill(entry, searchPath)
185
+ if (result.loaded && result.skill) {
186
+ this.register(result.skill)
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ export function createSkillRegister(config?: Partial<SkillRegisterConfig>): SkillRegister {
195
+ return new SkillRegister(config)
196
+ }
@@ -0,0 +1,66 @@
1
+ import type { PentestPhase, ToolCategory } from "../types.js"
2
+
3
+ export interface PentestSkill {
4
+ readonly name: string
5
+ readonly description: string
6
+ readonly version: string
7
+ readonly phase: readonly PentestPhase[]
8
+ readonly category: readonly ToolCategory[]
9
+ readonly tools: readonly string[]
10
+ readonly author?: string
11
+ readonly homepage?: string
12
+ readonly tags: readonly string[]
13
+ readonly template: string
14
+ }
15
+
16
+ export interface PentestSkillManifest {
17
+ readonly name: string
18
+ readonly description: string
19
+ readonly version: string
20
+ readonly phase: readonly PentestPhase[]
21
+ readonly category: readonly ToolCategory[]
22
+ readonly tools: readonly string[]
23
+ readonly tags: readonly string[]
24
+ readonly skill_path: string
25
+ readonly loaded: boolean
26
+ }
27
+
28
+ export interface SkillRegisterEntry {
29
+ readonly name: string
30
+ readonly skill: PentestSkill
31
+ readonly registered_at: string
32
+ readonly enabled: boolean
33
+ }
34
+
35
+ export interface SkillRegisterConfig {
36
+ readonly skills_dir: string
37
+ readonly auto_discover: boolean
38
+ readonly search_paths: readonly string[]
39
+ }
40
+
41
+ export interface GeneratedSkill {
42
+ readonly name: string
43
+ readonly content: string
44
+ readonly path: string
45
+ readonly tools_referenced: readonly string[]
46
+ readonly phase: readonly PentestPhase[]
47
+ }
48
+
49
+ export interface SkillGenerationOptions {
50
+ readonly name: string
51
+ readonly description: string
52
+ readonly phase: readonly PentestPhase[]
53
+ readonly category: readonly ToolCategory[]
54
+ readonly tools: readonly string[]
55
+ readonly author?: string
56
+ readonly tags?: readonly string[]
57
+ readonly output_dir?: string
58
+ readonly template?: "basic" | "recon" | "exploitation" | "reporting" | "custom"
59
+ }
60
+
61
+ export interface SkillLoadResult {
62
+ readonly name: string
63
+ readonly loaded: boolean
64
+ readonly skill?: PentestSkill
65
+ readonly error?: string
66
+ }