@redstone-md/mapr 0.0.1-alpha
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/LICENSE +45 -0
- package/README.md +109 -0
- package/bin/mapr +2 -0
- package/index.ts +247 -0
- package/lib/ai-analyzer.ts +598 -0
- package/lib/artifacts.ts +233 -0
- package/lib/cli-args.ts +152 -0
- package/lib/config.ts +385 -0
- package/lib/formatter.ts +109 -0
- package/lib/local-rag.ts +104 -0
- package/lib/progress.ts +10 -0
- package/lib/provider.ts +85 -0
- package/lib/reporter.ts +213 -0
- package/lib/scraper.ts +169 -0
- package/lib/swarm-prompts.ts +56 -0
- package/lib/wasm.ts +62 -0
- package/package.json +62 -0
package/lib/reporter.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { writeFile } from "fs/promises";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { artifactTypeSchema } from "./artifacts";
|
|
6
|
+
import type { BundleAnalysis } from "./ai-analyzer";
|
|
7
|
+
import type { FormattedArtifact } from "./formatter";
|
|
8
|
+
|
|
9
|
+
const reportInputSchema = z.object({
|
|
10
|
+
targetUrl: z.string().url(),
|
|
11
|
+
htmlPages: z.array(z.string().url()),
|
|
12
|
+
reportStatus: z.enum(["complete", "partial"]).default("complete"),
|
|
13
|
+
analysisError: z.string().min(1).optional(),
|
|
14
|
+
artifacts: z.array(
|
|
15
|
+
z.object({
|
|
16
|
+
url: z.string().url(),
|
|
17
|
+
type: artifactTypeSchema,
|
|
18
|
+
content: z.string(),
|
|
19
|
+
formattedContent: z.string(),
|
|
20
|
+
sizeBytes: z.number().int().nonnegative(),
|
|
21
|
+
discoveredFrom: z.string().min(1),
|
|
22
|
+
formattingSkipped: z.boolean(),
|
|
23
|
+
formattingNote: z.string().optional(),
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
26
|
+
analysis: z.object({
|
|
27
|
+
overview: z.string(),
|
|
28
|
+
entryPoints: z.array(
|
|
29
|
+
z.object({
|
|
30
|
+
symbol: z.string(),
|
|
31
|
+
description: z.string(),
|
|
32
|
+
evidence: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
initializationFlow: z.array(z.string()),
|
|
36
|
+
callGraph: z.array(
|
|
37
|
+
z.object({
|
|
38
|
+
caller: z.string(),
|
|
39
|
+
callee: z.string(),
|
|
40
|
+
rationale: z.string(),
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
restoredNames: z.array(
|
|
44
|
+
z.object({
|
|
45
|
+
originalName: z.string(),
|
|
46
|
+
suggestedName: z.string(),
|
|
47
|
+
justification: z.string(),
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
notableLibraries: z.array(z.string()),
|
|
51
|
+
investigationTips: z.array(z.string()),
|
|
52
|
+
risks: z.array(z.string()),
|
|
53
|
+
artifactSummaries: z.array(
|
|
54
|
+
z.object({
|
|
55
|
+
url: z.string().url(),
|
|
56
|
+
type: artifactTypeSchema,
|
|
57
|
+
chunkCount: z.number().int().nonnegative(),
|
|
58
|
+
summary: z.string(),
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
analyzedChunkCount: z.number().int().nonnegative(),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
type ReportInput = z.infer<typeof reportInputSchema>;
|
|
66
|
+
|
|
67
|
+
function formatBulletList(items: string[], emptyState: string): string {
|
|
68
|
+
return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : `- ${emptyState}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatArtifactTable(artifacts: FormattedArtifact[]): string {
|
|
72
|
+
if (artifacts.length === 0) {
|
|
73
|
+
return "_No artifacts were downloaded._";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = [
|
|
77
|
+
"| Artifact URL | Type | Size (bytes) | Discovered From | Formatting | Note |",
|
|
78
|
+
"| --- | --- | ---: | --- | --- | --- |",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const artifact of artifacts) {
|
|
82
|
+
lines.push(
|
|
83
|
+
`| ${artifact.url} | ${artifact.type} | ${artifact.sizeBytes} | ${artifact.discoveredFrom} | ${
|
|
84
|
+
artifact.formattingSkipped ? "Skipped" : "Applied"
|
|
85
|
+
} | ${artifact.formattingNote ?? "None"} |`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class ReportWriter {
|
|
93
|
+
public generateMarkdown(input: ReportInput): string {
|
|
94
|
+
const report = reportInputSchema.parse(input);
|
|
95
|
+
|
|
96
|
+
const entryPointsSection =
|
|
97
|
+
report.analysis.entryPoints.length > 0
|
|
98
|
+
? report.analysis.entryPoints
|
|
99
|
+
.map((entryPoint) => `- \`${entryPoint.symbol}\`: ${entryPoint.description} Evidence: ${entryPoint.evidence}`)
|
|
100
|
+
.join("\n")
|
|
101
|
+
: "- None identified";
|
|
102
|
+
|
|
103
|
+
const callGraphSection =
|
|
104
|
+
report.analysis.callGraph.length > 0
|
|
105
|
+
? report.analysis.callGraph
|
|
106
|
+
.map((edge) => `- \`${edge.caller}\` -> \`${edge.callee}\`: ${edge.rationale}`)
|
|
107
|
+
.join("\n")
|
|
108
|
+
: "- No clear call edges extracted";
|
|
109
|
+
|
|
110
|
+
const restoredNamesSection =
|
|
111
|
+
report.analysis.restoredNames.length > 0
|
|
112
|
+
? report.analysis.restoredNames
|
|
113
|
+
.map((entry) => `- \`${entry.originalName}\` -> \`${entry.suggestedName}\`: ${entry.justification}`)
|
|
114
|
+
.join("\n")
|
|
115
|
+
: "- No confident renames proposed";
|
|
116
|
+
|
|
117
|
+
const artifactSummarySection =
|
|
118
|
+
report.analysis.artifactSummaries.length > 0
|
|
119
|
+
? report.analysis.artifactSummaries
|
|
120
|
+
.map(
|
|
121
|
+
(summary) =>
|
|
122
|
+
`- ${summary.url} [${summary.type}] (${summary.chunkCount} chunk(s)): ${summary.summary}`,
|
|
123
|
+
)
|
|
124
|
+
.join("\n")
|
|
125
|
+
: "- None";
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
"# Mapr Reverse-Engineering Report",
|
|
129
|
+
"",
|
|
130
|
+
`- Target URL: ${report.targetUrl}`,
|
|
131
|
+
`- Generated: ${new Date().toISOString()}`,
|
|
132
|
+
`- Report status: ${report.reportStatus}`,
|
|
133
|
+
`- HTML pages crawled: ${report.htmlPages.length}`,
|
|
134
|
+
`- Artifacts analyzed: ${report.artifacts.length}`,
|
|
135
|
+
`- AI chunks analyzed: ${report.analysis.analyzedChunkCount}`,
|
|
136
|
+
"",
|
|
137
|
+
report.analysisError ? "## Analysis Status" : undefined,
|
|
138
|
+
report.analysisError ? `- Analysis ended early: ${report.analysisError}` : undefined,
|
|
139
|
+
report.analysisError ? "" : undefined,
|
|
140
|
+
"## Website Surface",
|
|
141
|
+
"",
|
|
142
|
+
formatBulletList(report.htmlPages, "No HTML pages crawled beyond the entry page"),
|
|
143
|
+
"",
|
|
144
|
+
"## Executive Summary",
|
|
145
|
+
"",
|
|
146
|
+
report.analysis.overview,
|
|
147
|
+
"",
|
|
148
|
+
"## Entry Points",
|
|
149
|
+
"",
|
|
150
|
+
entryPointsSection,
|
|
151
|
+
"",
|
|
152
|
+
"## Initialization Flow",
|
|
153
|
+
"",
|
|
154
|
+
formatBulletList(report.analysis.initializationFlow, "No initialization flow extracted"),
|
|
155
|
+
"",
|
|
156
|
+
"## Call Graph",
|
|
157
|
+
"",
|
|
158
|
+
callGraphSection,
|
|
159
|
+
"",
|
|
160
|
+
"## Restored Names",
|
|
161
|
+
"",
|
|
162
|
+
restoredNamesSection,
|
|
163
|
+
"",
|
|
164
|
+
"## Notable Libraries",
|
|
165
|
+
"",
|
|
166
|
+
formatBulletList(report.analysis.notableLibraries, "No notable libraries identified"),
|
|
167
|
+
"",
|
|
168
|
+
"## Investigation Tips",
|
|
169
|
+
"",
|
|
170
|
+
formatBulletList(report.analysis.investigationTips, "No investigation tips generated"),
|
|
171
|
+
"",
|
|
172
|
+
"## Risks And Observations",
|
|
173
|
+
"",
|
|
174
|
+
formatBulletList(report.analysis.risks, "No specific risks highlighted"),
|
|
175
|
+
"",
|
|
176
|
+
"## Artifact Summaries",
|
|
177
|
+
"",
|
|
178
|
+
artifactSummarySection,
|
|
179
|
+
"",
|
|
180
|
+
"## Downloaded Artifacts",
|
|
181
|
+
"",
|
|
182
|
+
formatArtifactTable(report.artifacts),
|
|
183
|
+
]
|
|
184
|
+
.filter((line): line is string => line !== undefined)
|
|
185
|
+
.join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public async writeReport(input: {
|
|
189
|
+
targetUrl: string;
|
|
190
|
+
htmlPages: string[];
|
|
191
|
+
reportStatus?: "complete" | "partial";
|
|
192
|
+
analysisError?: string;
|
|
193
|
+
artifacts: FormattedArtifact[];
|
|
194
|
+
analysis: BundleAnalysis;
|
|
195
|
+
outputPathOverride?: string;
|
|
196
|
+
}): Promise<string> {
|
|
197
|
+
const { outputPathOverride, ...reportInput } = input;
|
|
198
|
+
const validatedInput = reportInputSchema.parse(reportInput);
|
|
199
|
+
const reportContent = this.generateMarkdown(validatedInput);
|
|
200
|
+
const outputPath =
|
|
201
|
+
outputPathOverride !== undefined
|
|
202
|
+
? resolve(process.cwd(), outputPathOverride)
|
|
203
|
+
: resolve(
|
|
204
|
+
process.cwd(),
|
|
205
|
+
`report-${new URL(validatedInput.targetUrl).hostname.replace(/[^a-zA-Z0-9.-]/g, "-")}-${new Date()
|
|
206
|
+
.toISOString()
|
|
207
|
+
.replace(/[:.]/g, "-")}.md`,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
await writeFile(outputPath, `${reportContent}\n`, "utf8");
|
|
211
|
+
return outputPath;
|
|
212
|
+
}
|
|
213
|
+
}
|
package/lib/scraper.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
artifactCandidateSchema,
|
|
6
|
+
discoveredArtifactSchema,
|
|
7
|
+
extractArtifactCandidates,
|
|
8
|
+
extractNestedCandidates,
|
|
9
|
+
type ArtifactCandidate,
|
|
10
|
+
type DiscoveredArtifact,
|
|
11
|
+
} from "./artifacts";
|
|
12
|
+
import { WasmModuleSummarizer } from "./wasm";
|
|
13
|
+
|
|
14
|
+
const httpUrlSchema = z
|
|
15
|
+
.string()
|
|
16
|
+
.trim()
|
|
17
|
+
.url("Expected a valid URL.")
|
|
18
|
+
.refine((value) => /^https?:\/\//.test(value), "Expected an http or https URL.");
|
|
19
|
+
|
|
20
|
+
const scraperOptionsSchema = z.object({
|
|
21
|
+
maxPages: z.number().int().positive().default(10),
|
|
22
|
+
maxArtifacts: z.number().int().positive().default(200),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export interface ScrapeResult {
|
|
26
|
+
pageUrl: string;
|
|
27
|
+
artifacts: DiscoveredArtifact[];
|
|
28
|
+
htmlPages: string[];
|
|
29
|
+
scriptUrls: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
33
|
+
type ScraperOptions = z.input<typeof scraperOptionsSchema>;
|
|
34
|
+
|
|
35
|
+
function isPageCandidate(candidate: ArtifactCandidate, rootOrigin: string): boolean {
|
|
36
|
+
return candidate.type === "html" && new URL(candidate.url).origin === rootOrigin;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldFollowCandidate(candidate: ArtifactCandidate, rootOrigin: string): boolean {
|
|
40
|
+
if (candidate.type === "html") {
|
|
41
|
+
return new URL(candidate.url).origin === rootOrigin;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class BundleScraper {
|
|
48
|
+
private readonly options: z.infer<typeof scraperOptionsSchema>;
|
|
49
|
+
private readonly wasmSummarizer = new WasmModuleSummarizer();
|
|
50
|
+
|
|
51
|
+
public constructor(
|
|
52
|
+
private readonly fetcher: FetchLike = fetch,
|
|
53
|
+
options: ScraperOptions = {},
|
|
54
|
+
) {
|
|
55
|
+
this.options = scraperOptionsSchema.parse(options);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async scrape(pageUrl: string): Promise<ScrapeResult> {
|
|
59
|
+
const validatedPageUrl = httpUrlSchema.parse(pageUrl);
|
|
60
|
+
const rootOrigin = new URL(validatedPageUrl).origin;
|
|
61
|
+
const visitedUrls = new Set<string>();
|
|
62
|
+
const htmlPages = new Set<string>();
|
|
63
|
+
const artifacts: DiscoveredArtifact[] = [];
|
|
64
|
+
const queue: ArtifactCandidate[] = [
|
|
65
|
+
artifactCandidateSchema.parse({
|
|
66
|
+
url: validatedPageUrl,
|
|
67
|
+
type: "html",
|
|
68
|
+
discoveredFrom: "root",
|
|
69
|
+
}),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
while (queue.length > 0) {
|
|
73
|
+
if (artifacts.length >= this.options.maxArtifacts) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const candidate = queue.shift();
|
|
78
|
+
if (!candidate || visitedUrls.has(candidate.url)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!shouldFollowCandidate(candidate, rootOrigin)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isPageCandidate(candidate, rootOrigin) && htmlPages.size >= this.options.maxPages && candidate.url !== validatedPageUrl) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
visitedUrls.add(candidate.url);
|
|
91
|
+
const artifact = await this.fetchArtifact(candidate);
|
|
92
|
+
artifacts.push(artifact);
|
|
93
|
+
|
|
94
|
+
if (artifact.type === "html") {
|
|
95
|
+
htmlPages.add(artifact.url);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const nestedCandidates = extractNestedCandidates(artifact);
|
|
99
|
+
for (const nestedCandidate of nestedCandidates) {
|
|
100
|
+
if (!visitedUrls.has(nestedCandidate.url)) {
|
|
101
|
+
queue.push(nestedCandidate);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
pageUrl: validatedPageUrl,
|
|
108
|
+
artifacts,
|
|
109
|
+
htmlPages: [...htmlPages],
|
|
110
|
+
scriptUrls: artifacts
|
|
111
|
+
.filter((artifact) => artifact.type === "script" || artifact.type === "service-worker" || artifact.type === "worker")
|
|
112
|
+
.map((artifact) => artifact.url),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async fetchArtifact(candidate: ArtifactCandidate): Promise<DiscoveredArtifact> {
|
|
117
|
+
const response = await this.fetchResponse(candidate.url, candidate.type);
|
|
118
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
119
|
+
|
|
120
|
+
if (candidate.type === "wasm" || contentType.includes("application/wasm")) {
|
|
121
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
122
|
+
return discoveredArtifactSchema.parse({
|
|
123
|
+
url: candidate.url,
|
|
124
|
+
type: "wasm",
|
|
125
|
+
sizeBytes: bytes.byteLength,
|
|
126
|
+
content: this.wasmSummarizer.summarize({
|
|
127
|
+
url: candidate.url,
|
|
128
|
+
bytes,
|
|
129
|
+
}),
|
|
130
|
+
discoveredFrom: candidate.discoveredFrom,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const content = await response.text();
|
|
135
|
+
const resolvedType = contentType.includes("text/html") ? "html" : candidate.type;
|
|
136
|
+
|
|
137
|
+
return discoveredArtifactSchema.parse({
|
|
138
|
+
url: candidate.url,
|
|
139
|
+
type: resolvedType,
|
|
140
|
+
sizeBytes: Buffer.byteLength(content, "utf8"),
|
|
141
|
+
content,
|
|
142
|
+
discoveredFrom: candidate.discoveredFrom,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async fetchResponse(url: string, artifactType: ArtifactCandidate["type"]): Promise<Response> {
|
|
147
|
+
try {
|
|
148
|
+
const response = await this.fetcher(url, {
|
|
149
|
+
headers: {
|
|
150
|
+
"user-agent": "mapr/0.2.0",
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
throw new Error(`Failed to fetch ${artifactType} from ${url}: ${response.status} ${response.statusText}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return response;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error instanceof Error) {
|
|
161
|
+
throw new Error(`Unable to fetch ${artifactType} artifact ${url}: ${error.message}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw new Error(`Unable to fetch ${artifactType} artifact ${url}.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { extractArtifactCandidates };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const SWARM_AGENT_ORDER = ["scout", "runtime", "naming", "security", "synthesizer"] as const;
|
|
2
|
+
|
|
3
|
+
export type SwarmAgentName = (typeof SWARM_AGENT_ORDER)[number];
|
|
4
|
+
|
|
5
|
+
const GLOBAL_MISSION = [
|
|
6
|
+
"You are part of a senior reverse-engineering swarm focused on frontend delivery artifacts.",
|
|
7
|
+
"The target may contain minified JavaScript, Vite bundles, service workers, HTML shells, CSS assets, manifests, and WASM summaries.",
|
|
8
|
+
"Your job is to maximize signal, preserve uncertainty honestly, and infer execution flow with evidence instead of speculation.",
|
|
9
|
+
"Prefer concrete names, control-flow observations, runtime triggers, network boundaries, storage usage, background execution, and operator-facing investigation tips.",
|
|
10
|
+
"If code is obfuscated or incomplete, say so explicitly and still extract the strongest defensible conclusions.",
|
|
11
|
+
].join(" ");
|
|
12
|
+
|
|
13
|
+
export function getGlobalMissionPrompt(): string {
|
|
14
|
+
return GLOBAL_MISSION;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getSwarmAgentPrompt(agent: SwarmAgentName): string {
|
|
18
|
+
switch (agent) {
|
|
19
|
+
case "scout":
|
|
20
|
+
return [
|
|
21
|
+
GLOBAL_MISSION,
|
|
22
|
+
"You are the Scout agent.",
|
|
23
|
+
"Map the surface area of this artifact chunk.",
|
|
24
|
+
"Identify frameworks, runtime boundaries, imports, exported symbols, bootstrapping clues, worker registration, fetch calls, DOM hooks, storage access, cache usage, and suspected cross-artifact relationships.",
|
|
25
|
+
"Provide concise notes that other agents can build on.",
|
|
26
|
+
].join(" ");
|
|
27
|
+
case "runtime":
|
|
28
|
+
return [
|
|
29
|
+
GLOBAL_MISSION,
|
|
30
|
+
"You are the Runtime Flow agent.",
|
|
31
|
+
"Infer initialization order, triggers, lifecycle transitions, event wiring, entry points, and probable call relationships.",
|
|
32
|
+
"Use prior swarm notes as hard context and add only evidence-backed execution reasoning.",
|
|
33
|
+
].join(" ");
|
|
34
|
+
case "naming":
|
|
35
|
+
return [
|
|
36
|
+
GLOBAL_MISSION,
|
|
37
|
+
"You are the Semantic Naming agent.",
|
|
38
|
+
"Restore better names for opaque variables, functions, classes, and modules.",
|
|
39
|
+
"Anchor every rename suggestion in context, data flow, side effects, or call usage.",
|
|
40
|
+
].join(" ");
|
|
41
|
+
case "security":
|
|
42
|
+
return [
|
|
43
|
+
GLOBAL_MISSION,
|
|
44
|
+
"You are the Security and Operations agent.",
|
|
45
|
+
"Look for service worker risks, caching behavior, persistence, auth/session touchpoints, feature flags, telemetry, dynamic code loading, and WASM trust boundaries.",
|
|
46
|
+
"Output practical investigation tips that a human engineer should follow next.",
|
|
47
|
+
].join(" ");
|
|
48
|
+
case "synthesizer":
|
|
49
|
+
return [
|
|
50
|
+
GLOBAL_MISSION,
|
|
51
|
+
"You are the Synthesizer agent.",
|
|
52
|
+
"Merge all upstream swarm notes into a single precise chunk analysis object.",
|
|
53
|
+
"De-duplicate findings, preserve uncertainty, and optimize for a human reverse-engineer who needs a ready-to-use technical map.",
|
|
54
|
+
].join(" ");
|
|
55
|
+
}
|
|
56
|
+
}
|
package/lib/wasm.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const wasmInputSchema = z.object({
|
|
4
|
+
url: z.string().url(),
|
|
5
|
+
bytes: z.instanceof(Uint8Array),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
function extractPrintableStrings(bytes: Uint8Array): string[] {
|
|
9
|
+
const matches = new Set<string>();
|
|
10
|
+
let current = "";
|
|
11
|
+
|
|
12
|
+
for (const byte of bytes) {
|
|
13
|
+
if (byte >= 32 && byte <= 126) {
|
|
14
|
+
current += String.fromCharCode(byte);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (current.length >= 4) {
|
|
19
|
+
matches.add(current);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
current = "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (current.length >= 4) {
|
|
26
|
+
matches.add(current);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return [...matches].slice(0, 20);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class WasmModuleSummarizer {
|
|
33
|
+
public summarize(input: { url: string; bytes: Uint8Array }): string {
|
|
34
|
+
const validatedInput = wasmInputSchema.parse(input);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const module = new WebAssembly.Module(validatedInput.bytes);
|
|
38
|
+
const imports = WebAssembly.Module.imports(module);
|
|
39
|
+
const exports = WebAssembly.Module.exports(module);
|
|
40
|
+
const embeddedStrings = extractPrintableStrings(validatedInput.bytes);
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
`WASM module: ${validatedInput.url}`,
|
|
44
|
+
`Byte size: ${validatedInput.bytes.byteLength}`,
|
|
45
|
+
`Imports: ${
|
|
46
|
+
imports.length > 0
|
|
47
|
+
? imports.map((entry) => `${entry.module}.${entry.name} (${entry.kind})`).join(", ")
|
|
48
|
+
: "none"
|
|
49
|
+
}`,
|
|
50
|
+
`Exports: ${exports.length > 0 ? exports.map((entry) => `${entry.name} (${entry.kind})`).join(", ") : "none"}`,
|
|
51
|
+
`Embedded strings: ${embeddedStrings.length > 0 ? embeddedStrings.join(", ") : "none"}`,
|
|
52
|
+
].join("\n");
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
55
|
+
return [
|
|
56
|
+
`WASM module: ${validatedInput.url}`,
|
|
57
|
+
`Byte size: ${validatedInput.bytes.byteLength}`,
|
|
58
|
+
`Binary summary unavailable: ${message}`,
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redstone-md/mapr",
|
|
3
|
+
"version": "0.0.1-alpha",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Bun-native CLI/TUI for reverse-engineering frontend websites, bundles, WASM, and service workers",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/redstone-md/Mapr.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/redstone-md/Mapr",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/redstone-md/Mapr/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"bun",
|
|
17
|
+
"cli",
|
|
18
|
+
"tui",
|
|
19
|
+
"reverse-engineering",
|
|
20
|
+
"javascript",
|
|
21
|
+
"frontend",
|
|
22
|
+
"wasm",
|
|
23
|
+
"service-worker",
|
|
24
|
+
"ai"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"bun": ">=1.3.9"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"mapr": "./bin/mapr"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"index.ts",
|
|
35
|
+
"lib",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"start": "bun run index.ts",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"check": "bunx tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "bun test && bunx tsc --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@ai-sdk/openai": "3.0.41",
|
|
50
|
+
"@ai-sdk/openai-compatible": "2.0.35",
|
|
51
|
+
"@clack/prompts": "1.1.0",
|
|
52
|
+
"ai": "6.0.116",
|
|
53
|
+
"cheerio": "1.2.0",
|
|
54
|
+
"picocolors": "1.1.1",
|
|
55
|
+
"prettier": "3.8.1",
|
|
56
|
+
"zod": "4.3.6"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"bun-types": "1.3.10",
|
|
60
|
+
"typescript": "5.9.3"
|
|
61
|
+
}
|
|
62
|
+
}
|