pentesting 0.3.2 → 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.
- package/dist/auto-update-H72IBVEQ.js +24 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-IU6YJKJT.js +182 -0
- package/dist/chunk-LZGHM27D.js +134 -0
- package/dist/index.js +98 -169
- package/dist/replay-6WU2ANWJ.js +130 -0
- package/dist/skill-2AON6M2V.js +416 -0
- package/dist/update-DNXSBIOM.js +24 -0
- package/package.json +1 -1
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/config/agent-constants.ts
|
|
2
|
+
var AGENT_STATUS = {
|
|
3
|
+
IDLE: "idle",
|
|
4
|
+
RUNNING: "running",
|
|
5
|
+
PAUSED: "paused",
|
|
6
|
+
STUCK: "stuck",
|
|
7
|
+
WAITING_INPUT: "waiting_input",
|
|
8
|
+
COMPLETED: "completed"
|
|
9
|
+
};
|
|
10
|
+
var PHASE_ID = {
|
|
11
|
+
RECON: "recon",
|
|
12
|
+
SCAN: "scan",
|
|
13
|
+
ENUM: "enum",
|
|
14
|
+
VULN: "vuln",
|
|
15
|
+
EXPLOIT: "exploit",
|
|
16
|
+
PRIVESC: "privesc",
|
|
17
|
+
PIVOT: "pivot",
|
|
18
|
+
PERSIST: "persist",
|
|
19
|
+
EXFIL: "exfil",
|
|
20
|
+
REPORT: "report"
|
|
21
|
+
};
|
|
22
|
+
var PHASE_STATUS = {
|
|
23
|
+
PENDING: "pending",
|
|
24
|
+
IN_PROGRESS: "in_progress",
|
|
25
|
+
COMPLETED: "completed",
|
|
26
|
+
FAILED: "failed",
|
|
27
|
+
SKIPPED: "skipped"
|
|
28
|
+
};
|
|
29
|
+
var THOUGHT_TYPE = {
|
|
30
|
+
OBSERVATION: "observation",
|
|
31
|
+
HYPOTHESIS: "hypothesis",
|
|
32
|
+
PLAN: "plan",
|
|
33
|
+
ACTION: "action",
|
|
34
|
+
RESULT: "result",
|
|
35
|
+
REFLECTION: "reflection",
|
|
36
|
+
STUCK: "stuck",
|
|
37
|
+
BREAKTHROUGH: "breakthrough"
|
|
38
|
+
};
|
|
39
|
+
var AGENT_EVENT = {
|
|
40
|
+
// Lifecycle
|
|
41
|
+
PLUGINS_LOADED: "plugins_loaded",
|
|
42
|
+
HOOKS_LOADED: "hooks_loaded",
|
|
43
|
+
COMMANDS_LOADED: "commands_loaded",
|
|
44
|
+
MCP_SERVER_ADDED: "mcp_server_added",
|
|
45
|
+
// Execution
|
|
46
|
+
ITERATION: "iteration",
|
|
47
|
+
THOUGHT: "thought",
|
|
48
|
+
RESPONSE: "response",
|
|
49
|
+
TOOL_CALL: "tool_call",
|
|
50
|
+
TOOL_RESULT: "tool_result",
|
|
51
|
+
COMMAND_EXECUTE: "command_execute",
|
|
52
|
+
APPROVAL_NEEDED: "approval_needed",
|
|
53
|
+
TOKEN_USAGE: "token_usage",
|
|
54
|
+
// State changes
|
|
55
|
+
TARGET_SET: "target_set",
|
|
56
|
+
PHASE_CHANGE: "phase_change",
|
|
57
|
+
AGENT_SWITCH: "agent_switch",
|
|
58
|
+
PAUSED: "paused",
|
|
59
|
+
RESUMED: "resumed",
|
|
60
|
+
RESET: "reset",
|
|
61
|
+
// Discoveries
|
|
62
|
+
FINDING: "finding",
|
|
63
|
+
CREDENTIAL: "credential",
|
|
64
|
+
COMPROMISED: "compromised",
|
|
65
|
+
// Completion
|
|
66
|
+
COMPLETE: "complete",
|
|
67
|
+
REPORT: "report",
|
|
68
|
+
ERROR: "error",
|
|
69
|
+
HINT_RECEIVED: "hint_received",
|
|
70
|
+
CONTEXT_COMPACTED: "context_compacted"
|
|
71
|
+
};
|
|
72
|
+
var CLI_COMMAND = {
|
|
73
|
+
HELP: "help",
|
|
74
|
+
TARGET: "target",
|
|
75
|
+
START: "start",
|
|
76
|
+
STOP: "stop",
|
|
77
|
+
FINDINGS: "findings",
|
|
78
|
+
CLEAR: "clear",
|
|
79
|
+
EXIT: "exit"
|
|
80
|
+
};
|
|
81
|
+
var MESSAGE_TYPE = {
|
|
82
|
+
USER: "user",
|
|
83
|
+
ASSISTANT: "assistant",
|
|
84
|
+
TOOL: "tool",
|
|
85
|
+
THINKING: "thinking",
|
|
86
|
+
ERROR: "error",
|
|
87
|
+
SYSTEM: "system",
|
|
88
|
+
RESULT: "result"
|
|
89
|
+
};
|
|
90
|
+
var TOOL_NAME = {
|
|
91
|
+
// System
|
|
92
|
+
BASH: "bash",
|
|
93
|
+
READ_FILE: "read_file",
|
|
94
|
+
WRITE_FILE: "write_file",
|
|
95
|
+
LIST_DIRECTORY: "list_directory",
|
|
96
|
+
// Network
|
|
97
|
+
NMAP_SCAN: "nmap_scan",
|
|
98
|
+
TCPDUMP_CAPTURE: "tcpdump_capture",
|
|
99
|
+
// Web
|
|
100
|
+
WEB_REQUEST: "web_request",
|
|
101
|
+
DIRECTORY_BRUTEFORCE: "directory_bruteforce",
|
|
102
|
+
SQL_INJECTION: "sql_injection",
|
|
103
|
+
BROWSER_AUTOMATION: "browser_automation",
|
|
104
|
+
// Exploit
|
|
105
|
+
SEARCHSPLOIT: "searchsploit",
|
|
106
|
+
METASPLOIT: "metasploit",
|
|
107
|
+
GENERATE_PAYLOAD: "generate_payload",
|
|
108
|
+
// Credential
|
|
109
|
+
BRUTEFORCE_LOGIN: "bruteforce_login",
|
|
110
|
+
CRACK_HASH: "crack_hash",
|
|
111
|
+
DUMP_CREDENTIALS: "dump_credentials",
|
|
112
|
+
// Privilege Escalation
|
|
113
|
+
CHECK_SUDO: "check_sudo",
|
|
114
|
+
FIND_SUID: "find_suid",
|
|
115
|
+
RUN_PRIVESC_ENUM: "run_privesc_enum",
|
|
116
|
+
// Post-Exploitation
|
|
117
|
+
SETUP_TUNNEL: "setup_tunnel",
|
|
118
|
+
LATERAL_MOVEMENT: "lateral_movement",
|
|
119
|
+
// Reporting
|
|
120
|
+
REPORT_FINDING: "report_finding",
|
|
121
|
+
TAKE_SCREENSHOT: "take_screenshot"
|
|
122
|
+
};
|
|
123
|
+
var SENSITIVE_TOOLS = [
|
|
124
|
+
TOOL_NAME.WRITE_FILE,
|
|
125
|
+
TOOL_NAME.BRUTEFORCE_LOGIN,
|
|
126
|
+
TOOL_NAME.METASPLOIT,
|
|
127
|
+
TOOL_NAME.SQL_INJECTION,
|
|
128
|
+
TOOL_NAME.DUMP_CREDENTIALS,
|
|
129
|
+
TOOL_NAME.GENERATE_PAYLOAD,
|
|
130
|
+
TOOL_NAME.LATERAL_MOVEMENT
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
// src/config/constants.ts
|
|
134
|
+
var APP_NAME = "pentesting";
|
|
135
|
+
var APP_VERSION = "0.4.0";
|
|
136
|
+
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
137
|
+
var LLM_API_KEY = process.env.PENTEST_API_KEY || process.env.ANTHROPIC_API_KEY || "";
|
|
138
|
+
var LLM_BASE_URL = process.env.PENTEST_BASE_URL || void 0;
|
|
139
|
+
var LLM_MODEL = process.env.PENTEST_MODEL || "claude-sonnet-4-20250514";
|
|
140
|
+
var LLM_MAX_TOKENS = parseInt(process.env.PENTEST_MAX_TOKENS || "16384", 10);
|
|
141
|
+
var AGENT_CONFIG = {
|
|
142
|
+
maxIterations: 200,
|
|
143
|
+
maxToolCallsPerIteration: 10,
|
|
144
|
+
autoApprove: false,
|
|
145
|
+
sensitiveTools: SENSITIVE_TOOLS,
|
|
146
|
+
defaultTimeout: 6e4,
|
|
147
|
+
longRunningTimeout: 6e5,
|
|
148
|
+
stuckThreshold: 5,
|
|
149
|
+
stuckTimeThreshold: 3e5,
|
|
150
|
+
maxPhaseAttempts: 20
|
|
151
|
+
};
|
|
152
|
+
var PENTEST_PHASES = [
|
|
153
|
+
{ id: PHASE_ID.RECON, name: "Reconnaissance", description: "Information gathering" },
|
|
154
|
+
{ id: PHASE_ID.SCAN, name: "Scanning", description: "Port and service scanning" },
|
|
155
|
+
{ id: PHASE_ID.ENUM, name: "Enumeration", description: "Deep service enumeration" },
|
|
156
|
+
{ id: PHASE_ID.VULN, name: "Vulnerability Analysis", description: "Vulnerability identification" },
|
|
157
|
+
{ id: PHASE_ID.EXPLOIT, name: "Exploitation", description: "Gaining access" },
|
|
158
|
+
{ id: PHASE_ID.PRIVESC, name: "Privilege Escalation", description: "Elevating privileges" },
|
|
159
|
+
{ id: PHASE_ID.PIVOT, name: "Pivoting", description: "Lateral movement" },
|
|
160
|
+
{ id: PHASE_ID.PERSIST, name: "Persistence", description: "Maintaining access" },
|
|
161
|
+
{ id: PHASE_ID.EXFIL, name: "Data Exfiltration", description: "Data extraction" },
|
|
162
|
+
{ id: PHASE_ID.REPORT, name: "Reporting", description: "Documentation" }
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
export {
|
|
166
|
+
AGENT_STATUS,
|
|
167
|
+
PHASE_ID,
|
|
168
|
+
PHASE_STATUS,
|
|
169
|
+
THOUGHT_TYPE,
|
|
170
|
+
AGENT_EVENT,
|
|
171
|
+
CLI_COMMAND,
|
|
172
|
+
MESSAGE_TYPE,
|
|
173
|
+
TOOL_NAME,
|
|
174
|
+
APP_NAME,
|
|
175
|
+
APP_VERSION,
|
|
176
|
+
APP_DESCRIPTION,
|
|
177
|
+
LLM_API_KEY,
|
|
178
|
+
LLM_BASE_URL,
|
|
179
|
+
LLM_MODEL,
|
|
180
|
+
LLM_MAX_TOKENS,
|
|
181
|
+
AGENT_CONFIG
|
|
182
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APP_NAME,
|
|
3
|
+
APP_VERSION
|
|
4
|
+
} from "./chunk-IU6YJKJT.js";
|
|
5
|
+
|
|
6
|
+
// src/core/update/auto-update.ts
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
var UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
12
|
+
var VERSION_CACHE_FILE = join(homedir(), ".pentest", "latest_version.json");
|
|
13
|
+
function semverTuple(version) {
|
|
14
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
15
|
+
if (!match) return [0, 0, 0];
|
|
16
|
+
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
|
|
17
|
+
}
|
|
18
|
+
function compareSemver(a, b) {
|
|
19
|
+
const [a1, a2, a3] = semverTuple(a);
|
|
20
|
+
const [b1, b2, b3] = semverTuple(b);
|
|
21
|
+
if (a1 !== b1) return a1 < b1 ? -1 : 1;
|
|
22
|
+
if (a2 !== b2) return a2 < b2 ? -1 : 1;
|
|
23
|
+
if (a3 !== b3) return a3 < b3 ? -1 : 1;
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
function readVersionCache() {
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(VERSION_CACHE_FILE)) return null;
|
|
29
|
+
const data = JSON.parse(readFileSync(VERSION_CACHE_FILE, "utf-8"));
|
|
30
|
+
return data;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function writeVersionCache(info) {
|
|
36
|
+
try {
|
|
37
|
+
const dir = join(homedir(), ".pentest");
|
|
38
|
+
if (!existsSync(dir)) {
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
writeFileSync(VERSION_CACHE_FILE, JSON.stringify(info, null, 2));
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function fetchLatestVersion(packageName = APP_NAME) {
|
|
46
|
+
try {
|
|
47
|
+
const output = execSync(`npm view ${packageName} version`, {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
timeout: 1e4,
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
51
|
+
});
|
|
52
|
+
return output.trim();
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function checkForUpdate(forceCheck = false) {
|
|
58
|
+
const currentVersion = APP_VERSION;
|
|
59
|
+
const cached = readVersionCache();
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (!forceCheck && cached && now - cached.checkedAt < UPDATE_CHECK_INTERVAL) {
|
|
62
|
+
const hasUpdate2 = compareSemver(cached.version, currentVersion) > 0;
|
|
63
|
+
return {
|
|
64
|
+
hasUpdate: hasUpdate2,
|
|
65
|
+
currentVersion,
|
|
66
|
+
latestVersion: cached.version
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const latestVersion = fetchLatestVersion();
|
|
70
|
+
if (!latestVersion) {
|
|
71
|
+
return {
|
|
72
|
+
hasUpdate: false,
|
|
73
|
+
currentVersion,
|
|
74
|
+
latestVersion: null,
|
|
75
|
+
error: "Failed to fetch latest version"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
writeVersionCache({
|
|
79
|
+
version: latestVersion,
|
|
80
|
+
checkedAt: now
|
|
81
|
+
});
|
|
82
|
+
const hasUpdate = compareSemver(latestVersion, currentVersion) > 0;
|
|
83
|
+
return {
|
|
84
|
+
hasUpdate,
|
|
85
|
+
currentVersion,
|
|
86
|
+
latestVersion
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function checkForUpdateAsync() {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
setImmediate(() => {
|
|
92
|
+
resolve(checkForUpdate());
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function formatUpdateNotification(result) {
|
|
97
|
+
if (!result.hasUpdate || !result.latestVersion) return null;
|
|
98
|
+
return `
|
|
99
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
100
|
+
\u2502 \u{1F195} A new version of ${APP_NAME} is available! \u2502
|
|
101
|
+
\u2502 \u2502
|
|
102
|
+
\u2502 Current: ${result.currentVersion.padEnd(10)} Latest: ${result.latestVersion.padEnd(10)} \u2502
|
|
103
|
+
\u2502 \u2502
|
|
104
|
+
\u2502 Run: npm update -g ${APP_NAME} \u2502
|
|
105
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
106
|
+
`.trim();
|
|
107
|
+
}
|
|
108
|
+
function doUpdate() {
|
|
109
|
+
try {
|
|
110
|
+
execSync(`npm update -g ${APP_NAME}`, {
|
|
111
|
+
encoding: "utf-8",
|
|
112
|
+
timeout: 12e4,
|
|
113
|
+
stdio: "inherit"
|
|
114
|
+
});
|
|
115
|
+
return { success: true, message: `Updated ${APP_NAME} successfully!` };
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
message: `Failed to update: ${e instanceof Error ? e.message : String(e)}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
semverTuple,
|
|
126
|
+
compareSemver,
|
|
127
|
+
readVersionCache,
|
|
128
|
+
writeVersionCache,
|
|
129
|
+
fetchLatestVersion,
|
|
130
|
+
checkForUpdate,
|
|
131
|
+
checkForUpdateAsync,
|
|
132
|
+
formatUpdateNotification,
|
|
133
|
+
doUpdate
|
|
134
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import {
|
|
3
|
+
AGENT_CONFIG,
|
|
4
|
+
AGENT_EVENT,
|
|
5
|
+
AGENT_STATUS,
|
|
6
|
+
APP_DESCRIPTION,
|
|
7
|
+
APP_VERSION,
|
|
8
|
+
CLI_COMMAND,
|
|
9
|
+
LLM_API_KEY,
|
|
10
|
+
LLM_BASE_URL,
|
|
11
|
+
LLM_MAX_TOKENS,
|
|
12
|
+
LLM_MODEL,
|
|
13
|
+
MESSAGE_TYPE,
|
|
14
|
+
PHASE_ID,
|
|
15
|
+
PHASE_STATUS,
|
|
16
|
+
THOUGHT_TYPE,
|
|
17
|
+
TOOL_NAME
|
|
18
|
+
} from "./chunk-IU6YJKJT.js";
|
|
19
|
+
import {
|
|
20
|
+
__require
|
|
21
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
8
22
|
|
|
9
23
|
// src/index.tsx
|
|
10
24
|
import { render } from "ink";
|
|
@@ -206,138 +220,6 @@ Analyze your situation honestly:
|
|
|
206
220
|
|
|
207
221
|
Based on this reflection, propose 3 completely different approaches to try next.`;
|
|
208
222
|
|
|
209
|
-
// src/config/agent-constants.ts
|
|
210
|
-
var AGENT_STATUS = {
|
|
211
|
-
IDLE: "idle",
|
|
212
|
-
RUNNING: "running",
|
|
213
|
-
PAUSED: "paused",
|
|
214
|
-
STUCK: "stuck",
|
|
215
|
-
WAITING_INPUT: "waiting_input",
|
|
216
|
-
COMPLETED: "completed"
|
|
217
|
-
};
|
|
218
|
-
var PHASE_ID = {
|
|
219
|
-
RECON: "recon",
|
|
220
|
-
SCAN: "scan",
|
|
221
|
-
ENUM: "enum",
|
|
222
|
-
VULN: "vuln",
|
|
223
|
-
EXPLOIT: "exploit",
|
|
224
|
-
PRIVESC: "privesc",
|
|
225
|
-
PIVOT: "pivot",
|
|
226
|
-
PERSIST: "persist",
|
|
227
|
-
EXFIL: "exfil",
|
|
228
|
-
REPORT: "report"
|
|
229
|
-
};
|
|
230
|
-
var PHASE_STATUS = {
|
|
231
|
-
PENDING: "pending",
|
|
232
|
-
IN_PROGRESS: "in_progress",
|
|
233
|
-
COMPLETED: "completed",
|
|
234
|
-
FAILED: "failed",
|
|
235
|
-
SKIPPED: "skipped"
|
|
236
|
-
};
|
|
237
|
-
var THOUGHT_TYPE = {
|
|
238
|
-
OBSERVATION: "observation",
|
|
239
|
-
HYPOTHESIS: "hypothesis",
|
|
240
|
-
PLAN: "plan",
|
|
241
|
-
ACTION: "action",
|
|
242
|
-
RESULT: "result",
|
|
243
|
-
REFLECTION: "reflection",
|
|
244
|
-
STUCK: "stuck",
|
|
245
|
-
BREAKTHROUGH: "breakthrough"
|
|
246
|
-
};
|
|
247
|
-
var AGENT_EVENT = {
|
|
248
|
-
// Lifecycle
|
|
249
|
-
PLUGINS_LOADED: "plugins_loaded",
|
|
250
|
-
HOOKS_LOADED: "hooks_loaded",
|
|
251
|
-
COMMANDS_LOADED: "commands_loaded",
|
|
252
|
-
MCP_SERVER_ADDED: "mcp_server_added",
|
|
253
|
-
// Execution
|
|
254
|
-
ITERATION: "iteration",
|
|
255
|
-
THOUGHT: "thought",
|
|
256
|
-
RESPONSE: "response",
|
|
257
|
-
TOOL_CALL: "tool_call",
|
|
258
|
-
TOOL_RESULT: "tool_result",
|
|
259
|
-
COMMAND_EXECUTE: "command_execute",
|
|
260
|
-
APPROVAL_NEEDED: "approval_needed",
|
|
261
|
-
TOKEN_USAGE: "token_usage",
|
|
262
|
-
// State changes
|
|
263
|
-
TARGET_SET: "target_set",
|
|
264
|
-
PHASE_CHANGE: "phase_change",
|
|
265
|
-
AGENT_SWITCH: "agent_switch",
|
|
266
|
-
PAUSED: "paused",
|
|
267
|
-
RESUMED: "resumed",
|
|
268
|
-
RESET: "reset",
|
|
269
|
-
// Discoveries
|
|
270
|
-
FINDING: "finding",
|
|
271
|
-
CREDENTIAL: "credential",
|
|
272
|
-
COMPROMISED: "compromised",
|
|
273
|
-
// Completion
|
|
274
|
-
COMPLETE: "complete",
|
|
275
|
-
REPORT: "report",
|
|
276
|
-
ERROR: "error",
|
|
277
|
-
HINT_RECEIVED: "hint_received",
|
|
278
|
-
CONTEXT_COMPACTED: "context_compacted"
|
|
279
|
-
};
|
|
280
|
-
var CLI_COMMAND = {
|
|
281
|
-
HELP: "help",
|
|
282
|
-
TARGET: "target",
|
|
283
|
-
START: "start",
|
|
284
|
-
STOP: "stop",
|
|
285
|
-
FINDINGS: "findings",
|
|
286
|
-
CLEAR: "clear",
|
|
287
|
-
EXIT: "exit"
|
|
288
|
-
};
|
|
289
|
-
var MESSAGE_TYPE = {
|
|
290
|
-
USER: "user",
|
|
291
|
-
ASSISTANT: "assistant",
|
|
292
|
-
TOOL: "tool",
|
|
293
|
-
THINKING: "thinking",
|
|
294
|
-
ERROR: "error",
|
|
295
|
-
SYSTEM: "system",
|
|
296
|
-
RESULT: "result"
|
|
297
|
-
};
|
|
298
|
-
var TOOL_NAME = {
|
|
299
|
-
// System
|
|
300
|
-
BASH: "bash",
|
|
301
|
-
READ_FILE: "read_file",
|
|
302
|
-
WRITE_FILE: "write_file",
|
|
303
|
-
LIST_DIRECTORY: "list_directory",
|
|
304
|
-
// Network
|
|
305
|
-
NMAP_SCAN: "nmap_scan",
|
|
306
|
-
TCPDUMP_CAPTURE: "tcpdump_capture",
|
|
307
|
-
// Web
|
|
308
|
-
WEB_REQUEST: "web_request",
|
|
309
|
-
DIRECTORY_BRUTEFORCE: "directory_bruteforce",
|
|
310
|
-
SQL_INJECTION: "sql_injection",
|
|
311
|
-
BROWSER_AUTOMATION: "browser_automation",
|
|
312
|
-
// Exploit
|
|
313
|
-
SEARCHSPLOIT: "searchsploit",
|
|
314
|
-
METASPLOIT: "metasploit",
|
|
315
|
-
GENERATE_PAYLOAD: "generate_payload",
|
|
316
|
-
// Credential
|
|
317
|
-
BRUTEFORCE_LOGIN: "bruteforce_login",
|
|
318
|
-
CRACK_HASH: "crack_hash",
|
|
319
|
-
DUMP_CREDENTIALS: "dump_credentials",
|
|
320
|
-
// Privilege Escalation
|
|
321
|
-
CHECK_SUDO: "check_sudo",
|
|
322
|
-
FIND_SUID: "find_suid",
|
|
323
|
-
RUN_PRIVESC_ENUM: "run_privesc_enum",
|
|
324
|
-
// Post-Exploitation
|
|
325
|
-
SETUP_TUNNEL: "setup_tunnel",
|
|
326
|
-
LATERAL_MOVEMENT: "lateral_movement",
|
|
327
|
-
// Reporting
|
|
328
|
-
REPORT_FINDING: "report_finding",
|
|
329
|
-
TAKE_SCREENSHOT: "take_screenshot"
|
|
330
|
-
};
|
|
331
|
-
var SENSITIVE_TOOLS = [
|
|
332
|
-
TOOL_NAME.WRITE_FILE,
|
|
333
|
-
TOOL_NAME.BRUTEFORCE_LOGIN,
|
|
334
|
-
TOOL_NAME.METASPLOIT,
|
|
335
|
-
TOOL_NAME.SQL_INJECTION,
|
|
336
|
-
TOOL_NAME.DUMP_CREDENTIALS,
|
|
337
|
-
TOOL_NAME.GENERATE_PAYLOAD,
|
|
338
|
-
TOOL_NAME.LATERAL_MOVEMENT
|
|
339
|
-
];
|
|
340
|
-
|
|
341
223
|
// src/core/tools/tool-definitions.ts
|
|
342
224
|
var SYSTEM_TOOLS = [
|
|
343
225
|
{
|
|
@@ -1381,37 +1263,6 @@ const { chromium } = require('playwright');
|
|
|
1381
1263
|
return executeBash(`script -q /dev/null -c "cat" | tee "${filename || "terminal.txt"}"`);
|
|
1382
1264
|
}
|
|
1383
1265
|
|
|
1384
|
-
// src/config/constants.ts
|
|
1385
|
-
var APP_VERSION = "0.3.2";
|
|
1386
|
-
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
1387
|
-
var LLM_API_KEY = process.env.PENTEST_API_KEY || process.env.ANTHROPIC_API_KEY || "";
|
|
1388
|
-
var LLM_BASE_URL = process.env.PENTEST_BASE_URL || void 0;
|
|
1389
|
-
var LLM_MODEL = process.env.PENTEST_MODEL || "claude-sonnet-4-20250514";
|
|
1390
|
-
var LLM_MAX_TOKENS = parseInt(process.env.PENTEST_MAX_TOKENS || "16384", 10);
|
|
1391
|
-
var AGENT_CONFIG = {
|
|
1392
|
-
maxIterations: 200,
|
|
1393
|
-
maxToolCallsPerIteration: 10,
|
|
1394
|
-
autoApprove: false,
|
|
1395
|
-
sensitiveTools: SENSITIVE_TOOLS,
|
|
1396
|
-
defaultTimeout: 6e4,
|
|
1397
|
-
longRunningTimeout: 6e5,
|
|
1398
|
-
stuckThreshold: 5,
|
|
1399
|
-
stuckTimeThreshold: 3e5,
|
|
1400
|
-
maxPhaseAttempts: 20
|
|
1401
|
-
};
|
|
1402
|
-
var PENTEST_PHASES = [
|
|
1403
|
-
{ id: PHASE_ID.RECON, name: "Reconnaissance", description: "Information gathering" },
|
|
1404
|
-
{ id: PHASE_ID.SCAN, name: "Scanning", description: "Port and service scanning" },
|
|
1405
|
-
{ id: PHASE_ID.ENUM, name: "Enumeration", description: "Deep service enumeration" },
|
|
1406
|
-
{ id: PHASE_ID.VULN, name: "Vulnerability Analysis", description: "Vulnerability identification" },
|
|
1407
|
-
{ id: PHASE_ID.EXPLOIT, name: "Exploitation", description: "Gaining access" },
|
|
1408
|
-
{ id: PHASE_ID.PRIVESC, name: "Privilege Escalation", description: "Elevating privileges" },
|
|
1409
|
-
{ id: PHASE_ID.PIVOT, name: "Pivoting", description: "Lateral movement" },
|
|
1410
|
-
{ id: PHASE_ID.PERSIST, name: "Persistence", description: "Maintaining access" },
|
|
1411
|
-
{ id: PHASE_ID.EXFIL, name: "Data Exfiltration", description: "Data extraction" },
|
|
1412
|
-
{ id: PHASE_ID.REPORT, name: "Reporting", description: "Documentation" }
|
|
1413
|
-
];
|
|
1414
|
-
|
|
1415
1266
|
// src/core/hooks/hook-executor.ts
|
|
1416
1267
|
import { spawn as spawn2 } from "child_process";
|
|
1417
1268
|
import * as fs2 from "fs/promises";
|
|
@@ -5033,6 +4884,21 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
5033
4884
|
setCheckpointCount(contextManagerRef.current?.getCheckpoints().length || 0);
|
|
5034
4885
|
}
|
|
5035
4886
|
});
|
|
4887
|
+
import("./auto-update-H72IBVEQ.js").then(({ checkForUpdateAsync, formatUpdateNotification }) => {
|
|
4888
|
+
checkForUpdateAsync().then((result) => {
|
|
4889
|
+
if (result.hasUpdate) {
|
|
4890
|
+
const notification = formatUpdateNotification(result);
|
|
4891
|
+
if (notification) {
|
|
4892
|
+
setMessages((prev) => [...prev, {
|
|
4893
|
+
id: "update-notification",
|
|
4894
|
+
type: MESSAGE_TYPE.SYSTEM,
|
|
4895
|
+
content: notification,
|
|
4896
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4897
|
+
}]);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
});
|
|
4901
|
+
});
|
|
5036
4902
|
}, []);
|
|
5037
4903
|
const startTimeRef = useRef(0);
|
|
5038
4904
|
const timerRef = useRef(null);
|
|
@@ -5196,6 +5062,12 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
5196
5062
|
/compact Compact context
|
|
5197
5063
|
/sessions List saved sessions
|
|
5198
5064
|
/resume [id] Resume session
|
|
5065
|
+
/replay Show session recordings
|
|
5066
|
+
|
|
5067
|
+
\u2500\u2500 Skills & Extras \u2500\u2500
|
|
5068
|
+
/skills List available skills
|
|
5069
|
+
/update Check for updates
|
|
5070
|
+
/update now Install update
|
|
5199
5071
|
|
|
5200
5072
|
\u2500\u2500 Findings \u2500\u2500
|
|
5201
5073
|
/findings Show findings
|
|
@@ -5423,6 +5295,63 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
5423
5295
|
Tokens: ${tokenUsage.total.toLocaleString()}
|
|
5424
5296
|
Checkpoints: ${checkpointCount}`);
|
|
5425
5297
|
return;
|
|
5298
|
+
case "skills":
|
|
5299
|
+
try {
|
|
5300
|
+
const { getSkillManager } = await import("./skill-2AON6M2V.js");
|
|
5301
|
+
const skillMgr = getSkillManager();
|
|
5302
|
+
const skills = skillMgr.list();
|
|
5303
|
+
if (skills.length === 0) {
|
|
5304
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "No skills found. Add SKILL.md files to ~/.pentest/skills/");
|
|
5305
|
+
} else {
|
|
5306
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F4DA} ${skills.length} Skills:`);
|
|
5307
|
+
skills.forEach((s) => {
|
|
5308
|
+
addMessage(MESSAGE_TYPE.SYSTEM, ` ${s.type === "flow" ? "\u{1F504}" : "\u{1F4DD}"} ${s.name}: ${s.description}`);
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
5311
|
+
} catch (e) {
|
|
5312
|
+
addMessage(MESSAGE_TYPE.ERROR, `Skill error: ${e instanceof Error ? e.message : String(e)}`);
|
|
5313
|
+
}
|
|
5314
|
+
return;
|
|
5315
|
+
case "replay":
|
|
5316
|
+
try {
|
|
5317
|
+
const { findRecentWireFiles, getReplaySummary, parseWireFile } = await import("./replay-6WU2ANWJ.js");
|
|
5318
|
+
const wireFiles = findRecentWireFiles(sessionDirRef.current);
|
|
5319
|
+
if (wireFiles.length === 0) {
|
|
5320
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "No session recordings found");
|
|
5321
|
+
} else {
|
|
5322
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F4FC} ${wireFiles.length} Session Recordings:`);
|
|
5323
|
+
for (const file of wireFiles.slice(0, 5)) {
|
|
5324
|
+
const events = parseWireFile(file);
|
|
5325
|
+
const summary = getReplaySummary(events);
|
|
5326
|
+
const name = file.split("/").pop()?.replace(".jsonl", "") || "unknown";
|
|
5327
|
+
addMessage(MESSAGE_TYPE.SYSTEM, ` ${name}: ${summary.toolCalls} tools, ${summary.findings} findings, ${summary.duration.toFixed(1)}s`);
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
} catch (e) {
|
|
5331
|
+
addMessage(MESSAGE_TYPE.ERROR, `Replay error: ${e instanceof Error ? e.message : String(e)}`);
|
|
5332
|
+
}
|
|
5333
|
+
return;
|
|
5334
|
+
case "update":
|
|
5335
|
+
try {
|
|
5336
|
+
const { checkForUpdate, formatUpdateNotification, doUpdate } = await import("./update-DNXSBIOM.js");
|
|
5337
|
+
const result = checkForUpdate(true);
|
|
5338
|
+
if (result.hasUpdate) {
|
|
5339
|
+
const notification = formatUpdateNotification(result);
|
|
5340
|
+
if (notification) addMessage(MESSAGE_TYPE.SYSTEM, notification);
|
|
5341
|
+
if (args[0] === "now") {
|
|
5342
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "\u{1F504} Updating...");
|
|
5343
|
+
const updateResult = doUpdate();
|
|
5344
|
+
addMessage(updateResult.success ? MESSAGE_TYPE.SYSTEM : MESSAGE_TYPE.ERROR, updateResult.message);
|
|
5345
|
+
} else {
|
|
5346
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "Run /update now to update");
|
|
5347
|
+
}
|
|
5348
|
+
} else {
|
|
5349
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u2713 You have the latest version (${result.currentVersion})`);
|
|
5350
|
+
}
|
|
5351
|
+
} catch (e) {
|
|
5352
|
+
addMessage(MESSAGE_TYPE.ERROR, `Update error: ${e instanceof Error ? e.message : String(e)}`);
|
|
5353
|
+
}
|
|
5354
|
+
return;
|
|
5426
5355
|
case "think":
|
|
5427
5356
|
addMessage(MESSAGE_TYPE.SYSTEM, "\u{1F9E0} Thinking mode: Extended reasoning enabled");
|
|
5428
5357
|
return;
|
|
@@ -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
|
+
};
|