opencode-api-security-testing 4.0.1 → 5.2.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/SKILL.md +6 -0
- package/agents/api-cyber-supervisor.md +5 -3
- package/package.json +48 -47
- package/postinstall.mjs +243 -39
- package/src/index.ts +318 -25
- package/src/tools/endpoint-discover.ts +325 -0
- package/src/tools/report-generator.ts +355 -0
- package/src/utils/env-checker.ts +264 -0
package/SKILL.md
CHANGED
|
@@ -69,6 +69,12 @@ tools:
|
|
|
69
69
|
- name: report_generator
|
|
70
70
|
description: "Compile evidence-based security report."
|
|
71
71
|
usage: "During 报告 to generate deliverables."
|
|
72
|
+
- name: endpoint_discover
|
|
73
|
+
description: "Auto-discover API endpoints from JS, HTML, sitemap, robots.txt, and common paths."
|
|
74
|
+
usage: "During 侦察 to automatically map the attack surface without manual input."
|
|
75
|
+
- name: env_checker
|
|
76
|
+
description: "Auto-detect and install missing dependencies (Python, pip, Playwright, etc.)."
|
|
77
|
+
usage: "Runs automatically during postinstall; can be triggered manually to fix environment issues."
|
|
72
78
|
notes:
|
|
73
79
|
- "All tools must be used within their defined phases; avoid cross-phase misuse."
|
|
74
80
|
- "Preserve evidence with timestamps; ensure traceability for audits."
|
|
@@ -31,6 +31,7 @@ color: "#FF5733"
|
|
|
31
31
|
|
|
32
32
|
| 工具 | 用途 | 场景 |
|
|
33
33
|
|------|------|------|
|
|
34
|
+
| endpoint_discover | 端点自动发现 | 从 JS/HTML/Sitemap 提取 |
|
|
34
35
|
| api_security_scan | 完整扫描 | 全面测试 |
|
|
35
36
|
| api_fuzz_test | 模糊测试 | 发现未知端点 |
|
|
36
37
|
| browser_collect | 浏览器采集 | SPA 应用 |
|
|
@@ -40,13 +41,14 @@ color: "#FF5733"
|
|
|
40
41
|
| cloud_storage_test | 云存储测试 | OSS/S3 |
|
|
41
42
|
| idor_test | IDOR 测试 | 越权漏洞 |
|
|
42
43
|
| sqli_test | SQLi 测试 | 注入漏洞 |
|
|
44
|
+
| report_generate | 报告生成 | Markdown/HTML/JSON 格式 |
|
|
43
45
|
|
|
44
46
|
## 测试流程
|
|
45
47
|
|
|
46
48
|
### Phase 1: 侦察
|
|
47
|
-
1.
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
49
|
+
1. endpoint_discover 自动发现所有 API 端点
|
|
50
|
+
2. browser_collect 采集动态端点
|
|
51
|
+
3. js_parse 分析 JS 文件
|
|
50
52
|
|
|
51
53
|
### Phase 2: 分析
|
|
52
54
|
1. 识别技术栈
|
package/package.json
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "opencode-api-security-testing",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "API Security Testing Plugin for OpenCode - Automated vulnerability scanning and penetration testing",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "src/index.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"src/",
|
|
9
|
-
"agents/",
|
|
10
|
-
"core/",
|
|
11
|
-
"references/",
|
|
12
|
-
"SKILL.md",
|
|
13
|
-
"postinstall.mjs",
|
|
14
|
-
"preuninstall.mjs"
|
|
15
|
-
],
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"opencode
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"@opencode-ai/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"@opencode-ai/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-api-security-testing",
|
|
3
|
+
"version": "5.2.0",
|
|
4
|
+
"description": "API Security Testing Plugin for OpenCode - Automated vulnerability scanning and penetration testing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src/",
|
|
9
|
+
"agents/",
|
|
10
|
+
"core/",
|
|
11
|
+
"references/",
|
|
12
|
+
"SKILL.md",
|
|
13
|
+
"postinstall.mjs",
|
|
14
|
+
"preuninstall.mjs"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node postinstall.mjs",
|
|
18
|
+
"preuninstall": "node preuninstall.mjs",
|
|
19
|
+
"init": "node init.mjs"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"opencode",
|
|
23
|
+
"opencode-plugin",
|
|
24
|
+
"security",
|
|
25
|
+
"api-security",
|
|
26
|
+
"pentest",
|
|
27
|
+
"vulnerability-scanning"
|
|
28
|
+
],
|
|
29
|
+
"author": "steveopen1",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/steveopen1/skill-play"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/steveopen1/skill-play/tree/main/agent-plugins/OPENCODE/api-security-testing",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@opencode-ai/plugin": "^1.1.19",
|
|
38
|
+
"@opencode-ai/sdk": "^1.1.19"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@opencode-ai/plugin": "^1.1.19",
|
|
42
|
+
"@opencode-ai/sdk": "^1.1.19"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^25.5.2",
|
|
46
|
+
"typescript": "^6.0.2"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/postinstall.mjs
CHANGED
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
* postinstall.mjs - API Security Testing Plugin
|
|
5
5
|
*
|
|
6
6
|
* Installs:
|
|
7
|
-
* 1. agents to ~/.
|
|
8
|
-
* 2.
|
|
7
|
+
* 1. agents to ~/.claude/agents/ (oh-my-opencode discovery path)
|
|
8
|
+
* 2. agents to ~/.config/opencode/agents/ (OpenCode native discovery path)
|
|
9
|
+
* 3. SKILL.md and references to ~/.config/opencode/skills/api-security-testing/
|
|
10
|
+
* 4. Auto-detects and installs Python dependencies (requests, beautifulsoup4, playwright)
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
13
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
12
14
|
import { join } from "node:path";
|
|
13
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { execSync } from "node:child_process";
|
|
17
|
+
import { platform } from "node:os";
|
|
14
18
|
|
|
15
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
20
|
const __dirname = join(__filename, "..");
|
|
@@ -20,29 +24,241 @@ function getOpencodeBaseDir() {
|
|
|
20
24
|
return join(home, ".config", "opencode");
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
function getClaudeBaseDir() {
|
|
28
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/root";
|
|
29
|
+
return join(home, ".claude");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function copyDirRecursive(src, dest) {
|
|
33
|
+
if (!existsSync(dest)) {
|
|
34
|
+
mkdirSync(dest, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
const items = readdirSync(src, { withFileTypes: true });
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const srcPath = join(src, item.name);
|
|
40
|
+
const destPath = join(dest, item.name);
|
|
41
|
+
try {
|
|
42
|
+
if (item.isDirectory()) {
|
|
43
|
+
copyDirRecursive(srcPath, destPath);
|
|
44
|
+
} else {
|
|
45
|
+
copyFileSync(srcPath, destPath);
|
|
46
|
+
count++;
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(` ✗ ${item.name}: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return count;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run shell command safely
|
|
57
|
+
*/
|
|
58
|
+
function runCommand(cmd, timeout = 30000) {
|
|
59
|
+
try {
|
|
60
|
+
const output = execSync(cmd, {
|
|
61
|
+
encoding: "utf-8",
|
|
62
|
+
timeout,
|
|
63
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
64
|
+
});
|
|
65
|
+
return { success: true, output: output.trim(), error: "" };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
output: error.stdout || "",
|
|
70
|
+
error: error.stderr || error.message || "Unknown error"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check Python availability
|
|
77
|
+
*/
|
|
78
|
+
function checkPython() {
|
|
79
|
+
for (const cmd of ["python3 --version", "python --version"]) {
|
|
80
|
+
const result = runCommand(cmd, 5000);
|
|
81
|
+
if (result.success) {
|
|
82
|
+
const versionMatch = result.output.match(/Python\s+(\d+\.\d+\.\d+)/i) ||
|
|
83
|
+
result.error.match(/Python\s+(\d+\.\d+\.\d+)/i);
|
|
84
|
+
return { available: true, version: versionMatch ? versionMatch[1] : "unknown", cmd: cmd.split(" ")[0] };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { available: false, version: null, cmd: null };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if pip is available
|
|
92
|
+
*/
|
|
93
|
+
function checkPip(pythonCmd) {
|
|
94
|
+
const cmds = [
|
|
95
|
+
`${pythonCmd} -m pip --version`,
|
|
96
|
+
"pip3 --version",
|
|
97
|
+
"pip --version"
|
|
98
|
+
];
|
|
99
|
+
for (const cmd of cmds) {
|
|
100
|
+
const result = runCommand(cmd, 5000);
|
|
101
|
+
if (result.success) return cmd.includes("-m pip") ? `${pythonCmd} -m pip` : cmd.split(" ")[0];
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if a Python package is installed
|
|
108
|
+
*/
|
|
109
|
+
function checkPythonPackage(pythonCmd, packageName) {
|
|
110
|
+
const result = runCommand(`${pythonCmd} -c "import ${packageName}"`, 5000);
|
|
111
|
+
return result.success;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Install a Python package
|
|
116
|
+
*/
|
|
117
|
+
function installPythonPackage(pipCmd, packageName) {
|
|
118
|
+
const cmds = [
|
|
119
|
+
`${pipCmd} install -q ${packageName}`,
|
|
120
|
+
`${pipCmd} install --user -q ${packageName}`
|
|
121
|
+
];
|
|
122
|
+
for (const cmd of cmds) {
|
|
123
|
+
const result = runCommand(cmd, 120000);
|
|
124
|
+
if (result.success) return { success: true, error: "" };
|
|
125
|
+
}
|
|
126
|
+
return { success: false, error: `Failed to install ${packageName}` };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if Playwright browsers are installed
|
|
131
|
+
*/
|
|
132
|
+
function checkPlaywright(pythonCmd) {
|
|
133
|
+
const hasPackage = checkPythonPackage(pythonCmd, "playwright");
|
|
134
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "/root";
|
|
135
|
+
const pwCachePath = join(homeDir, ".cache", "ms-playwright");
|
|
136
|
+
const browsersInstalled = existsSync(pwCachePath);
|
|
137
|
+
return { installed: hasPackage, browsersInstalled };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Install Playwright
|
|
142
|
+
*/
|
|
143
|
+
function installPlaywright(pythonCmd) {
|
|
144
|
+
console.log(" Installing Playwright Python package...");
|
|
145
|
+
const pipCmd = checkPip(pythonCmd);
|
|
146
|
+
if (!pipCmd) return { success: false, error: "pip not found" };
|
|
147
|
+
|
|
148
|
+
const pkgResult = installPythonPackage(pipCmd, "playwright");
|
|
149
|
+
if (!pkgResult.success) return pkgResult;
|
|
150
|
+
|
|
151
|
+
console.log(" Installing Playwright browsers (chromium)...");
|
|
152
|
+
const browserResult = runCommand(`${pythonCmd} -m playwright install chromium`, 300000);
|
|
153
|
+
if (browserResult.success) return { success: true, error: "" };
|
|
154
|
+
|
|
155
|
+
return { success: false, error: browserResult.error };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Environment detection and auto-installation
|
|
160
|
+
*/
|
|
161
|
+
function checkAndFixEnvironment() {
|
|
162
|
+
console.log("\n[env-check] Starting environment detection...");
|
|
163
|
+
|
|
164
|
+
const pythonCheck = checkPython();
|
|
165
|
+
if (!pythonCheck.available) {
|
|
166
|
+
console.log(" ⚠ Python 3 not found. Python tools will not work.");
|
|
167
|
+
console.log(" → Install Python 3.8+ from https://python.org");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(` ✓ Python ${pythonCheck.version} detected`);
|
|
172
|
+
const pythonCmd = pythonCheck.cmd;
|
|
173
|
+
|
|
174
|
+
const pipCmd = checkPip(pythonCmd);
|
|
175
|
+
if (!pipCmd) {
|
|
176
|
+
console.log(" ⚠ pip not found. Cannot auto-install Python packages.");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
console.log(` ✓ pip detected`);
|
|
180
|
+
|
|
181
|
+
// Required packages
|
|
182
|
+
const requiredPackages = ["requests", "beautifulsoup4", "urllib3"];
|
|
183
|
+
for (const pkg of requiredPackages) {
|
|
184
|
+
if (!checkPythonPackage(pythonCmd, pkg)) {
|
|
185
|
+
console.log(` Installing ${pkg}...`);
|
|
186
|
+
const result = installPythonPackage(pipCmd, pkg);
|
|
187
|
+
if (result.success) {
|
|
188
|
+
console.log(` ✓ ${pkg} installed`);
|
|
189
|
+
} else {
|
|
190
|
+
console.log(` ✗ Failed to install ${pkg}`);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
console.log(` ✓ ${pkg} already installed`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check Playwright
|
|
198
|
+
const pwCheck = checkPlaywright(pythonCmd);
|
|
199
|
+
if (!pwCheck.installed || !pwCheck.browsersInstalled) {
|
|
200
|
+
console.log(" Playwright not fully installed, installing...");
|
|
201
|
+
const result = installPlaywright(pythonCmd);
|
|
202
|
+
if (result.success) {
|
|
203
|
+
console.log(" ✓ Playwright + browsers installed");
|
|
204
|
+
} else {
|
|
205
|
+
console.log(` ⚠ Playwright installation failed: ${result.error}`);
|
|
206
|
+
console.log(" → browser_collect tool will not work until Playwright is installed");
|
|
207
|
+
console.log(` → Manual fix: ${pythonCmd} -m pip install playwright && ${pythonCmd} -m playwright install chromium`);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
console.log(" ✓ Playwright already installed");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log("[env-check] Environment check complete\n");
|
|
214
|
+
}
|
|
215
|
+
|
|
23
216
|
function main() {
|
|
24
217
|
const packageRoot = __dirname;
|
|
25
218
|
const agentsSourceDir = join(packageRoot, "agents");
|
|
26
|
-
const
|
|
27
|
-
const
|
|
219
|
+
const opencodeBaseDir = getOpencodeBaseDir();
|
|
220
|
+
const claudeBaseDir = getClaudeBaseDir();
|
|
221
|
+
const opencodeAgentsDir = join(opencodeBaseDir, "agents");
|
|
222
|
+
const claudeAgentsDir = join(claudeBaseDir, "agents");
|
|
223
|
+
const skillTargetDir = join(opencodeBaseDir, "skills", "api-security-testing");
|
|
28
224
|
|
|
29
225
|
console.log("[api-security-testing] Installing...");
|
|
30
226
|
console.log(` Package root: ${packageRoot}`);
|
|
227
|
+
console.log(` Platform: ${platform()}`);
|
|
31
228
|
|
|
32
229
|
let totalInstalled = 0;
|
|
33
230
|
let totalFailed = 0;
|
|
34
231
|
|
|
35
|
-
// 1. Install agents
|
|
36
|
-
console.log("\n[1/
|
|
232
|
+
// 1. Install agents to BOTH locations
|
|
233
|
+
console.log("\n[1/5] Installing agents to ~/.claude/agents/ (oh-my-opencode)...");
|
|
234
|
+
if (existsSync(agentsSourceDir)) {
|
|
235
|
+
if (!existsSync(claudeAgentsDir)) {
|
|
236
|
+
mkdirSync(claudeAgentsDir, { recursive: true });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const files = readdirSync(agentsSourceDir).filter(f => f.endsWith(".md"));
|
|
240
|
+
for (const file of files) {
|
|
241
|
+
try {
|
|
242
|
+
copyFileSync(join(agentsSourceDir, file), join(claudeAgentsDir, file));
|
|
243
|
+
console.log(` ✓ ${file}`);
|
|
244
|
+
totalInstalled++;
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error(` ✗ ${file}: ${err.message}`);
|
|
247
|
+
totalFailed++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log("\n[2/5] Installing agents to ~/.config/opencode/agents/ (OpenCode native)...");
|
|
37
253
|
if (existsSync(agentsSourceDir)) {
|
|
38
|
-
if (!existsSync(
|
|
39
|
-
mkdirSync(
|
|
254
|
+
if (!existsSync(opencodeAgentsDir)) {
|
|
255
|
+
mkdirSync(opencodeAgentsDir, { recursive: true });
|
|
40
256
|
}
|
|
41
257
|
|
|
42
258
|
const files = readdirSync(agentsSourceDir).filter(f => f.endsWith(".md"));
|
|
43
259
|
for (const file of files) {
|
|
44
260
|
try {
|
|
45
|
-
copyFileSync(join(agentsSourceDir, file), join(
|
|
261
|
+
copyFileSync(join(agentsSourceDir, file), join(opencodeAgentsDir, file));
|
|
46
262
|
console.log(` ✓ ${file}`);
|
|
47
263
|
totalInstalled++;
|
|
48
264
|
} catch (err) {
|
|
@@ -52,8 +268,8 @@ function main() {
|
|
|
52
268
|
}
|
|
53
269
|
}
|
|
54
270
|
|
|
55
|
-
//
|
|
56
|
-
console.log("\n[
|
|
271
|
+
// 3. Install SKILL.md
|
|
272
|
+
console.log("\n[3/5] Installing SKILL.md...");
|
|
57
273
|
const skillSource = join(packageRoot, "SKILL.md");
|
|
58
274
|
if (existsSync(skillSource)) {
|
|
59
275
|
if (!existsSync(skillTargetDir)) {
|
|
@@ -69,46 +285,34 @@ function main() {
|
|
|
69
285
|
}
|
|
70
286
|
}
|
|
71
287
|
|
|
72
|
-
//
|
|
73
|
-
console.log("\n[
|
|
288
|
+
// 4. Install references
|
|
289
|
+
console.log("\n[4/5] Installing references...");
|
|
74
290
|
const refsSourceDir = join(packageRoot, "references");
|
|
75
291
|
const refsTargetDir = join(skillTargetDir, "references");
|
|
76
292
|
if (existsSync(refsSourceDir)) {
|
|
77
|
-
if (!existsSync(refsTargetDir)) {
|
|
78
|
-
mkdirSync(refsTargetDir, { recursive: true });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function copyDir(src, dest) {
|
|
82
|
-
const items = readdirSync(src);
|
|
83
|
-
for (const item of items) {
|
|
84
|
-
const srcPath = join(src, item);
|
|
85
|
-
const destPath = join(dest, item);
|
|
86
|
-
try {
|
|
87
|
-
copyFileSync(srcPath, destPath);
|
|
88
|
-
totalInstalled++;
|
|
89
|
-
} catch {
|
|
90
|
-
if (existsSync(srcPath) && !srcPath.endsWith(".md")) {
|
|
91
|
-
mkdirSync(destPath, { recursive: true });
|
|
92
|
-
copyDir(srcPath, destPath);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
293
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
294
|
+
const count = copyDirRecursive(refsSourceDir, refsTargetDir);
|
|
295
|
+
totalInstalled += count;
|
|
296
|
+
console.log(` ✓ references/ (${count} files)`);
|
|
102
297
|
} catch (err) {
|
|
103
298
|
console.error(` ✗ references/: ${err.message}`);
|
|
104
299
|
totalFailed++;
|
|
105
300
|
}
|
|
106
301
|
}
|
|
107
302
|
|
|
303
|
+
// 5. Environment detection and auto-install
|
|
304
|
+
console.log("\n[5/5] Detecting environment and installing dependencies...");
|
|
305
|
+
try {
|
|
306
|
+
checkAndFixEnvironment();
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.log(` ⚠ Environment check failed: ${err.message}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
108
311
|
console.log(`\n========================================`);
|
|
109
312
|
if (totalFailed === 0) {
|
|
110
313
|
console.log(`✓ Installed ${totalInstalled} file(s)`);
|
|
111
|
-
console.log(`\nAgents: ${
|
|
314
|
+
console.log(`\nAgents (oh-my-opencode): ${claudeAgentsDir}`);
|
|
315
|
+
console.log(`Agents (OpenCode native): ${opencodeAgentsDir}`);
|
|
112
316
|
console.log(`Skill: ${skillTargetDir}`);
|
|
113
317
|
console.log(`\n⚠️ IMPORTANT: Restart OpenCode to discover new agents`);
|
|
114
318
|
} else {
|