@jshookmcp/jshook 0.2.8 → 0.2.9
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/README.md +36 -5
- package/README.zh.md +36 -5
- package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-BNk-EoBt.mjs} +3 -3
- package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-Cq8q01kp.mjs} +5 -5
- package/dist/ConsoleMonitor-CPVQW1Y-.mjs +2201 -0
- package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-BNPxu0RH.mjs} +1 -1
- package/dist/DetailedDataManager-BQQcxh64.mjs +217 -0
- package/dist/EventBus-DgPmwpeu.mjs +141 -0
- package/dist/EvidenceGraphBridge-SFesNera.mjs +153 -0
- package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-CWYgw0YW.mjs} +13 -6
- package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-gzWtkKuf.mjs} +1 -1
- package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-B9gZCdFP.mjs} +3 -3
- package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-BLDH0dCv.mjs} +4 -4
- package/dist/HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs +639 -0
- package/dist/InstrumentationSession-CvPC7Jwy.mjs +244 -0
- package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CbVdCIJF.mjs} +3 -3
- package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-BsDZbLYm.mjs} +81 -78
- package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-Bcpml6II.mjs} +44 -18
- package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-dZtA1ZGn.mjs} +14 -53
- package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-B-FjA2mJ.mjs} +1 -1
- package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-D1lzJ_VG.mjs} +2 -2
- package/dist/PageController-Bqm2kZ_X.mjs +417 -0
- package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-BOhyVsjx.mjs} +4 -4
- package/dist/PrerequisiteError-Dl33Svkz.mjs +20 -0
- package/dist/ResponseBuilder-D3iFYx2N.mjs +143 -0
- package/dist/ReverseEvidenceGraph-Dlsk94LC.mjs +269 -0
- package/dist/ScriptManager-aHHq0X7U.mjs +3000 -0
- package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-CqdIFlQl.mjs} +2 -2
- package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-DhFaPvRO.mjs} +3 -3
- package/dist/ToolCatalog-C0JGZoOm.mjs +582 -0
- package/dist/ToolError-jh9whhMd.mjs +15 -0
- package/dist/ToolProbe-oC7aPrkv.mjs +45 -0
- package/dist/ToolRegistry-BjaF4oNz.mjs +131 -0
- package/dist/ToolRouter.policy-BWV67ZK-.mjs +304 -0
- package/dist/TraceRecorder-DgxyVbdQ.mjs +519 -0
- package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-CePkipZY.mjs} +1 -1
- package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-BvKs-gxc.mjs} +2 -2
- package/dist/WorkflowEngine-CuvkZtWu.mjs +598 -0
- package/dist/analysis-CL9uACt9.mjs +463 -0
- package/dist/antidebug-CqDTB_uk.mjs +1081 -0
- package/dist/artifactRetention-CFEprwPw.mjs +591 -0
- package/dist/artifacts-Bk2-_uPq.mjs +59 -0
- package/dist/betterSqlite3-0pqusHHH.mjs +74 -0
- package/dist/binary-instrument-CXfpx6fT.mjs +979 -0
- package/dist/bind-helpers-xFfRF-qm.mjs +22 -0
- package/dist/boringssl-inspector-BH2D3VKc.mjs +180 -0
- package/dist/browser-BpOr5PEx.mjs +4082 -0
- package/dist/concurrency-Bt0yv1kJ.mjs +41 -0
- package/dist/{constants-CCvsN80K.mjs → constants-B0OANIBL.mjs} +88 -46
- package/dist/coordination-qUbyF8KU.mjs +259 -0
- package/dist/debugger-gnKxRSN0.mjs +1271 -0
- package/dist/definitions-6M-eejaT.mjs +53 -0
- package/dist/definitions-B18eyf0B.mjs +18 -0
- package/dist/definitions-B3QdlrHv.mjs +34 -0
- package/dist/definitions-B4rAvHNZ.mjs +63 -0
- package/dist/definitions-BB_4jnmy.mjs +37 -0
- package/dist/definitions-BMfYXoNC.mjs +43 -0
- package/dist/definitions-Beid2EB3.mjs +27 -0
- package/dist/definitions-C1UvM5Iy.mjs +126 -0
- package/dist/definitions-CXEI7QC72.mjs +216 -0
- package/dist/definitions-C_4r7Fo-2.mjs +14 -0
- package/dist/definitions-CkFDALoa.mjs +26 -0
- package/dist/definitions-Cke7zEb8.mjs +94 -0
- package/dist/definitions-ClJLzsJQ.mjs +25 -0
- package/dist/definitions-Cq-zroAU.mjs +28 -0
- package/dist/definitions-Cy3Sl6gV.mjs +34 -0
- package/dist/definitions-D3VsGcvz.mjs +47 -0
- package/dist/definitions-DVGfrn7y.mjs +96 -0
- package/dist/definitions-LKpC3-nL.mjs +9 -0
- package/dist/definitions-bAhHQJq9.mjs +359 -0
- package/dist/encoding-Bvz5jLRv.mjs +1065 -0
- package/dist/evidence-graph-bridge-C_fv9PuC.mjs +135 -0
- package/dist/{factory-CibqTNC8.mjs → factory-DxlGh9Xf.mjs} +37 -52
- package/dist/graphql-DYWzJ29s.mjs +1026 -0
- package/dist/handlers-9sAbfIg-.mjs +2552 -0
- package/dist/handlers-Bl8zkwz1.mjs +2716 -0
- package/dist/handlers-C67ktuRN.mjs +710 -0
- package/dist/handlers-C87g8oCe.mjs +276 -0
- package/dist/handlers-CTsDAO6p.mjs +681 -0
- package/dist/handlers-Cgyg6c0U.mjs +645 -0
- package/dist/handlers-D6j6yka7.mjs +2124 -0
- package/dist/handlers-DdFzXLvF.mjs +446 -0
- package/dist/handlers-DeLOCd5m.mjs +799 -0
- package/dist/handlers-DlCJN4Td.mjs +757 -0
- package/dist/handlers-DxGIq15_2.mjs +917 -0
- package/dist/handlers-U6L4xhuF.mjs +585 -0
- package/dist/handlers-tB9Mp9ZK.mjs +84 -0
- package/dist/handlers-tiy7EIBp.mjs +572 -0
- package/dist/handlers.impl-DS0d9fUw.mjs +761 -0
- package/dist/hooks-CzCWByww.mjs +898 -0
- package/dist/index.mjs +377 -155
- package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
- package/dist/maintenance-P7ePRXQC.mjs +830 -0
- package/dist/manifest-2ToTpjv8.mjs +106 -0
- package/dist/manifest-3g71z6Bg.mjs +79 -0
- package/dist/manifest-82baTv4U.mjs +45 -0
- package/dist/manifest-B3QVVeBS.mjs +82 -0
- package/dist/manifest-BB2J8IMJ.mjs +149 -0
- package/dist/manifest-BKbgbSiY.mjs +60 -0
- package/dist/manifest-Bcf-TJzH.mjs +848 -0
- package/dist/manifest-BmtZzQiQ2.mjs +45 -0
- package/dist/manifest-Bnd7kqEY.mjs +55 -0
- package/dist/manifest-BqQX6OQC2.mjs +65 -0
- package/dist/manifest-BqrQ4Tpj.mjs +81 -0
- package/dist/manifest-Br4RPFt5.mjs +370 -0
- package/dist/manifest-C5qDjysN.mjs +107 -0
- package/dist/manifest-C9RT5nk32.mjs +34 -0
- package/dist/manifest-CAhOuvSl.mjs +204 -0
- package/dist/manifest-CBYWCUBJ.mjs +51 -0
- package/dist/manifest-CFADCRa1.mjs +37 -0
- package/dist/manifest-CQVhavRF.mjs +114 -0
- package/dist/manifest-CT7zZBV1.mjs +48 -0
- package/dist/manifest-CV12bcrF.mjs +121 -0
- package/dist/manifest-CXsRWjjI.mjs +224 -0
- package/dist/manifest-CZLUCfG02.mjs +95 -0
- package/dist/manifest-D6phHKFd.mjs +131 -0
- package/dist/manifest-DCyjf4n2.mjs +294 -0
- package/dist/manifest-DHsnKgP6.mjs +60 -0
- package/dist/manifest-Df_dliIe.mjs +55 -0
- package/dist/manifest-Dh8WBmEW.mjs +129 -0
- package/dist/manifest-DhKRAT8_.mjs +92 -0
- package/dist/manifest-DlpTj4ic2.mjs +193 -0
- package/dist/manifest-DrbmZcFl2.mjs +253 -0
- package/dist/manifest-DuwHjUa5.mjs +70 -0
- package/dist/manifest-DzwvxPJX.mjs +38 -0
- package/dist/manifest-NXctwWQq.mjs +68 -0
- package/dist/manifest-Sc_0JQ13.mjs +418 -0
- package/dist/manifest-gZ4s_UtG.mjs +96 -0
- package/dist/manifest-qSleDqdO.mjs +1023 -0
- package/dist/modules-C184v-S9.mjs +11365 -0
- package/dist/mojo-ipc-B_H61Afw.mjs +525 -0
- package/dist/network-671Cw6hV.mjs +3346 -0
- package/dist/{artifacts-BbdOMET5.mjs → outputPaths-B1uGmrWZ.mjs} +219 -212
- package/dist/parse-args-BlRjqlkL.mjs +39 -0
- package/dist/platform-WmNn8Sxb.mjs +2070 -0
- package/dist/process-QcbIy5Zq.mjs +1401 -0
- package/dist/proxy-DqNs0bAd.mjs +170 -0
- package/dist/registry-D-6e18lB.mjs +34 -0
- package/dist/response-BQVP-xUn.mjs +28 -0
- package/dist/server/plugin-api.mjs +2 -2
- package/dist/shared-state-board-DV-dpHFJ.mjs +586 -0
- package/dist/sourcemap-Dq8ez8vS.mjs +650 -0
- package/dist/ssrf-policy-ZaUfvhq7.mjs +166 -0
- package/dist/streaming-BUQ0VJsg.mjs +725 -0
- package/dist/tool-builder-DCbIC5Eo.mjs +186 -0
- package/dist/transform-CiYJfNX0.mjs +1007 -0
- package/dist/types-Bx92KJfT.mjs +4 -0
- package/dist/wasm-DQTnHDs4.mjs +531 -0
- package/dist/workflow-f3xJOcjx.mjs +725 -0
- package/package.json +16 -16
- package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
- package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
- package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-CDe5WPSV.mjs} +0 -0
- package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-Bo4T3bz8.mjs} +0 -0
- package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-CwVLVdDM.mjs} +0 -0
- package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-DVkj9kpI.mjs} +0 -0
- package/dist/{types-BBjOqye-.mjs → types-CPhOReNX.mjs} +1 -1
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import { t as logger } from "./logger-Dh_xb7_2.mjs";
|
|
2
|
+
import { H as EXTENSION_GIT_CHECKOUT_TIMEOUT_MS, Q as GHIDRA_BRIDGE_ENDPOINT, U as EXTENSION_GIT_CLONE_TIMEOUT_MS, mt as IDA_BRIDGE_ENDPOINT } from "./constants-B0OANIBL.mjs";
|
|
3
|
+
import { n as getArtifactRetentionConfig, t as cleanupArtifacts } from "./artifactRetention-CFEprwPw.mjs";
|
|
4
|
+
import { i as serializeError, n as asJsonResponse } from "./response-BQVP-xUn.mjs";
|
|
5
|
+
import { c as getConfig, i as getProjectRoot } from "./outputPaths-B1uGmrWZ.mjs";
|
|
6
|
+
import { t as INSTALLED_EXTENSION_METADATA_FILENAME } from "./types-Bx92KJfT.mjs";
|
|
7
|
+
import { r as probeBetterSqlite3 } from "./betterSqlite3-0pqusHHH.mjs";
|
|
8
|
+
import { r as ioLimit } from "./concurrency-Bt0yv1kJ.mjs";
|
|
9
|
+
import { t as ToolRegistry } from "./ToolRegistry-BjaF4oNz.mjs";
|
|
10
|
+
import "./definitions-ClJLzsJQ.mjs";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
+
import { execFile } from "node:child_process";
|
|
17
|
+
import { promisify } from "node:util";
|
|
18
|
+
//#region src/utils/environmentDoctor.ts
|
|
19
|
+
const execFileAsync$1 = promisify(execFile);
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
let sharedRegistry = null;
|
|
22
|
+
let sharedRegistryTimestamp = 0;
|
|
23
|
+
const REGISTRY_CACHE_TTL_MS = 12e4;
|
|
24
|
+
function getSharedRegistry() {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
if (!sharedRegistry || now - sharedRegistryTimestamp > REGISTRY_CACHE_TTL_MS) {
|
|
27
|
+
sharedRegistry = new ToolRegistry();
|
|
28
|
+
sharedRegistryTimestamp = now;
|
|
29
|
+
}
|
|
30
|
+
return sharedRegistry;
|
|
31
|
+
}
|
|
32
|
+
async function runEnvironmentDoctor(options) {
|
|
33
|
+
const includeBridgeHealth = options?.includeBridgeHealth ?? true;
|
|
34
|
+
const externalResultsPromise = getSharedRegistry().probeAll(true);
|
|
35
|
+
const gitCommandPromise = ioLimit(() => checkCommand("git", ["--version"]));
|
|
36
|
+
const pythonCommandPromise = ioLimit(() => checkCommand("python", ["--version"]));
|
|
37
|
+
const pnpmCommandPromise = ioLimit(() => checkPnpmCommand());
|
|
38
|
+
const corepackCheckPromise = ioLimit(() => checkCommand("corepack", ["--version"]));
|
|
39
|
+
const bridgesPromise = includeBridgeHealth ? Promise.all([
|
|
40
|
+
ioLimit(() => checkHttpEndpoint("ghidra-bridge", `${GHIDRA_BRIDGE_ENDPOINT.replace(/\/$/, "")}/health`)),
|
|
41
|
+
ioLimit(() => checkHttpEndpoint("ida-bridge", `${IDA_BRIDGE_ENDPOINT.replace(/\/$/, "")}/health`)),
|
|
42
|
+
ioLimit(() => checkHttpEndpoint("burp-mcp-sse", process.env.BURP_MCP_SSE_URL?.trim() || "http://127.0.0.1:9876"))
|
|
43
|
+
]) : Promise.resolve([]);
|
|
44
|
+
const [externalResults, gitCommand, pythonCommand, pnpmCommand, corepackCheck, bridges] = await Promise.all([
|
|
45
|
+
externalResultsPromise,
|
|
46
|
+
gitCommandPromise,
|
|
47
|
+
pythonCommandPromise,
|
|
48
|
+
pnpmCommandPromise,
|
|
49
|
+
corepackCheckPromise,
|
|
50
|
+
bridgesPromise
|
|
51
|
+
]);
|
|
52
|
+
const corepackCommand = normalizeCorepackCheck(corepackCheck, pnpmCommand);
|
|
53
|
+
const packages = [
|
|
54
|
+
checkPackage("@modelcontextprotocol/sdk"),
|
|
55
|
+
checkPackage("rebrowser-puppeteer-core"),
|
|
56
|
+
checkBetterSqlite3(),
|
|
57
|
+
checkPackage("camoufox-js", "Optional Firefox anti-detect driver"),
|
|
58
|
+
checkPackage("playwright-core", "Optional browser automation dependency"),
|
|
59
|
+
checkNativeMemory()
|
|
60
|
+
];
|
|
61
|
+
const commands = [
|
|
62
|
+
gitCommand,
|
|
63
|
+
pythonCommand,
|
|
64
|
+
pnpmCommand,
|
|
65
|
+
corepackCommand,
|
|
66
|
+
...Object.entries(externalResults).map(([name, result]) => ({
|
|
67
|
+
name,
|
|
68
|
+
status: result.available ? "ok" : "missing",
|
|
69
|
+
detail: result.available ? `${result.path ?? "PATH"}${result.version ? ` (${result.version})` : ""}` : result.reason ?? "Unavailable"
|
|
70
|
+
}))
|
|
71
|
+
];
|
|
72
|
+
const limitations = buildPlatformLimitations();
|
|
73
|
+
const recommendations = buildRecommendations(packages, commands, bridges, limitations);
|
|
74
|
+
return {
|
|
75
|
+
success: [
|
|
76
|
+
...packages,
|
|
77
|
+
...commands,
|
|
78
|
+
...bridges
|
|
79
|
+
].every((item) => item.status !== "error"),
|
|
80
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
81
|
+
runtime: {
|
|
82
|
+
platform: process.platform,
|
|
83
|
+
arch: process.arch,
|
|
84
|
+
node: process.version,
|
|
85
|
+
cwd: process.cwd(),
|
|
86
|
+
projectRoot: getProjectRoot()
|
|
87
|
+
},
|
|
88
|
+
packages,
|
|
89
|
+
commands,
|
|
90
|
+
bridges,
|
|
91
|
+
config: {
|
|
92
|
+
transport: (process.env.MCP_TRANSPORT ?? "stdio").toLowerCase(),
|
|
93
|
+
toolProfile: (process.env.MCP_TOOL_PROFILE ?? "search").toLowerCase(),
|
|
94
|
+
pluginRoots: process.env.MCP_PLUGIN_ROOTS ?? "<jshook-install>/plugins",
|
|
95
|
+
workflowRoots: process.env.MCP_WORKFLOW_ROOTS ?? "<jshook-install>/workflows",
|
|
96
|
+
pluginSignatureRequired: process.env.MCP_PLUGIN_SIGNATURE_REQUIRED ?? (process.env.NODE_ENV === "production" ? "true (production default)" : "false"),
|
|
97
|
+
pluginStrictLoad: process.env.MCP_PLUGIN_STRICT_LOAD ?? (process.env.NODE_ENV === "production" ? "true (production default)" : "false"),
|
|
98
|
+
artifactRetention: getArtifactRetentionConfig()
|
|
99
|
+
},
|
|
100
|
+
limitations,
|
|
101
|
+
recommendations
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function checkPackage(packageName, missingHint) {
|
|
105
|
+
try {
|
|
106
|
+
const packageJson = require(require.resolve(`${packageName}/package.json`));
|
|
107
|
+
return {
|
|
108
|
+
name: packageName,
|
|
109
|
+
status: "ok",
|
|
110
|
+
detail: packageJson.version ? `installed (${packageJson.version})` : "installed"
|
|
111
|
+
};
|
|
112
|
+
} catch {
|
|
113
|
+
return {
|
|
114
|
+
name: packageName,
|
|
115
|
+
status: "missing",
|
|
116
|
+
detail: missingHint ?? "Not installed"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function checkBetterSqlite3() {
|
|
121
|
+
const result = probeBetterSqlite3();
|
|
122
|
+
return {
|
|
123
|
+
name: "better-sqlite3",
|
|
124
|
+
status: result.status,
|
|
125
|
+
detail: result.detail
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function isPnpmOperational(pnpm) {
|
|
129
|
+
return pnpm.status === "ok" || pnpm.detail.includes("npx fallback works");
|
|
130
|
+
}
|
|
131
|
+
async function checkPnpmCommand() {
|
|
132
|
+
const direct = await checkCommand("pnpm", ["--version"]);
|
|
133
|
+
if (direct.status === "ok") return direct;
|
|
134
|
+
const npxFallback = await checkCommand("npx", ["pnpm", "--version"], 1e4);
|
|
135
|
+
if (npxFallback.status === "ok") return {
|
|
136
|
+
name: "pnpm",
|
|
137
|
+
status: "warn",
|
|
138
|
+
detail: `direct pnpm command unavailable; npx fallback works (${npxFallback.detail})`
|
|
139
|
+
};
|
|
140
|
+
return direct;
|
|
141
|
+
}
|
|
142
|
+
function normalizeCorepackCheck(corepack, pnpm) {
|
|
143
|
+
if (corepack.status !== "missing" || !isPnpmOperational(pnpm)) return corepack;
|
|
144
|
+
return {
|
|
145
|
+
name: corepack.name,
|
|
146
|
+
status: "warn",
|
|
147
|
+
detail: process.platform === "win32" ? pnpm.detail.includes("npx fallback works") ? "corepack not found; use `npx pnpm` directly (common with nvm4w-managed Node on Windows)" : "corepack not found; standalone pnpm is available (common with nvm4w-managed Node on Windows)" : pnpm.detail.includes("npx fallback works") ? "corepack not found; use `npx pnpm` directly" : "corepack not found; standalone pnpm is available"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check koffi + platform-specific native library availability for memory tools.
|
|
152
|
+
* Only loads/unloads the library — does NOT call any native functions (avoids SIGBUS on SIP macOS).
|
|
153
|
+
*/
|
|
154
|
+
function checkNativeMemory() {
|
|
155
|
+
try {
|
|
156
|
+
const koffiVersion = require(require.resolve("koffi/package.json")).version ?? "unknown";
|
|
157
|
+
if (process.platform === "win32") return {
|
|
158
|
+
name: "native-memory",
|
|
159
|
+
status: "ok",
|
|
160
|
+
detail: `koffi ${koffiVersion} — Win32 kernel32.dll available`
|
|
161
|
+
};
|
|
162
|
+
if (process.platform === "darwin") try {
|
|
163
|
+
require("koffi").load("/usr/lib/libSystem.B.dylib").unload();
|
|
164
|
+
delete require.cache[require.resolve("koffi")];
|
|
165
|
+
return {
|
|
166
|
+
name: "native-memory",
|
|
167
|
+
status: "ok",
|
|
168
|
+
detail: `koffi ${koffiVersion} — macOS libSystem.B.dylib available (Mach APIs need root + SIP config)`
|
|
169
|
+
};
|
|
170
|
+
} catch {
|
|
171
|
+
return {
|
|
172
|
+
name: "native-memory",
|
|
173
|
+
status: "warn",
|
|
174
|
+
detail: `koffi ${koffiVersion} installed but cannot load libSystem.B.dylib`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
name: "native-memory",
|
|
179
|
+
status: "warn",
|
|
180
|
+
detail: `koffi ${koffiVersion} — no native FFI memory provider for ${process.platform} (proc-based ops available on Linux)`
|
|
181
|
+
};
|
|
182
|
+
} catch {
|
|
183
|
+
return {
|
|
184
|
+
name: "native-memory",
|
|
185
|
+
status: "missing",
|
|
186
|
+
detail: "koffi not installed — native memory tools unavailable. Install with: pnpm add koffi"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function checkCommand(command, args, timeout = 4e3) {
|
|
191
|
+
try {
|
|
192
|
+
const { stdout, stderr } = await execFileAsync$1(command, args, {
|
|
193
|
+
timeout,
|
|
194
|
+
windowsHide: true
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
name: command,
|
|
198
|
+
status: "ok",
|
|
199
|
+
detail: `${stdout || stderr}`.trim().split(/\r?\n/)[0] || "available"
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (process.platform === "win32") try {
|
|
203
|
+
const { stdout, stderr } = await execFileAsync$1("cmd", [
|
|
204
|
+
"/c",
|
|
205
|
+
command,
|
|
206
|
+
...args
|
|
207
|
+
], {
|
|
208
|
+
timeout,
|
|
209
|
+
windowsHide: true
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
name: command,
|
|
213
|
+
status: "ok",
|
|
214
|
+
detail: `${`${stdout || stderr}`.trim().split(/\r?\n/)[0] || "available"} (via cmd)`
|
|
215
|
+
};
|
|
216
|
+
} catch (cmdError) {
|
|
217
|
+
return formatCommandError(command, cmdError);
|
|
218
|
+
}
|
|
219
|
+
return formatCommandError(command, error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function formatCommandError(command, error) {
|
|
223
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
224
|
+
return {
|
|
225
|
+
name: command,
|
|
226
|
+
status: /ENOENT|not recognized|not found/i.test(detail) ? "missing" : "warn",
|
|
227
|
+
detail
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function checkHttpEndpoint(name, url) {
|
|
231
|
+
try {
|
|
232
|
+
const res = await fetch(url, {
|
|
233
|
+
method: "GET",
|
|
234
|
+
signal: AbortSignal.timeout(3e3)
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
name,
|
|
238
|
+
status: res.ok ? "ok" : "warn",
|
|
239
|
+
detail: `${url} -> HTTP ${res.status}`
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
return {
|
|
243
|
+
name,
|
|
244
|
+
status: "warn",
|
|
245
|
+
detail: `${url} -> ${error instanceof Error ? error.message : String(error)}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function buildPlatformLimitations() {
|
|
250
|
+
const limitations = [];
|
|
251
|
+
if (process.platform === "darwin") {
|
|
252
|
+
limitations.push("26 cross-platform memory tools available (scan, pointer-chain, structure-analysis, heap). 15 Windows-only tools unavailable (PE analysis, anti-cheat, code injection, speedhack, hardware breakpoints).");
|
|
253
|
+
limitations.push("Native memory operations (mach_vm_read/write) require root privileges and may require SIP configuration on ARM64.");
|
|
254
|
+
} else if (process.platform === "linux") {
|
|
255
|
+
limitations.push("Process management available via /proc. Native FFI memory provider not implemented — memory read/write uses /proc/pid/mem (requires root or CAP_SYS_PTRACE).");
|
|
256
|
+
limitations.push("Camoufox runs on Linux, but some Chrome/CDP-heavy workflows are better served by the Chrome driver.");
|
|
257
|
+
} else if (process.platform !== "win32") limitations.push(`Platform ${process.platform} is not supported for native memory operations. Use Windows or macOS.`);
|
|
258
|
+
return limitations;
|
|
259
|
+
}
|
|
260
|
+
function buildRecommendations(packages, commands, bridges, limitations) {
|
|
261
|
+
const recommendations = [];
|
|
262
|
+
const pnpmCommand = commands.find((item) => item.name === "pnpm");
|
|
263
|
+
const corepackCommand = commands.find((item) => item.name === "corepack");
|
|
264
|
+
if (packages.some((item) => item.name === "better-sqlite3" && item.status !== "ok")) recommendations.push("Install or rebuild the optional SQLite trace backend with `pnpm add -O better-sqlite3@12.6.2` or `npm rebuild better-sqlite3 --foreground-scripts` under the active Node version if you need trace tooling.");
|
|
265
|
+
if (packages.some((item) => item.name === "camoufox-js" && item.status !== "ok")) recommendations.push("Install optional browser dependencies with `pnpm run install:full` if you need Camoufox support.");
|
|
266
|
+
if (commands.some((item) => item.name.startsWith("wabt.") && item.status !== "ok")) recommendations.push("Install wabt if you need full WASM disassembly/decompilation; otherwise the server will stay in basic mode.");
|
|
267
|
+
if (pnpmCommand && !isPnpmOperational(pnpmCommand)) recommendations.push("Install pnpm or enable Corepack (`corepack enable`) before running package-management workflows.");
|
|
268
|
+
else if (pnpmCommand?.detail.includes("npx fallback works")) recommendations.push("Use `npx pnpm` directly on this machine or repair the local pnpm/Corepack shim if scripts expect bare `pnpm`.");
|
|
269
|
+
else if (corepackCommand?.status === "warn" && corepackCommand.detail.includes("standalone pnpm")) recommendations.push("Use `pnpm` or `npx pnpm` directly on this machine; `corepack` is optional and may be absent on nvm4w-managed Windows installs.");
|
|
270
|
+
if (bridges.some((item) => item.status !== "ok")) recommendations.push("Check local bridge endpoints (Ghidra / IDA / Burp) before relying on native-bridge workflows.");
|
|
271
|
+
if (limitations.length > 0) recommendations.push("Review platform limitations before using process/memory tooling on non-Windows hosts.");
|
|
272
|
+
return recommendations;
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/server/domains/maintenance/handlers.ts
|
|
276
|
+
var CoreMaintenanceHandlers = class {
|
|
277
|
+
tokenBudget;
|
|
278
|
+
unifiedCache;
|
|
279
|
+
artifactCleanup;
|
|
280
|
+
environmentDoctor;
|
|
281
|
+
constructor(deps) {
|
|
282
|
+
this.tokenBudget = deps.tokenBudget;
|
|
283
|
+
this.unifiedCache = deps.unifiedCache;
|
|
284
|
+
this.artifactCleanup = deps.artifactCleanup ?? cleanupArtifacts;
|
|
285
|
+
this.environmentDoctor = deps.environmentDoctor ?? runEnvironmentDoctor;
|
|
286
|
+
}
|
|
287
|
+
async handleGetTokenBudgetStats() {
|
|
288
|
+
try {
|
|
289
|
+
const stats = this.tokenBudget.getStats();
|
|
290
|
+
return asJsonResponse({
|
|
291
|
+
success: true,
|
|
292
|
+
...stats,
|
|
293
|
+
sessionDuration: `${Math.round((Date.now() - stats.sessionStartTime) / 1e3)}s`
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.error("Failed to read token budget stats:", error);
|
|
297
|
+
return asJsonResponse(serializeError(error));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async handleManualTokenCleanup() {
|
|
301
|
+
try {
|
|
302
|
+
const beforeStats = this.tokenBudget.getStats();
|
|
303
|
+
this.tokenBudget.manualCleanup();
|
|
304
|
+
const afterStats = this.tokenBudget.getStats();
|
|
305
|
+
const freed = beforeStats.currentUsage - afterStats.currentUsage;
|
|
306
|
+
return asJsonResponse({
|
|
307
|
+
success: true,
|
|
308
|
+
message: "Manual cleanup completed",
|
|
309
|
+
before: {
|
|
310
|
+
usage: beforeStats.currentUsage,
|
|
311
|
+
percentage: beforeStats.usagePercentage
|
|
312
|
+
},
|
|
313
|
+
after: {
|
|
314
|
+
usage: afterStats.currentUsage,
|
|
315
|
+
percentage: afterStats.usagePercentage
|
|
316
|
+
},
|
|
317
|
+
freed: {
|
|
318
|
+
tokens: freed,
|
|
319
|
+
percentage: Math.round(freed / beforeStats.maxTokens * 100)
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
logger.error("Failed to perform manual cleanup:", error);
|
|
324
|
+
return asJsonResponse(serializeError(error));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async handleResetTokenBudget() {
|
|
328
|
+
try {
|
|
329
|
+
this.tokenBudget.reset();
|
|
330
|
+
return asJsonResponse({
|
|
331
|
+
success: true,
|
|
332
|
+
message: "Token budget reset successfully",
|
|
333
|
+
currentUsage: 0,
|
|
334
|
+
maxTokens: 2e5,
|
|
335
|
+
usagePercentage: 0
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error("Failed to reset token budget:", error);
|
|
339
|
+
return asJsonResponse(serializeError(error));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async handleGetCacheStats() {
|
|
343
|
+
try {
|
|
344
|
+
return asJsonResponse({
|
|
345
|
+
success: true,
|
|
346
|
+
...await this.unifiedCache.getGlobalStats()
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
logger.error("Failed to get cache stats:", error);
|
|
350
|
+
return asJsonResponse(serializeError(error));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async handleSmartCacheCleanup(targetSize) {
|
|
354
|
+
try {
|
|
355
|
+
return asJsonResponse({
|
|
356
|
+
success: true,
|
|
357
|
+
...await this.unifiedCache.smartCleanup(targetSize)
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
logger.error("Failed to perform cache cleanup:", error);
|
|
361
|
+
return asJsonResponse(serializeError(error));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async handleClearAllCaches() {
|
|
365
|
+
try {
|
|
366
|
+
await this.unifiedCache.clearAll();
|
|
367
|
+
return asJsonResponse({
|
|
368
|
+
success: true,
|
|
369
|
+
message: "All caches cleared"
|
|
370
|
+
});
|
|
371
|
+
} catch (error) {
|
|
372
|
+
logger.error("Failed to clear caches:", error);
|
|
373
|
+
return asJsonResponse(serializeError(error));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async handleCleanupArtifacts(args) {
|
|
377
|
+
try {
|
|
378
|
+
return asJsonResponse(await this.artifactCleanup({
|
|
379
|
+
retentionDays: args.retentionDays,
|
|
380
|
+
maxTotalBytes: args.maxTotalBytes,
|
|
381
|
+
dryRun: args.dryRun
|
|
382
|
+
}));
|
|
383
|
+
} catch (error) {
|
|
384
|
+
logger.error("Failed to cleanup artifacts:", error);
|
|
385
|
+
return asJsonResponse(serializeError(error));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async handleEnvironmentDoctor(args) {
|
|
389
|
+
try {
|
|
390
|
+
return asJsonResponse(await this.environmentDoctor({ includeBridgeHealth: args.includeBridgeHealth }));
|
|
391
|
+
} catch (error) {
|
|
392
|
+
logger.error("Failed to run environment doctor:", error);
|
|
393
|
+
return asJsonResponse(serializeError(error));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/server/domains/maintenance/handlers.extensions.ts
|
|
399
|
+
const execFileAsync = promisify(execFile);
|
|
400
|
+
function getJshookInstallRoot() {
|
|
401
|
+
return fileURLToPath(new URL("../../../../", import.meta.url));
|
|
402
|
+
}
|
|
403
|
+
function parseFirstRoot(raw) {
|
|
404
|
+
const value = raw?.trim();
|
|
405
|
+
if (!value) return void 0;
|
|
406
|
+
return value.split(",").map((item) => item.trim()).find((item) => item.length > 0);
|
|
407
|
+
}
|
|
408
|
+
function resolveDefaultExtensionRoot(kind) {
|
|
409
|
+
const envKey = kind === "workflow" ? "MCP_WORKFLOW_ROOTS" : "MCP_PLUGIN_ROOTS";
|
|
410
|
+
const configured = parseFirstRoot(process.env[envKey]);
|
|
411
|
+
if (configured) return resolve(configured);
|
|
412
|
+
return resolve(getJshookInstallRoot(), kind === "workflow" ? "workflows" : "plugins");
|
|
413
|
+
}
|
|
414
|
+
function getRegistryBaseUrl() {
|
|
415
|
+
const baseUrl = (process.env.EXTENSION_REGISTRY_BASE_URL ?? "").trim().replace(/\/+$/, "");
|
|
416
|
+
if (!baseUrl) throw new Error("EXTENSION_REGISTRY_BASE_URL is not configured. Set it in .env or environment before browsing or installing extensions.");
|
|
417
|
+
return baseUrl;
|
|
418
|
+
}
|
|
419
|
+
function normalizeInstallPathSegment(value, field) {
|
|
420
|
+
const normalized = value?.trim();
|
|
421
|
+
if (!normalized) {
|
|
422
|
+
if (field === "subpath") return ".";
|
|
423
|
+
throw new Error(`Registry source.${field} must be a non-empty string`);
|
|
424
|
+
}
|
|
425
|
+
return normalized;
|
|
426
|
+
}
|
|
427
|
+
function ensurePathStaysWithin(baseDir, targetPath, field) {
|
|
428
|
+
const rel = relative(baseDir, targetPath).replace(/\\/g, "/");
|
|
429
|
+
if (rel === ".." || rel.startsWith("../") || isAbsolute(rel)) throw new Error(`Registry source.${field} must stay within ${baseDir}: ${targetPath}`);
|
|
430
|
+
}
|
|
431
|
+
function resolveExtensionProjectDir(installDir, subpath) {
|
|
432
|
+
const projectDir = resolve(installDir, normalizeInstallPathSegment(subpath, "subpath"));
|
|
433
|
+
ensurePathStaysWithin(installDir, projectDir, "subpath");
|
|
434
|
+
return projectDir;
|
|
435
|
+
}
|
|
436
|
+
function resolveExtensionEntryFile(projectDir, entryPath) {
|
|
437
|
+
const resolvedEntryFile = resolve(projectDir, normalizeInstallPathSegment(entryPath, "entry"));
|
|
438
|
+
ensurePathStaysWithin(projectDir, resolvedEntryFile, "entry");
|
|
439
|
+
return resolvedEntryFile;
|
|
440
|
+
}
|
|
441
|
+
function buildRuntimeEntryCandidates(entryPath) {
|
|
442
|
+
const normalizedEntry = normalizeInstallPathSegment(entryPath, "entry").replace(/\\/g, "/");
|
|
443
|
+
const candidates = [normalizedEntry];
|
|
444
|
+
if (!normalizedEntry.endsWith(".ts")) return candidates;
|
|
445
|
+
const jsEntry = `${normalizedEntry.slice(0, -3)}.js`;
|
|
446
|
+
candidates.unshift(jsEntry);
|
|
447
|
+
if (!normalizedEntry.startsWith("dist/")) candidates.unshift(`dist/${jsEntry}`);
|
|
448
|
+
return [...new Set(candidates)];
|
|
449
|
+
}
|
|
450
|
+
function resolveInstalledRuntimeEntry(projectDir, entryPath) {
|
|
451
|
+
const candidates = buildRuntimeEntryCandidates(entryPath);
|
|
452
|
+
for (const candidate of candidates) if (existsSync(resolveExtensionEntryFile(projectDir, candidate))) return candidate;
|
|
453
|
+
return normalizeInstallPathSegment(entryPath, "entry");
|
|
454
|
+
}
|
|
455
|
+
async function writeInstalledExtensionMetadata(kind, entry, projectDir, installedEntryPath) {
|
|
456
|
+
const payload = {
|
|
457
|
+
version: 1,
|
|
458
|
+
kind,
|
|
459
|
+
slug: entry.slug,
|
|
460
|
+
id: entry.id,
|
|
461
|
+
source: {
|
|
462
|
+
type: entry.source.type,
|
|
463
|
+
repo: entry.source.repo,
|
|
464
|
+
ref: entry.source.ref,
|
|
465
|
+
commit: entry.source.commit,
|
|
466
|
+
subpath: normalizeInstallPathSegment(entry.source.subpath, "subpath"),
|
|
467
|
+
entry: normalizeInstallPathSegment(installedEntryPath, "entry")
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
const metadataPath = resolve(projectDir, INSTALLED_EXTENSION_METADATA_FILENAME);
|
|
471
|
+
await writeFile(metadataPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
472
|
+
return metadataPath;
|
|
473
|
+
}
|
|
474
|
+
const LOCAL_EXTENSION_SDK_PACKAGE = "@jshookmcp/extension-sdk";
|
|
475
|
+
const LOCAL_EXTENSION_SDK_ROOT = resolve(getJshookInstallRoot(), "packages", "extension-sdk");
|
|
476
|
+
function getRegistryCacheDir() {
|
|
477
|
+
return getConfig().paths.registryCacheDir;
|
|
478
|
+
}
|
|
479
|
+
var RegistryFetchError = class extends Error {
|
|
480
|
+
constructor(code, url, message, cachePath, status) {
|
|
481
|
+
super(message);
|
|
482
|
+
this.code = code;
|
|
483
|
+
this.url = url;
|
|
484
|
+
this.cachePath = cachePath;
|
|
485
|
+
this.status = status;
|
|
486
|
+
this.name = "RegistryFetchError";
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
function resolvePackageManagerInvocation(packageManager, args) {
|
|
490
|
+
if (process.platform !== "win32")
|
|
491
|
+
/* istanbul ignore next -- OS specific fallback */
|
|
492
|
+
return {
|
|
493
|
+
command: packageManager,
|
|
494
|
+
args
|
|
495
|
+
};
|
|
496
|
+
return {
|
|
497
|
+
command: "powershell.exe",
|
|
498
|
+
args: [
|
|
499
|
+
"-NoProfile",
|
|
500
|
+
"-NonInteractive",
|
|
501
|
+
"-Command",
|
|
502
|
+
`${packageManager} ${args.join(" ")}`
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
async function execPackageManager(packageManager, args, options) {
|
|
507
|
+
const invocation = resolvePackageManagerInvocation(packageManager, args);
|
|
508
|
+
return execFileAsync(invocation.command, invocation.args, {
|
|
509
|
+
...options,
|
|
510
|
+
env: {
|
|
511
|
+
...process.env,
|
|
512
|
+
...options?.env,
|
|
513
|
+
CI: "true"
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
async function resolvePackageManager(installDir) {
|
|
518
|
+
const packageJsonPath = resolve(installDir, "package.json");
|
|
519
|
+
const pnpmLockPath = resolve(installDir, "pnpm-lock.yaml");
|
|
520
|
+
const npmLockPath = resolve(installDir, "package-lock.json");
|
|
521
|
+
if (existsSync(packageJsonPath)) try {
|
|
522
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
523
|
+
const packageManager = JSON.parse(raw).packageManager?.trim().toLowerCase().split("@")[0];
|
|
524
|
+
if (packageManager === "pnpm") return "pnpm";
|
|
525
|
+
if (packageManager === "npm") return "npm";
|
|
526
|
+
} catch {}
|
|
527
|
+
if (existsSync(pnpmLockPath)) return "pnpm";
|
|
528
|
+
if (existsSync(npmLockPath)) return "npm";
|
|
529
|
+
return "pnpm";
|
|
530
|
+
}
|
|
531
|
+
function getRegistryCachePath(kind) {
|
|
532
|
+
return resolve(getRegistryCacheDir(), `registry-${kind}.json`);
|
|
533
|
+
}
|
|
534
|
+
async function readRegistryCache(kind) {
|
|
535
|
+
const cachePath = getRegistryCachePath(kind);
|
|
536
|
+
try {
|
|
537
|
+
const raw = await readFile(cachePath, "utf8");
|
|
538
|
+
return JSON.parse(raw);
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function writeRegistryCache(kind, payload) {
|
|
544
|
+
const cachePath = getRegistryCachePath(kind);
|
|
545
|
+
await mkdir(dirname(cachePath), { recursive: true });
|
|
546
|
+
await writeFile(cachePath, JSON.stringify(payload, null, 2), "utf8");
|
|
547
|
+
}
|
|
548
|
+
function classifyRegistryFetchError(url, error, cachePath) {
|
|
549
|
+
if (error instanceof RegistryFetchError) return error;
|
|
550
|
+
if (error instanceof DOMException && error.name === "AbortError") return new RegistryFetchError("timeout", url, `Registry fetch timed out after 10000ms: ${url}`, cachePath);
|
|
551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
552
|
+
if (message.includes("ENOTFOUND") || message.includes("getaddrinfo")) return new RegistryFetchError("dns_failure", url, `DNS resolution failed for registry URL: ${url}`, cachePath);
|
|
553
|
+
if (message.includes("ECONNREFUSED")) return new RegistryFetchError("connection_refused", url, `Connection refused by registry server: ${url}`, cachePath);
|
|
554
|
+
if (message.includes("CERT_") || message.includes("certificate") || message.includes("SSL")) return new RegistryFetchError("tls_error", url, `TLS/certificate error when connecting to registry: ${url}`, cachePath);
|
|
555
|
+
const httpMatch = message.match(/HTTP\s+(\d+)/i);
|
|
556
|
+
if (httpMatch) return new RegistryFetchError("http_error", url, message, cachePath, Number(httpMatch[1]));
|
|
557
|
+
return new RegistryFetchError("fetch_failed", url, message, cachePath);
|
|
558
|
+
}
|
|
559
|
+
function serializeRegistryFetchError(error) {
|
|
560
|
+
return {
|
|
561
|
+
success: false,
|
|
562
|
+
error: error.code,
|
|
563
|
+
message: error.message,
|
|
564
|
+
url: error.url,
|
|
565
|
+
...typeof error.status === "number" ? { status: error.status } : {}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async function fetchJson(url, options) {
|
|
569
|
+
const controller = new AbortController();
|
|
570
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
571
|
+
const cachePath = options?.cacheKey ? getRegistryCachePath(options.cacheKey) : void 0;
|
|
572
|
+
try {
|
|
573
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
574
|
+
if (!res.ok) throw new RegistryFetchError("http_error", url, `HTTP ${res.status} ${res.statusText} from ${url}`, cachePath, res.status);
|
|
575
|
+
const data = await res.json();
|
|
576
|
+
if (options?.cacheKey) try {
|
|
577
|
+
await writeRegistryCache(options.cacheKey, data);
|
|
578
|
+
} catch (cacheError) {
|
|
579
|
+
logger.warn(`[extensions] Failed to persist ${options.cacheKey} registry cache for ${url}:`, cacheError);
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
data,
|
|
583
|
+
stale: false,
|
|
584
|
+
source: "network",
|
|
585
|
+
cachePath
|
|
586
|
+
};
|
|
587
|
+
} catch (error) {
|
|
588
|
+
const classified = classifyRegistryFetchError(url, error, cachePath);
|
|
589
|
+
if (options?.cacheKey) {
|
|
590
|
+
const cached = await readRegistryCache(options.cacheKey);
|
|
591
|
+
if (cached) {
|
|
592
|
+
logger.warn(`[extensions] Using stale ${options.cacheKey} registry cache after ${classified.code}: ${url}`);
|
|
593
|
+
return {
|
|
594
|
+
data: cached,
|
|
595
|
+
stale: true,
|
|
596
|
+
source: "cache",
|
|
597
|
+
cachePath
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
throw classified;
|
|
602
|
+
} finally {
|
|
603
|
+
clearTimeout(timer);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function rewriteLocalExtensionSdkDependency(installDir) {
|
|
607
|
+
const packageJsonPath = resolve(installDir, "package.json");
|
|
608
|
+
try {
|
|
609
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
610
|
+
const pkg = JSON.parse(raw);
|
|
611
|
+
const sections = [
|
|
612
|
+
"dependencies",
|
|
613
|
+
"devDependencies",
|
|
614
|
+
"peerDependencies",
|
|
615
|
+
"optionalDependencies"
|
|
616
|
+
];
|
|
617
|
+
const localSdkSpec = `file:${relative(installDir, LOCAL_EXTENSION_SDK_ROOT).replace(/\\/g, "/") || "."}`;
|
|
618
|
+
let changed = false;
|
|
619
|
+
for (const sectionName of sections) {
|
|
620
|
+
const section = pkg[sectionName];
|
|
621
|
+
if (!section || typeof section !== "object") continue;
|
|
622
|
+
const dependencyMap = section;
|
|
623
|
+
const currentValue = dependencyMap[LOCAL_EXTENSION_SDK_PACKAGE];
|
|
624
|
+
if (typeof currentValue === "string" && currentValue.startsWith("workspace:")) {
|
|
625
|
+
dependencyMap[LOCAL_EXTENSION_SDK_PACKAGE] = localSdkSpec;
|
|
626
|
+
changed = true;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!changed) return false;
|
|
630
|
+
await writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
|
631
|
+
logger.info(`[extensions] Rewrote ${LOCAL_EXTENSION_SDK_PACKAGE} dependency to local file path for ${installDir}`);
|
|
632
|
+
return true;
|
|
633
|
+
} catch (error) {
|
|
634
|
+
logger.warn(`[extensions] Failed to rewrite ${LOCAL_EXTENSION_SDK_PACKAGE} dependency for ${installDir}:`, error);
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function findRegistryEntryBySlug(registryBase, slug) {
|
|
639
|
+
const [workflowResult, pluginResult] = await Promise.allSettled([fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: "workflows" }), fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: "plugins" })]);
|
|
640
|
+
if (workflowResult.status === "fulfilled") {
|
|
641
|
+
const workflowEntry = (Array.isArray(workflowResult.value.data.workflows) ? workflowResult.value.data.workflows : []).find((item) => item.slug === slug);
|
|
642
|
+
if (workflowEntry) return {
|
|
643
|
+
entry: workflowEntry,
|
|
644
|
+
kind: "workflow"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (pluginResult.status === "fulfilled") {
|
|
648
|
+
const pluginEntry = (Array.isArray(pluginResult.value.data.plugins) ? pluginResult.value.data.plugins : []).find((item) => item.slug === slug);
|
|
649
|
+
if (pluginEntry) return {
|
|
650
|
+
entry: pluginEntry,
|
|
651
|
+
kind: "plugin"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const workflowFetchError = workflowResult.status === "rejected" ? workflowResult.reason instanceof Error ? workflowResult.reason : new Error(String(workflowResult.reason)) : void 0;
|
|
655
|
+
const pluginFetchError = pluginResult.status === "rejected" ? pluginResult.reason instanceof Error ? pluginResult.reason : new Error(String(pluginResult.reason)) : void 0;
|
|
656
|
+
if (workflowFetchError && pluginFetchError) throw new Error(`Failed to resolve extension slug "${slug}": workflow registry error: ${workflowFetchError.message}; plugin registry error: ${pluginFetchError.message}`);
|
|
657
|
+
if (pluginFetchError) throw new Error(`Extension "${slug}" was not found in workflow registry, and plugin registry lookup failed: ${pluginFetchError.message}`);
|
|
658
|
+
if (workflowFetchError) throw new Error(`Extension "${slug}" was not found in plugin registry, and workflow registry lookup failed: ${workflowFetchError.message}`);
|
|
659
|
+
throw new Error(`Extension "${slug}" not found in workflow or plugin registry`);
|
|
660
|
+
}
|
|
661
|
+
var ExtensionManagementHandlers = class {
|
|
662
|
+
ctx;
|
|
663
|
+
constructor(ctx) {
|
|
664
|
+
this.ctx = ctx;
|
|
665
|
+
}
|
|
666
|
+
async handleListExtensions() {
|
|
667
|
+
try {
|
|
668
|
+
return asJsonResponse({
|
|
669
|
+
success: true,
|
|
670
|
+
...this.ctx.listExtensions()
|
|
671
|
+
});
|
|
672
|
+
} catch (error) {
|
|
673
|
+
logger.error("Failed to list extensions:", error);
|
|
674
|
+
return asJsonResponse(serializeError(error));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async handleReloadExtensions() {
|
|
678
|
+
try {
|
|
679
|
+
return asJsonResponse({
|
|
680
|
+
success: true,
|
|
681
|
+
...await this.ctx.reloadExtensions()
|
|
682
|
+
});
|
|
683
|
+
} catch (error) {
|
|
684
|
+
logger.error("Failed to reload extensions:", error);
|
|
685
|
+
return asJsonResponse(serializeError(error));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async handleBrowseExtensionRegistry(kind) {
|
|
689
|
+
try {
|
|
690
|
+
const registryBase = getRegistryBaseUrl();
|
|
691
|
+
const showPlugins = kind === "all" || kind === "plugin";
|
|
692
|
+
const showWorkflows = kind === "all" || kind === "workflow";
|
|
693
|
+
const result = { success: true };
|
|
694
|
+
let stale = false;
|
|
695
|
+
const pluginPromise = showPlugins ? fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: "plugins" }) : void 0;
|
|
696
|
+
const workflowPromise = showWorkflows ? fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: "workflows" }) : void 0;
|
|
697
|
+
const [pluginIndex, workflowIndex] = await Promise.all([pluginPromise ?? Promise.resolve(void 0), workflowPromise ?? Promise.resolve(void 0)]);
|
|
698
|
+
if (pluginIndex) {
|
|
699
|
+
const plugins = Array.isArray(pluginIndex.data.plugins) ? pluginIndex.data.plugins : [];
|
|
700
|
+
result.plugins = plugins.map((p) => ({
|
|
701
|
+
slug: p.slug,
|
|
702
|
+
id: p.id,
|
|
703
|
+
name: p.meta.name,
|
|
704
|
+
description: p.meta.description,
|
|
705
|
+
author: p.meta.author,
|
|
706
|
+
repo: p.source.repo,
|
|
707
|
+
commit: p.source.commit,
|
|
708
|
+
entry: p.source.entry
|
|
709
|
+
}));
|
|
710
|
+
result.pluginCount = plugins.length;
|
|
711
|
+
result.pluginSource = pluginIndex.source;
|
|
712
|
+
stale = stale || pluginIndex.stale;
|
|
713
|
+
}
|
|
714
|
+
if (workflowIndex) {
|
|
715
|
+
const workflows = Array.isArray(workflowIndex.data.workflows) ? workflowIndex.data.workflows : [];
|
|
716
|
+
result.workflows = workflows.map((w) => ({
|
|
717
|
+
slug: w.slug,
|
|
718
|
+
id: w.id,
|
|
719
|
+
name: w.meta.name,
|
|
720
|
+
description: w.meta.description,
|
|
721
|
+
author: w.meta.author,
|
|
722
|
+
repo: w.source.repo,
|
|
723
|
+
commit: w.source.commit,
|
|
724
|
+
entry: w.source.entry
|
|
725
|
+
}));
|
|
726
|
+
result.workflowCount = workflows.length;
|
|
727
|
+
result.workflowSource = workflowIndex.source;
|
|
728
|
+
stale = stale || workflowIndex.stale;
|
|
729
|
+
}
|
|
730
|
+
if (stale) result.stale = true;
|
|
731
|
+
return asJsonResponse(result);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
logger.error("Failed to browse extension registry:", error);
|
|
734
|
+
if (error instanceof RegistryFetchError) return asJsonResponse(serializeRegistryFetchError(error));
|
|
735
|
+
return asJsonResponse(serializeError(error));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async handleInstallExtension(slug, targetDir) {
|
|
739
|
+
try {
|
|
740
|
+
const { entry, kind } = await findRegistryEntryBySlug(getRegistryBaseUrl(), slug);
|
|
741
|
+
const isWorkflow = kind === "workflow";
|
|
742
|
+
const defaultRoot = resolveDefaultExtensionRoot(isWorkflow ? "workflow" : "plugin");
|
|
743
|
+
const installDir = targetDir ? resolve(targetDir) : resolve(defaultRoot, slug);
|
|
744
|
+
const projectDir = resolveExtensionProjectDir(installDir, entry.source.subpath);
|
|
745
|
+
resolveExtensionEntryFile(projectDir, entry.source.entry);
|
|
746
|
+
if (existsSync(installDir)) return asJsonResponse({
|
|
747
|
+
success: false,
|
|
748
|
+
error: `Target directory already exists: ${installDir}`,
|
|
749
|
+
hint: "Remove the existing directory first, or specify a different targetDir"
|
|
750
|
+
});
|
|
751
|
+
await mkdir(dirname(installDir), { recursive: true });
|
|
752
|
+
await execFileAsync("git", [
|
|
753
|
+
"clone",
|
|
754
|
+
entry.source.repo,
|
|
755
|
+
installDir
|
|
756
|
+
], { timeout: EXTENSION_GIT_CLONE_TIMEOUT_MS });
|
|
757
|
+
await execFileAsync("git", [
|
|
758
|
+
"-C",
|
|
759
|
+
installDir,
|
|
760
|
+
"checkout",
|
|
761
|
+
entry.source.commit
|
|
762
|
+
], { timeout: EXTENSION_GIT_CHECKOUT_TIMEOUT_MS });
|
|
763
|
+
if (existsSync(resolve(projectDir, "package.json"))) {
|
|
764
|
+
await rewriteLocalExtensionSdkDependency(projectDir);
|
|
765
|
+
const packageManager = await resolvePackageManager(projectDir);
|
|
766
|
+
await execPackageManager(packageManager, packageManager === "pnpm" ? [
|
|
767
|
+
"--ignore-workspace",
|
|
768
|
+
"install",
|
|
769
|
+
"--no-frozen-lockfile",
|
|
770
|
+
"--ignore-scripts"
|
|
771
|
+
] : ["install", "--ignore-scripts"], {
|
|
772
|
+
cwd: projectDir,
|
|
773
|
+
timeout: Math.max(EXTENSION_GIT_CLONE_TIMEOUT_MS, 12e4)
|
|
774
|
+
});
|
|
775
|
+
await execPackageManager(packageManager, packageManager === "pnpm" ? [
|
|
776
|
+
"--ignore-workspace",
|
|
777
|
+
"run",
|
|
778
|
+
"--if-present",
|
|
779
|
+
"build"
|
|
780
|
+
] : [
|
|
781
|
+
"run",
|
|
782
|
+
"build",
|
|
783
|
+
"--if-present"
|
|
784
|
+
], {
|
|
785
|
+
cwd: projectDir,
|
|
786
|
+
timeout: Math.max(EXTENSION_GIT_CLONE_TIMEOUT_MS, 12e4)
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
const installedEntry = resolveInstalledRuntimeEntry(projectDir, entry.source.entry);
|
|
790
|
+
const entryFile = resolveExtensionEntryFile(projectDir, installedEntry);
|
|
791
|
+
if (!existsSync(entryFile)) return asJsonResponse({
|
|
792
|
+
success: false,
|
|
793
|
+
error: `Installed extension entry not found: ${installedEntry}`,
|
|
794
|
+
installDir,
|
|
795
|
+
projectDir,
|
|
796
|
+
expectedEntryFile: entryFile,
|
|
797
|
+
hint: "The registry source.entry or its compiled JS output must exist after clone/build before reloadExtensions can load it."
|
|
798
|
+
});
|
|
799
|
+
const metadataPath = await writeInstalledExtensionMetadata(isWorkflow ? "workflow" : "plugin", entry, projectDir, installedEntry);
|
|
800
|
+
const reloadResult = await this.ctx.reloadExtensions();
|
|
801
|
+
return asJsonResponse({
|
|
802
|
+
success: true,
|
|
803
|
+
installed: {
|
|
804
|
+
slug: entry.slug,
|
|
805
|
+
id: entry.id,
|
|
806
|
+
name: entry.meta.name,
|
|
807
|
+
repo: entry.source.repo,
|
|
808
|
+
commit: entry.source.commit,
|
|
809
|
+
installDir,
|
|
810
|
+
projectDir,
|
|
811
|
+
entry: installedEntry,
|
|
812
|
+
entryFile,
|
|
813
|
+
metadataPath
|
|
814
|
+
},
|
|
815
|
+
reload: {
|
|
816
|
+
addedTools: reloadResult.addedTools,
|
|
817
|
+
pluginCount: reloadResult.pluginCount,
|
|
818
|
+
workflowCount: reloadResult.workflowCount,
|
|
819
|
+
errors: reloadResult.errors,
|
|
820
|
+
warnings: reloadResult.warnings
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
} catch (error) {
|
|
824
|
+
logger.error("Failed to install extension:", error);
|
|
825
|
+
return asJsonResponse(serializeError(error));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
//#endregion
|
|
830
|
+
export { CoreMaintenanceHandlers, ExtensionManagementHandlers };
|