pentesting 0.3.1 → 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.
@@ -0,0 +1,130 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-3RG5ZIWI.js";
4
+
5
+ // src/core/replay/session-replay.ts
6
+ import { existsSync, readFileSync } from "fs";
7
+ function parseWireFile(filePath) {
8
+ if (!existsSync(filePath)) return [];
9
+ const events = [];
10
+ const content = readFileSync(filePath, "utf-8");
11
+ for (const line of content.split("\n")) {
12
+ if (!line.trim()) continue;
13
+ try {
14
+ const record = JSON.parse(line);
15
+ events.push({
16
+ type: record.message.type,
17
+ timestamp: record.message.timestamp,
18
+ data: record.message.data
19
+ });
20
+ } catch {
21
+ }
22
+ }
23
+ return events;
24
+ }
25
+ function getReplaySummary(events) {
26
+ if (events.length === 0) {
27
+ return { events, duration: 0, toolCalls: 0, findings: 0 };
28
+ }
29
+ const firstTs = events[0].timestamp;
30
+ const lastTs = events[events.length - 1].timestamp;
31
+ const duration = (lastTs - firstTs) / 1e3;
32
+ let toolCalls = 0;
33
+ let findings = 0;
34
+ for (const event of events) {
35
+ if (event.type === "tool_call") toolCalls++;
36
+ if (event.type === "status_update") {
37
+ const data = event.data;
38
+ if (data.event === "finding") findings++;
39
+ }
40
+ }
41
+ return { events, duration, toolCalls, findings };
42
+ }
43
+ function formatReplayEvent(event) {
44
+ const time = new Date(event.timestamp).toLocaleTimeString();
45
+ switch (event.type) {
46
+ case "turn_begin":
47
+ return `[${time}] \u{1F504} Turn started`;
48
+ case "step_begin": {
49
+ const data = event.data;
50
+ return `[${time}] \u{1F4CD} Step ${data.stepIndex + 1}`;
51
+ }
52
+ case "content_part": {
53
+ const data = event.data;
54
+ const preview = data.content.slice(0, 50).replace(/\n/g, " ");
55
+ const icon = data.isThinking ? "\u{1F4AD}" : "\u{1F4AC}";
56
+ return `[${time}] ${icon} ${preview}...`;
57
+ }
58
+ case "tool_call": {
59
+ const data = event.data;
60
+ return `[${time}] \u25B6 Tool: ${data.toolName}`;
61
+ }
62
+ case "tool_result": {
63
+ const data = event.data;
64
+ const icon = data.isError ? "\u2717" : "\u2713";
65
+ return `[${time}] ${icon} Tool result`;
66
+ }
67
+ case "status_update": {
68
+ const data = event.data;
69
+ if (data.event === "finding") {
70
+ return `[${time}] \u{1F3AF} Finding: ${data.title}`;
71
+ }
72
+ if (data.event === "phase_change") {
73
+ return `[${time}] \u{1F4CD} Phase: ${data.phase}`;
74
+ }
75
+ return `[${time}] \u{1F4CA} ${data.event}`;
76
+ }
77
+ case "turn_end":
78
+ return `[${time}] \u2713 Turn complete`;
79
+ default:
80
+ return `[${time}] ${event.type}`;
81
+ }
82
+ }
83
+ async function replaySession(wirePath, options = {}) {
84
+ const events = parseWireFile(wirePath);
85
+ const summary = getReplaySummary(events);
86
+ if (events.length === 0) return summary;
87
+ const speed = options.speed ?? 0;
88
+ let lastTs = events[0].timestamp;
89
+ for (const event of events) {
90
+ const formatted = formatReplayEvent(event);
91
+ if (options.onEvent) {
92
+ options.onEvent(event, formatted);
93
+ }
94
+ if (speed > 0) {
95
+ const delay = (event.timestamp - lastTs) / speed;
96
+ if (delay > 0 && delay < 5e3) {
97
+ await new Promise((r) => setTimeout(r, delay));
98
+ }
99
+ lastTs = event.timestamp;
100
+ }
101
+ }
102
+ return summary;
103
+ }
104
+ function findRecentWireFiles(sessionDir, limit = 10) {
105
+ const { readdirSync, statSync } = __require("fs");
106
+ const { join } = __require("path");
107
+ if (!existsSync(sessionDir)) return [];
108
+ const files = [];
109
+ try {
110
+ const entries = readdirSync(sessionDir, { withFileTypes: true });
111
+ for (const entry of entries) {
112
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
113
+ const filePath = join(sessionDir, entry.name);
114
+ const stat = statSync(filePath);
115
+ files.push({ path: filePath, mtime: stat.mtimeMs });
116
+ }
117
+ }
118
+ } catch {
119
+ return [];
120
+ }
121
+ files.sort((a, b) => b.mtime - a.mtime);
122
+ return files.slice(0, limit).map((f) => f.path);
123
+ }
124
+ export {
125
+ findRecentWireFiles,
126
+ formatReplayEvent,
127
+ getReplaySummary,
128
+ parseWireFile,
129
+ replaySession
130
+ };
@@ -0,0 +1,416 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
3
+ // src/core/skill/flow.ts
4
+ var FlowError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "FlowError";
8
+ }
9
+ };
10
+ var FlowParseError = class extends FlowError {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "FlowParseError";
14
+ }
15
+ };
16
+ var FlowValidationError = class extends FlowError {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "FlowValidationError";
20
+ }
21
+ };
22
+ function parseChoice(text) {
23
+ const match = text.match(/<choice>([^<]*)<\/choice>/);
24
+ return match ? match[1].trim() : null;
25
+ }
26
+ function validateFlow(nodes, outgoing) {
27
+ const beginNodes = Array.from(nodes.values()).filter((n) => n.kind === "begin");
28
+ const endNodes = Array.from(nodes.values()).filter((n) => n.kind === "end");
29
+ if (beginNodes.length !== 1) {
30
+ throw new FlowValidationError(`Expected exactly one BEGIN node, found ${beginNodes.length}`);
31
+ }
32
+ if (endNodes.length !== 1) {
33
+ throw new FlowValidationError(`Expected exactly one END node, found ${endNodes.length}`);
34
+ }
35
+ const beginId = beginNodes[0].id;
36
+ const endId = endNodes[0].id;
37
+ const reachable = /* @__PURE__ */ new Set();
38
+ const queue = [beginId];
39
+ while (queue.length > 0) {
40
+ const nodeId = queue.pop();
41
+ if (reachable.has(nodeId)) continue;
42
+ reachable.add(nodeId);
43
+ const edges = outgoing.get(nodeId) || [];
44
+ for (const edge of edges) {
45
+ if (!reachable.has(edge.dst)) {
46
+ queue.push(edge.dst);
47
+ }
48
+ }
49
+ }
50
+ if (!reachable.has(endId)) {
51
+ throw new FlowValidationError("END node is not reachable from BEGIN");
52
+ }
53
+ for (const node of nodes.values()) {
54
+ if (!reachable.has(node.id)) continue;
55
+ const edges = outgoing.get(node.id) || [];
56
+ if (edges.length <= 1) continue;
57
+ const labels = [];
58
+ for (const edge of edges) {
59
+ if (!edge.label?.trim()) {
60
+ throw new FlowValidationError(`Node "${node.id}" has an unlabeled edge`);
61
+ }
62
+ labels.push(edge.label);
63
+ }
64
+ if (new Set(labels).size !== labels.length) {
65
+ throw new FlowValidationError(`Node "${node.id}" has duplicate edge labels`);
66
+ }
67
+ }
68
+ return { beginId, endId };
69
+ }
70
+ function parseMermaidFlowchart(code) {
71
+ const nodes = /* @__PURE__ */ new Map();
72
+ const outgoing = /* @__PURE__ */ new Map();
73
+ const lines = code.split("\n").map((l) => l.trim()).filter((l) => l);
74
+ for (const line of lines) {
75
+ if (line.match(/^flowchart|^graph/i)) continue;
76
+ const nodeMatch = line.match(/^([A-Za-z0-9_]+)(\[([^\]]+)\]|\{([^\}]+)\}|\(\(([^\)]+)\)\)|\(\[([^\]]+)\]\))?$/);
77
+ if (nodeMatch && !line.includes("-->")) {
78
+ const id = nodeMatch[1];
79
+ const label = nodeMatch[3] || nodeMatch[4] || nodeMatch[5] || nodeMatch[6] || id;
80
+ let kind = "task";
81
+ if (nodeMatch[4]) kind = "decision";
82
+ else if (nodeMatch[5] || label.toLowerCase().includes("start") || label.toLowerCase().includes("begin")) kind = "begin";
83
+ else if (nodeMatch[6] || label.toLowerCase().includes("end")) kind = "end";
84
+ nodes.set(id, { id, label, kind });
85
+ continue;
86
+ }
87
+ const edgeMatch = line.match(/^([A-Za-z0-9_]+)\s*-->\s*(?:\|([^\|]+)\|\s*)?([A-Za-z0-9_]+)/);
88
+ if (edgeMatch) {
89
+ const src = edgeMatch[1];
90
+ const label = edgeMatch[2] || null;
91
+ const dst = edgeMatch[3];
92
+ if (!nodes.has(src)) nodes.set(src, { id: src, label: src, kind: "task" });
93
+ if (!nodes.has(dst)) nodes.set(dst, { id: dst, label: dst, kind: "task" });
94
+ const edges = outgoing.get(src) || [];
95
+ edges.push({ src, dst, label });
96
+ outgoing.set(src, edges);
97
+ }
98
+ }
99
+ const { beginId, endId } = validateFlow(nodes, outgoing);
100
+ return { nodes, outgoing, beginId, endId };
101
+ }
102
+ function parseD2Flowchart(code) {
103
+ const nodes = /* @__PURE__ */ new Map();
104
+ const outgoing = /* @__PURE__ */ new Map();
105
+ const lines = code.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
106
+ for (const line of lines) {
107
+ const edgeMatch = line.match(/^([A-Za-z0-9_]+)\s*->\s*([A-Za-z0-9_]+)(?:\s*:\s*(.+))?$/);
108
+ if (edgeMatch) {
109
+ const src = edgeMatch[1];
110
+ const dst = edgeMatch[2];
111
+ const label = edgeMatch[3] || null;
112
+ if (!nodes.has(src)) {
113
+ const kind = src.toLowerCase().includes("start") ? "begin" : "task";
114
+ nodes.set(src, { id: src, label: src, kind });
115
+ }
116
+ if (!nodes.has(dst)) {
117
+ const kind = dst.toLowerCase().includes("end") ? "end" : "task";
118
+ nodes.set(dst, { id: dst, label: dst, kind });
119
+ }
120
+ const edges = outgoing.get(src) || [];
121
+ edges.push({ src, dst, label });
122
+ outgoing.set(src, edges);
123
+ continue;
124
+ }
125
+ const nodeMatch = line.match(/^([A-Za-z0-9_]+)\s*:\s*"?([^"{}]+)"?\s*(?:\{([^}]+)\})?$/);
126
+ if (nodeMatch) {
127
+ const id = nodeMatch[1];
128
+ const label = nodeMatch[2].trim();
129
+ const attrs = nodeMatch[3] || "";
130
+ let kind = "task";
131
+ if (attrs.includes("diamond")) kind = "decision";
132
+ else if (attrs.includes("oval") && label.toLowerCase().includes("start")) kind = "begin";
133
+ else if (attrs.includes("oval") && label.toLowerCase().includes("end")) kind = "end";
134
+ nodes.set(id, { id, label, kind });
135
+ }
136
+ }
137
+ const { beginId, endId } = validateFlow(nodes, outgoing);
138
+ return { nodes, outgoing, beginId, endId };
139
+ }
140
+ var FlowExecutor = class {
141
+ flow;
142
+ currentNodeId;
143
+ moveCount = 0;
144
+ maxMoves;
145
+ constructor(flow, maxMoves = 1e3) {
146
+ this.flow = flow;
147
+ this.currentNodeId = flow.beginId;
148
+ this.maxMoves = maxMoves;
149
+ }
150
+ get currentNode() {
151
+ return this.flow.nodes.get(this.currentNodeId);
152
+ }
153
+ get isComplete() {
154
+ return this.currentNodeId === this.flow.endId;
155
+ }
156
+ get availableChoices() {
157
+ const edges = this.flow.outgoing.get(this.currentNodeId) || [];
158
+ return edges.filter((e) => e.label).map((e) => e.label);
159
+ }
160
+ /**
161
+ * Move to next node
162
+ */
163
+ move(choice) {
164
+ if (this.isComplete) {
165
+ throw new FlowError("Flow is already complete");
166
+ }
167
+ if (this.moveCount >= this.maxMoves) {
168
+ throw new FlowError(`Max moves (${this.maxMoves}) exceeded`);
169
+ }
170
+ const edges = this.flow.outgoing.get(this.currentNodeId) || [];
171
+ if (edges.length === 0) {
172
+ throw new FlowError(`No outgoing edges from node "${this.currentNodeId}"`);
173
+ }
174
+ let nextEdge;
175
+ if (edges.length === 1) {
176
+ nextEdge = edges[0];
177
+ } else if (choice) {
178
+ nextEdge = edges.find((e) => e.label?.toLowerCase() === choice.toLowerCase());
179
+ if (!nextEdge) {
180
+ throw new FlowError(`Invalid choice "${choice}". Available: ${this.availableChoices.join(", ")}`);
181
+ }
182
+ } else {
183
+ throw new FlowError(`Choice required. Available: ${this.availableChoices.join(", ")}`);
184
+ }
185
+ this.currentNodeId = nextEdge.dst;
186
+ this.moveCount++;
187
+ return this.currentNode;
188
+ }
189
+ /**
190
+ * Reset flow
191
+ */
192
+ reset() {
193
+ this.currentNodeId = this.flow.beginId;
194
+ this.moveCount = 0;
195
+ }
196
+ };
197
+
198
+ // src/core/skill/skill.ts
199
+ import { existsSync, readdirSync, readFileSync } from "fs";
200
+ import { join } from "path";
201
+ import { homedir } from "os";
202
+ function getBuiltinSkillsDir() {
203
+ return join(__dirname, "..", "..", "skills");
204
+ }
205
+ function getUserSkillsDirCandidates() {
206
+ return [
207
+ join(homedir(), ".config", "agents", "skills"),
208
+ join(homedir(), ".agents", "skills"),
209
+ join(homedir(), ".pentest", "skills"),
210
+ join(homedir(), ".claude", "skills")
211
+ ];
212
+ }
213
+ function getProjectSkillsDirCandidates(workDir) {
214
+ return [
215
+ join(workDir, ".agents", "skills"),
216
+ join(workDir, ".pentest", "skills"),
217
+ join(workDir, ".claude", "skills")
218
+ ];
219
+ }
220
+ function findFirstExistingDir(candidates) {
221
+ for (const candidate of candidates) {
222
+ if (existsSync(candidate)) {
223
+ return candidate;
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ function parseFrontmatter(content) {
229
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
230
+ if (!match) return {};
231
+ const yaml = match[1];
232
+ const result = {};
233
+ for (const line of yaml.split("\n")) {
234
+ const colonIndex = line.indexOf(":");
235
+ if (colonIndex > 0) {
236
+ const key = line.slice(0, colonIndex).trim();
237
+ const value = line.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, "");
238
+ result[key] = value;
239
+ }
240
+ }
241
+ return result;
242
+ }
243
+ function extractCodeBlocks(content) {
244
+ const blocks = [];
245
+ const regex = /```(\w+)?\n([\s\S]*?)```/g;
246
+ let match;
247
+ while ((match = regex.exec(content)) !== null) {
248
+ blocks.push({
249
+ lang: (match[1] || "").toLowerCase(),
250
+ code: match[2].trim()
251
+ });
252
+ }
253
+ return blocks;
254
+ }
255
+ function parseSkillText(content, dirPath) {
256
+ const frontmatter = parseFrontmatter(content);
257
+ const name = frontmatter.name || dirPath.split("/").pop() || "unnamed";
258
+ const description = frontmatter.description || "No description provided.";
259
+ const skillType = frontmatter.type || "standard";
260
+ let flow;
261
+ if (skillType === "flow") {
262
+ try {
263
+ const codeBlocks = extractCodeBlocks(content);
264
+ for (const block of codeBlocks) {
265
+ if (block.lang === "mermaid") {
266
+ flow = parseMermaidFlowchart(block.code);
267
+ break;
268
+ } else if (block.lang === "d2") {
269
+ flow = parseD2Flowchart(block.code);
270
+ break;
271
+ }
272
+ }
273
+ if (!flow) {
274
+ throw new FlowError("Flow skills require a mermaid or d2 code block");
275
+ }
276
+ } catch (e) {
277
+ console.error(`Failed to parse flow skill ${name}:`, e);
278
+ return {
279
+ name,
280
+ description,
281
+ type: "standard",
282
+ dir: dirPath,
283
+ content
284
+ };
285
+ }
286
+ }
287
+ return {
288
+ name,
289
+ description,
290
+ type: skillType,
291
+ dir: dirPath,
292
+ flow,
293
+ content
294
+ };
295
+ }
296
+ function discoverSkills(skillsDir) {
297
+ if (!existsSync(skillsDir)) return [];
298
+ const skills = [];
299
+ try {
300
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
301
+ for (const entry of entries) {
302
+ if (!entry.isDirectory()) continue;
303
+ const skillDir = join(skillsDir, entry.name);
304
+ const skillMd = join(skillDir, "SKILL.md");
305
+ if (!existsSync(skillMd)) continue;
306
+ try {
307
+ const content = readFileSync(skillMd, "utf-8");
308
+ const skill = parseSkillText(content, skillDir);
309
+ skills.push(skill);
310
+ } catch {
311
+ }
312
+ }
313
+ } catch {
314
+ }
315
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
316
+ }
317
+ function discoverSkillsFromRoots(skillsDirs) {
318
+ const skillsByName = /* @__PURE__ */ new Map();
319
+ for (const dir of skillsDirs) {
320
+ for (const skill of discoverSkills(dir)) {
321
+ skillsByName.set(skill.name.toLowerCase(), skill);
322
+ }
323
+ }
324
+ return Array.from(skillsByName.values()).sort((a, b) => a.name.localeCompare(b.name));
325
+ }
326
+ function resolveSkillRoots(workDir) {
327
+ const roots = [];
328
+ const builtinDir = getBuiltinSkillsDir();
329
+ if (existsSync(builtinDir)) {
330
+ roots.push(builtinDir);
331
+ }
332
+ const userDir = findFirstExistingDir(getUserSkillsDirCandidates());
333
+ if (userDir) {
334
+ roots.push(userDir);
335
+ }
336
+ const projectDir = findFirstExistingDir(getProjectSkillsDirCandidates(workDir));
337
+ if (projectDir) {
338
+ roots.push(projectDir);
339
+ }
340
+ return roots;
341
+ }
342
+ var SkillManager = class {
343
+ skills = /* @__PURE__ */ new Map();
344
+ workDir;
345
+ constructor(workDir) {
346
+ this.workDir = workDir || process.cwd();
347
+ }
348
+ /**
349
+ * Load all skills
350
+ */
351
+ load() {
352
+ const roots = resolveSkillRoots(this.workDir);
353
+ const skills = discoverSkillsFromRoots(roots);
354
+ this.skills.clear();
355
+ for (const skill of skills) {
356
+ this.skills.set(skill.name.toLowerCase(), skill);
357
+ }
358
+ }
359
+ /**
360
+ * Find skill by name
361
+ */
362
+ find(name) {
363
+ return this.skills.get(name.toLowerCase());
364
+ }
365
+ /**
366
+ * List all skills
367
+ */
368
+ list() {
369
+ return Array.from(this.skills.values());
370
+ }
371
+ /**
372
+ * Get skill content
373
+ */
374
+ getContent(name) {
375
+ const skill = this.find(name);
376
+ return skill?.content || null;
377
+ }
378
+ /**
379
+ * Format skills for system prompt
380
+ */
381
+ formatForPrompt() {
382
+ const skills = this.list();
383
+ if (skills.length === 0) return "No skills found.";
384
+ return skills.map(
385
+ (s) => `- ${s.name}
386
+ - Type: ${s.type}
387
+ - Description: ${s.description}`
388
+ ).join("\n");
389
+ }
390
+ };
391
+ var skillManager = null;
392
+ function getSkillManager(workDir) {
393
+ if (!skillManager) {
394
+ skillManager = new SkillManager(workDir);
395
+ skillManager.load();
396
+ }
397
+ return skillManager;
398
+ }
399
+ export {
400
+ FlowError,
401
+ FlowExecutor,
402
+ FlowParseError,
403
+ FlowValidationError,
404
+ SkillManager,
405
+ discoverSkills,
406
+ discoverSkillsFromRoots,
407
+ extractCodeBlocks,
408
+ getSkillManager,
409
+ parseChoice,
410
+ parseD2Flowchart,
411
+ parseFrontmatter,
412
+ parseMermaidFlowchart,
413
+ parseSkillText,
414
+ resolveSkillRoots,
415
+ validateFlow
416
+ };
@@ -0,0 +1,24 @@
1
+ import {
2
+ checkForUpdate,
3
+ checkForUpdateAsync,
4
+ compareSemver,
5
+ doUpdate,
6
+ fetchLatestVersion,
7
+ formatUpdateNotification,
8
+ readVersionCache,
9
+ semverTuple,
10
+ writeVersionCache
11
+ } from "./chunk-LZGHM27D.js";
12
+ import "./chunk-IU6YJKJT.js";
13
+ import "./chunk-3RG5ZIWI.js";
14
+ export {
15
+ checkForUpdate,
16
+ checkForUpdateAsync,
17
+ compareSemver,
18
+ doUpdate,
19
+ fetchLatestVersion,
20
+ formatUpdateNotification,
21
+ readVersionCache,
22
+ semverTuple,
23
+ writeVersionCache
24
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",