@veolab/discoverylab 1.6.3 → 1.6.6
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/assets/applab-bundle-icon.png +0 -0
- package/assets/icons/icons8-claude-150.png +0 -0
- package/assets/icons/icons8-claude-500.png +0 -0
- package/dist/{chunk-HFN6BTVO.js → chunk-JAA53ES7.js} +1 -1
- package/dist/{chunk-XKX6NBHF.js → chunk-TWRWARU4.js} +52 -2
- package/dist/{chunk-IVX2OSOJ.js → chunk-XDUFCPOC.js} +198 -69
- package/dist/{chunk-5AISGCS4.js → chunk-YFF3M76J.js} +162 -33
- package/dist/cli.js +29 -18
- package/dist/export/infographic-template.html +392 -128
- package/dist/index.d.ts +30 -6
- package/dist/index.html +75 -21
- package/dist/index.js +4 -4
- package/dist/{infographic-GQAHEOAA.js → infographic-OSDIJM5M.js} +119 -17
- package/dist/mcpb/node_modules/@anthropic-ai/sdk/src/lib/.keep +4 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/better_sqlite3.node.d +1 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/obj.target/better_sqlite3/src/better_sqlite3.o.d +133 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/obj.target/deps/locate_sqlite3.stamp.d +1 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/obj.target/sqlite3/gen/sqlite3/sqlite3.o.d +4 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/obj.target/test_extension/deps/test_extension.o.d +7 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/sqlite3.a.d +1 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/Release/test_extension.node.d +1 -0
- package/dist/mcpb/node_modules/better-sqlite3/build/Release/.deps/ba23eeee118cd63e16015df367567cb043fed872.intermediate.d +1 -0
- package/dist/{server-W3JQ5RG7.js → server-GXNAKM4H.js} +1 -1
- package/dist/{server-OVOACIOJ.js → server-WN6DCCUA.js} +1 -1
- package/dist/{setup-F7MGEFIM.js → setup-SMN7FJNZ.js} +2 -2
- package/dist/{tools-VYFNRUS4.js → tools-6BTUMR3G.js} +3 -3
- package/package.json +8 -2
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"name": "discoverylab",
|
|
13
13
|
"source": ".",
|
|
14
14
|
"description": "AI-powered app testing & marketing asset generator. Record mobile/web apps, run automated tests with Maestro & Playwright, and generate professional screenshots, GIFs, and test reports.",
|
|
15
|
-
"version": "1.6.
|
|
15
|
+
"version": "1.6.4",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Anderson Melo"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discoverylab",
|
|
3
3
|
"description": "AI-powered app testing & marketing asset generator. Record mobile/web apps, run automated tests with Maestro & Playwright, and generate professional screenshots, GIFs, and test reports.",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Anderson Melo",
|
|
7
7
|
"email": "anderson.90@gmail.com"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
var MCPServer = class {
|
|
8
8
|
tools = /* @__PURE__ */ new Map();
|
|
9
|
+
resources = /* @__PURE__ */ new Map();
|
|
9
10
|
serverInfo = {
|
|
10
11
|
name: "discoverylab",
|
|
11
12
|
version: APP_VERSION
|
|
@@ -18,6 +19,15 @@ var MCPServer = class {
|
|
|
18
19
|
this.registerTool(tool);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
registerResource(resource) {
|
|
23
|
+
this.resources.set(resource.uri, resource);
|
|
24
|
+
}
|
|
25
|
+
upsertResourceContents(uri, resource) {
|
|
26
|
+
this.resources.set(uri, {
|
|
27
|
+
uri,
|
|
28
|
+
...resource
|
|
29
|
+
});
|
|
30
|
+
}
|
|
21
31
|
async handleRequest(request) {
|
|
22
32
|
const { id, method, params } = request;
|
|
23
33
|
try {
|
|
@@ -28,6 +38,10 @@ var MCPServer = class {
|
|
|
28
38
|
return this.handleToolsList(id);
|
|
29
39
|
case "tools/call":
|
|
30
40
|
return this.handleToolCall(id, params);
|
|
41
|
+
case "resources/list":
|
|
42
|
+
return this.handleResourcesList(id);
|
|
43
|
+
case "resources/read":
|
|
44
|
+
return this.handleResourcesRead(id, params);
|
|
31
45
|
case "ping":
|
|
32
46
|
return { jsonrpc: "2.0", id, result: { pong: true } };
|
|
33
47
|
default:
|
|
@@ -54,7 +68,8 @@ var MCPServer = class {
|
|
|
54
68
|
protocolVersion: "2024-11-05",
|
|
55
69
|
serverInfo: this.serverInfo,
|
|
56
70
|
capabilities: {
|
|
57
|
-
tools: {}
|
|
71
|
+
tools: {},
|
|
72
|
+
resources: {}
|
|
58
73
|
}
|
|
59
74
|
}
|
|
60
75
|
};
|
|
@@ -63,7 +78,8 @@ var MCPServer = class {
|
|
|
63
78
|
const tools = Array.from(this.tools.values()).map((tool) => ({
|
|
64
79
|
name: tool.name,
|
|
65
80
|
description: tool.description,
|
|
66
|
-
inputSchema: this.zodToJsonSchema(tool.inputSchema)
|
|
81
|
+
inputSchema: this.zodToJsonSchema(tool.inputSchema),
|
|
82
|
+
...tool._meta ? { _meta: tool._meta } : {}
|
|
67
83
|
}));
|
|
68
84
|
return {
|
|
69
85
|
jsonrpc: "2.0",
|
|
@@ -71,6 +87,40 @@ var MCPServer = class {
|
|
|
71
87
|
result: { tools }
|
|
72
88
|
};
|
|
73
89
|
}
|
|
90
|
+
handleResourcesList(id) {
|
|
91
|
+
const resources = Array.from(this.resources.values()).map((resource) => ({
|
|
92
|
+
uri: resource.uri,
|
|
93
|
+
name: resource.name,
|
|
94
|
+
...resource.title ? { title: resource.title } : {},
|
|
95
|
+
...resource.description ? { description: resource.description } : {},
|
|
96
|
+
mimeType: resource.mimeType
|
|
97
|
+
}));
|
|
98
|
+
return {
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
|
+
id,
|
|
101
|
+
result: { resources }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
handleResourcesRead(id, params) {
|
|
105
|
+
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
106
|
+
const resource = this.resources.get(uri);
|
|
107
|
+
if (!resource) {
|
|
108
|
+
return {
|
|
109
|
+
jsonrpc: "2.0",
|
|
110
|
+
id,
|
|
111
|
+
error: { code: -32602, message: `Resource not found: ${uri}` }
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const contents = Array.isArray(resource.contents) && resource.contents.length > 0 ? resource.contents : [{
|
|
115
|
+
uri: resource.uri,
|
|
116
|
+
mimeType: resource.mimeType
|
|
117
|
+
}];
|
|
118
|
+
return {
|
|
119
|
+
jsonrpc: "2.0",
|
|
120
|
+
id,
|
|
121
|
+
result: { contents }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
74
124
|
async handleToolCall(id, params) {
|
|
75
125
|
const { name, arguments: args } = params;
|
|
76
126
|
const tool = this.tools.get(name);
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
import {
|
|
68
68
|
DATA_DIR,
|
|
69
69
|
EXPORTS_DIR,
|
|
70
|
+
FRAMES_DIR,
|
|
70
71
|
PROJECTS_DIR,
|
|
71
72
|
frames,
|
|
72
73
|
getDatabase,
|
|
@@ -3707,7 +3708,7 @@ app.post("/api/upload", async (c) => {
|
|
|
3707
3708
|
const { exec: exec2 } = await import("child_process");
|
|
3708
3709
|
const { promisify } = await import("util");
|
|
3709
3710
|
const execAsync = promisify(exec2);
|
|
3710
|
-
const { PROJECTS_DIR: PROJECTS_DIR2, FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
3711
|
+
const { PROJECTS_DIR: PROJECTS_DIR2, FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
3711
3712
|
const id = crypto.randomUUID();
|
|
3712
3713
|
const now = /* @__PURE__ */ new Date();
|
|
3713
3714
|
const projectDir = join6(PROJECTS_DIR2, id);
|
|
@@ -3736,7 +3737,7 @@ app.post("/api/upload", async (c) => {
|
|
|
3736
3737
|
let framePaths = [];
|
|
3737
3738
|
try {
|
|
3738
3739
|
if (isVideo) {
|
|
3739
|
-
const projectFramesDir = join6(
|
|
3740
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
3740
3741
|
if (!fsExists(projectFramesDir)) {
|
|
3741
3742
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
3742
3743
|
}
|
|
@@ -4503,8 +4504,8 @@ app.post("/api/analyze/:id", async (c) => {
|
|
|
4503
4504
|
const { promisify } = await import("util");
|
|
4504
4505
|
const { mkdirSync: mkdirSync5, readdirSync: readdirSync5 } = await import("fs");
|
|
4505
4506
|
const execAsync = promisify(exec2);
|
|
4506
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
4507
|
-
const projectFramesDir = join6(
|
|
4507
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
4508
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
4508
4509
|
if (!existsSync5(projectFramesDir)) {
|
|
4509
4510
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
4510
4511
|
}
|
|
@@ -5007,11 +5008,11 @@ app.get("/api/grid/project-frames/:id", async (c) => {
|
|
|
5007
5008
|
return c.json({ error: "Project not found" }, 404);
|
|
5008
5009
|
}
|
|
5009
5010
|
const project = result[0];
|
|
5010
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
5011
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
5011
5012
|
const { readdirSync: readdirSync5 } = await import("fs");
|
|
5012
5013
|
const availableFrames = [];
|
|
5013
5014
|
const { isBlankFrame } = await import("./frames-2NFCSKXQ.js");
|
|
5014
|
-
const projectFramesDir = join6(
|
|
5015
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
5015
5016
|
if (existsSync5(projectFramesDir)) {
|
|
5016
5017
|
const frameFiles = readdirSync5(projectFramesDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
|
|
5017
5018
|
for (const f of frameFiles) {
|
|
@@ -5138,18 +5139,43 @@ function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath)
|
|
|
5138
5139
|
}
|
|
5139
5140
|
return null;
|
|
5140
5141
|
}
|
|
5141
|
-
function
|
|
5142
|
-
|
|
5143
|
-
|
|
5142
|
+
function collectProjectExportFramePaths(projectId, recordingBaseDir) {
|
|
5143
|
+
const candidateDirs = [
|
|
5144
|
+
join6(FRAMES_DIR, projectId),
|
|
5145
|
+
join6(PROJECTS_DIR, projectId, "frames"),
|
|
5146
|
+
recordingBaseDir ? join6(recordingBaseDir, "screenshots") : null,
|
|
5147
|
+
recordingBaseDir
|
|
5148
|
+
].filter((value, index, items) => !!value && items.indexOf(value) === index);
|
|
5149
|
+
const framePaths = [];
|
|
5150
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5151
|
+
for (const dirPath of candidateDirs) {
|
|
5152
|
+
if (!existsSync5(dirPath) || !statSync3(dirPath).isDirectory()) {
|
|
5153
|
+
continue;
|
|
5154
|
+
}
|
|
5155
|
+
const files = readdirSync4(dirPath).filter((entry) => /\.(png|jpg|jpeg|webp)$/i.test(entry)).filter((entry) => !entry.startsWith("._")).sort();
|
|
5156
|
+
for (const entry of files) {
|
|
5157
|
+
const absolutePath = join6(dirPath, entry);
|
|
5158
|
+
if (seen.has(absolutePath)) {
|
|
5159
|
+
continue;
|
|
5160
|
+
}
|
|
5161
|
+
seen.add(absolutePath);
|
|
5162
|
+
framePaths.push(absolutePath);
|
|
5163
|
+
}
|
|
5144
5164
|
}
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5165
|
+
return framePaths;
|
|
5166
|
+
}
|
|
5167
|
+
function resolveAppLabBundleIconPath() {
|
|
5168
|
+
const candidates = [
|
|
5169
|
+
join6(__dirname, "..", "..", "assets", "applab-bundle-icon.png"),
|
|
5170
|
+
join6(__dirname, "..", "assets", "applab-bundle-icon.png"),
|
|
5171
|
+
join6(process.cwd(), "assets", "applab-bundle-icon.png")
|
|
5172
|
+
];
|
|
5173
|
+
for (const candidate of candidates) {
|
|
5174
|
+
if (existsSync5(candidate)) {
|
|
5175
|
+
return candidate;
|
|
5150
5176
|
}
|
|
5151
5177
|
}
|
|
5152
|
-
return
|
|
5178
|
+
return null;
|
|
5153
5179
|
}
|
|
5154
5180
|
async function runExportCommand(command, args, cwd) {
|
|
5155
5181
|
await new Promise((resolve, reject) => {
|
|
@@ -5209,6 +5235,56 @@ function normalizePersistedLocalESVPServerUrl(serverUrl, connectionMode) {
|
|
|
5209
5235
|
function resolvePersistedLocalESVPServerUrl(serverUrl, esvp) {
|
|
5210
5236
|
return normalizePersistedLocalESVPServerUrl(serverUrl, "local") || resolveProjectESVPServerUrl(esvp) || LOCAL_ESVP_SERVER_URL;
|
|
5211
5237
|
}
|
|
5238
|
+
function cloneJsonRecord(value) {
|
|
5239
|
+
if (!value) return value;
|
|
5240
|
+
try {
|
|
5241
|
+
return JSON.parse(JSON.stringify(value));
|
|
5242
|
+
} catch {
|
|
5243
|
+
return value;
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
function detachPortableESVPForExport(esvp, snapshotAttached = false) {
|
|
5247
|
+
if (!esvp) {
|
|
5248
|
+
return { value: null, detached: false };
|
|
5249
|
+
}
|
|
5250
|
+
const cloned = cloneJsonRecord(esvp) || {};
|
|
5251
|
+
const connectionMode = typeof cloned.connectionMode === "string" ? cloned.connectionMode.trim().toLowerCase() : "";
|
|
5252
|
+
const serverUrl = typeof cloned.serverUrl === "string" ? cloned.serverUrl.trim() : "";
|
|
5253
|
+
const shouldDetach = connectionMode === "local" || !serverUrl;
|
|
5254
|
+
if (!shouldDetach) {
|
|
5255
|
+
return { value: cloned, detached: false };
|
|
5256
|
+
}
|
|
5257
|
+
cloned.currentSessionId = null;
|
|
5258
|
+
cloned.serverUrl = null;
|
|
5259
|
+
cloned.detachedForExport = true;
|
|
5260
|
+
cloned.snapshotAttached = snapshotAttached;
|
|
5261
|
+
if (cloned.network && typeof cloned.network === "object") {
|
|
5262
|
+
const network = cloned.network;
|
|
5263
|
+
network.sourceSessionId = null;
|
|
5264
|
+
network.activeCaptureSessionId = null;
|
|
5265
|
+
network.detachedForExport = true;
|
|
5266
|
+
}
|
|
5267
|
+
if (cloned.validation && typeof cloned.validation === "object") {
|
|
5268
|
+
const validation = cloned.validation;
|
|
5269
|
+
validation.sourceSessionId = null;
|
|
5270
|
+
validation.replaySessionId = null;
|
|
5271
|
+
validation.detachedForExport = true;
|
|
5272
|
+
}
|
|
5273
|
+
return { value: cloned, detached: true };
|
|
5274
|
+
}
|
|
5275
|
+
function detachPortableSessionDataForExport(sessionData, detachedEsvp, detached = false) {
|
|
5276
|
+
if (!sessionData) return null;
|
|
5277
|
+
const cloned = cloneJsonRecord(sessionData) || {};
|
|
5278
|
+
if (detached) {
|
|
5279
|
+
cloned.esvp = detachedEsvp;
|
|
5280
|
+
if (cloned.networkCapture && typeof cloned.networkCapture === "object") {
|
|
5281
|
+
const networkCapture = cloned.networkCapture;
|
|
5282
|
+
networkCapture.sessionId = null;
|
|
5283
|
+
networkCapture.detachedForExport = true;
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
return cloned;
|
|
5287
|
+
}
|
|
5212
5288
|
function isESVPReplayValidationSupported(result) {
|
|
5213
5289
|
if (!result) return true;
|
|
5214
5290
|
if (result.supported === false) return false;
|
|
@@ -5282,7 +5358,6 @@ app.post("/api/export", async (c) => {
|
|
|
5282
5358
|
const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber);
|
|
5283
5359
|
const exportRecords = await db.select().from(projectExports).where(eq(projectExports.projectId, projectId)).orderBy(desc(projectExports.createdAt));
|
|
5284
5360
|
const recordingBaseDir = resolveProjectBundleRecordingDir(rawProject.videoPath, resolvedVideoPath);
|
|
5285
|
-
const exportArtifactsDir = join6(EXPORTS_DIR, projectId);
|
|
5286
5361
|
const sessionPath = recordingBaseDir ? join6(recordingBaseDir, "session.json") : null;
|
|
5287
5362
|
let sessionData = null;
|
|
5288
5363
|
let networkEntries = [];
|
|
@@ -5307,8 +5382,7 @@ app.post("/api/export", async (c) => {
|
|
|
5307
5382
|
const summaryPath = rawProject.aiSummary ? "analysis/app-intelligence.md" : null;
|
|
5308
5383
|
const ocrPath = rawProject.ocrText ? "analysis/ocr.txt" : null;
|
|
5309
5384
|
const thumbnailName = rawProject.thumbnailPath ? basename3(rawProject.thumbnailPath) : null;
|
|
5310
|
-
|
|
5311
|
-
const bundledFrames = projectFrames.map((frame) => {
|
|
5385
|
+
let bundledFrames = projectFrames.map((frame) => {
|
|
5312
5386
|
const extensionMatch = basename3(frame.imagePath).match(/(\.[^.]+)$/);
|
|
5313
5387
|
const extension = extensionMatch ? extensionMatch[1] : ".png";
|
|
5314
5388
|
const relativeImagePath = `frames/frame-${String(frame.frameNumber).padStart(4, "0")}${extension}`;
|
|
@@ -5318,19 +5392,39 @@ app.post("/api/export", async (c) => {
|
|
|
5318
5392
|
imagePath: relativeImagePath
|
|
5319
5393
|
};
|
|
5320
5394
|
});
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5395
|
+
if (bundledFrames.length === 0) {
|
|
5396
|
+
const fallbackFramePaths = collectProjectExportFramePaths(projectId, recordingBaseDir);
|
|
5397
|
+
bundledFrames = fallbackFramePaths.map((framePath, index) => {
|
|
5398
|
+
const extensionMatch = basename3(framePath).match(/(\.[^.]+)$/);
|
|
5399
|
+
const extension = extensionMatch ? extensionMatch[1] : ".png";
|
|
5400
|
+
const relativeImagePath = `frames/frame-${String(index + 1).padStart(4, "0")}${extension}`;
|
|
5401
|
+
copyPathIntoExportBundle(framePath, join6(bundleRoot, relativeImagePath));
|
|
5402
|
+
return {
|
|
5403
|
+
id: `${projectId}-fallback-frame-${index + 1}`,
|
|
5404
|
+
projectId,
|
|
5405
|
+
frameNumber: index + 1,
|
|
5406
|
+
imagePath: relativeImagePath,
|
|
5407
|
+
ocrText: null,
|
|
5408
|
+
timestamp,
|
|
5409
|
+
createdAt: new Date(timestamp),
|
|
5410
|
+
isKeyFrame: null
|
|
5411
|
+
};
|
|
5412
|
+
});
|
|
5326
5413
|
}
|
|
5414
|
+
const mediaFiles = [];
|
|
5327
5415
|
if (rawProject.thumbnailPath && existsSync5(rawProject.thumbnailPath) && thumbnailName) {
|
|
5328
5416
|
const relativePath = `media/${thumbnailName}`;
|
|
5329
5417
|
copyPathIntoExportBundle(rawProject.thumbnailPath, join6(bundleRoot, relativePath));
|
|
5330
5418
|
mediaFiles.push({ role: "thumbnail", path: relativePath });
|
|
5331
5419
|
}
|
|
5332
|
-
const
|
|
5333
|
-
const
|
|
5420
|
+
const canonicalBundleIconPath = resolveAppLabBundleIconPath();
|
|
5421
|
+
const bundleIconRelativePath = canonicalBundleIconPath ? "media/icon.png" : null;
|
|
5422
|
+
if (canonicalBundleIconPath && bundleIconRelativePath) {
|
|
5423
|
+
copyPathIntoExportBundle(canonicalBundleIconPath, join6(bundleRoot, bundleIconRelativePath));
|
|
5424
|
+
mediaFiles.push({ role: "icon", path: bundleIconRelativePath });
|
|
5425
|
+
}
|
|
5426
|
+
const recordingIncluded = false;
|
|
5427
|
+
const exportArtifactCount = 0;
|
|
5334
5428
|
const esvpSessionId = resolveProjectESVPSessionId(esvp);
|
|
5335
5429
|
const esvpServerUrl = resolveProjectESVPServerUrl(esvp);
|
|
5336
5430
|
let esvpSnapshot = null;
|
|
@@ -5350,11 +5444,23 @@ app.post("/api/export", async (c) => {
|
|
|
5350
5444
|
};
|
|
5351
5445
|
}
|
|
5352
5446
|
}
|
|
5447
|
+
const detachedEsvpResult = detachPortableESVPForExport(esvp, !!esvpSnapshot);
|
|
5448
|
+
const exportedESVP = detachedEsvpResult.value;
|
|
5449
|
+
const exportedSessionData = detachPortableSessionDataForExport(
|
|
5450
|
+
sessionData,
|
|
5451
|
+
exportedESVP,
|
|
5452
|
+
detachedEsvpResult.detached
|
|
5453
|
+
);
|
|
5454
|
+
const exportedESVPSessionId = detachedEsvpResult.detached ? null : esvpSessionId;
|
|
5353
5455
|
const packagedProject = {
|
|
5354
5456
|
...normalizedProject,
|
|
5355
|
-
videoPath:
|
|
5457
|
+
videoPath: null,
|
|
5356
5458
|
thumbnailPath: thumbnailName ? `media/${thumbnailName}` : normalizedProject.thumbnailPath,
|
|
5357
|
-
frames: bundledFrames
|
|
5459
|
+
frames: bundledFrames,
|
|
5460
|
+
icon: bundleIconRelativePath ? {
|
|
5461
|
+
path: bundleIconRelativePath,
|
|
5462
|
+
kind: "app-icon"
|
|
5463
|
+
} : null
|
|
5358
5464
|
};
|
|
5359
5465
|
writeExportJson(join6(bundleRoot, "manifest.json"), {
|
|
5360
5466
|
bundleVersion: 1,
|
|
@@ -5368,6 +5474,10 @@ app.post("/api/export", async (c) => {
|
|
|
5368
5474
|
id: rawProject.id,
|
|
5369
5475
|
name: rawProject.name,
|
|
5370
5476
|
platform: rawProject.platform || null,
|
|
5477
|
+
icon: bundleIconRelativePath ? {
|
|
5478
|
+
path: bundleIconRelativePath,
|
|
5479
|
+
kind: "app-icon"
|
|
5480
|
+
} : null,
|
|
5371
5481
|
frameCount: bundledFrames.length,
|
|
5372
5482
|
hasRecordingFolder: recordingIncluded,
|
|
5373
5483
|
hasNetworkTrace: networkEntries.length > 0 || !!networkCapture,
|
|
@@ -5381,13 +5491,13 @@ app.post("/api/export", async (c) => {
|
|
|
5381
5491
|
recordingFolder: recordingIncluded,
|
|
5382
5492
|
networkEntries: networkEntries.length,
|
|
5383
5493
|
exportArtifacts: exportArtifactCount,
|
|
5384
|
-
esvpSessionId:
|
|
5494
|
+
esvpSessionId: exportedESVPSessionId || null
|
|
5385
5495
|
}
|
|
5386
5496
|
});
|
|
5387
5497
|
writeExportJson(join6(bundleRoot, "metadata", "project.json"), packagedProject);
|
|
5388
5498
|
writeExportJson(join6(bundleRoot, "metadata", "exports.json"), exportRecords);
|
|
5389
|
-
if (
|
|
5390
|
-
writeExportJson(join6(bundleRoot, "metadata", "session.json"),
|
|
5499
|
+
if (exportedSessionData) {
|
|
5500
|
+
writeExportJson(join6(bundleRoot, "metadata", "session.json"), exportedSessionData);
|
|
5391
5501
|
}
|
|
5392
5502
|
if (rawProject.taskHubLinks) {
|
|
5393
5503
|
writeExportJson(join6(bundleRoot, "taskhub", "links.json"), normalizedProject.taskHubLinks);
|
|
@@ -5413,24 +5523,12 @@ app.post("/api/export", async (c) => {
|
|
|
5413
5523
|
if (networkCapture) {
|
|
5414
5524
|
writeExportJson(join6(bundleRoot, "network", "capture.json"), networkCapture);
|
|
5415
5525
|
}
|
|
5416
|
-
if (
|
|
5417
|
-
writeExportJson(join6(bundleRoot, "network", "esvp.json"),
|
|
5526
|
+
if (exportedESVP) {
|
|
5527
|
+
writeExportJson(join6(bundleRoot, "network", "esvp.json"), exportedESVP);
|
|
5418
5528
|
}
|
|
5419
5529
|
if (esvpSnapshot) {
|
|
5420
5530
|
writeExportJson(join6(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
|
|
5421
5531
|
}
|
|
5422
|
-
const projectExportsDir = join6(EXPORTS_DIR, projectId);
|
|
5423
|
-
if (existsSync5(projectExportsDir) && statSync3(projectExportsDir).isDirectory()) {
|
|
5424
|
-
const rendersDir = join6(bundleRoot, "renders");
|
|
5425
|
-
mkdirSync4(rendersDir, { recursive: true });
|
|
5426
|
-
const exportFiles = readdirSync4(projectExportsDir);
|
|
5427
|
-
for (const f of exportFiles) {
|
|
5428
|
-
const src = join6(projectExportsDir, f);
|
|
5429
|
-
if (statSync3(src).isFile()) {
|
|
5430
|
-
cpSync(src, join6(rendersDir, f));
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
}
|
|
5434
5532
|
const templateContentPath = join6(PROJECTS_DIR, projectId, "template-content.json");
|
|
5435
5533
|
if (existsSync5(templateContentPath)) {
|
|
5436
5534
|
mkdirSync4(join6(bundleRoot, "templates"), { recursive: true });
|
|
@@ -5443,12 +5541,16 @@ app.post("/api/export", async (c) => {
|
|
|
5443
5541
|
"This package bundles the local project context for sharing or re-analysis.",
|
|
5444
5542
|
"",
|
|
5445
5543
|
"Included when available:",
|
|
5446
|
-
"-
|
|
5447
|
-
"-
|
|
5544
|
+
"- selected thumbnail and analyzed frames",
|
|
5545
|
+
"- lightweight project/session metadata",
|
|
5448
5546
|
"- OCR text and app intelligence summary",
|
|
5449
5547
|
"- network trace, capture metadata, and ESVP snapshot",
|
|
5450
|
-
"-
|
|
5451
|
-
"
|
|
5548
|
+
"- Task Hub links, requirements, and test map",
|
|
5549
|
+
"",
|
|
5550
|
+
"Excluded by default to keep the bundle Claude-friendly:",
|
|
5551
|
+
"- original long-form media",
|
|
5552
|
+
"- recording folder",
|
|
5553
|
+
"- generated export assets and renders"
|
|
5452
5554
|
].join("\n"));
|
|
5453
5555
|
outputPath = join6(exportDir, `export-${timestamp}.${format}`);
|
|
5454
5556
|
mimeType = "application/zip";
|
|
@@ -5609,8 +5711,8 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
|
|
|
5609
5711
|
const db = getDatabase();
|
|
5610
5712
|
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
5611
5713
|
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
5612
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
5613
|
-
const projectFramesDir = join6(
|
|
5714
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
5715
|
+
const projectFramesDir = join6(FRAMES_DIR2, projectId);
|
|
5614
5716
|
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(10);
|
|
5615
5717
|
let frameImages = [];
|
|
5616
5718
|
if (dbFrames.length > 0) {
|
|
@@ -5940,27 +6042,54 @@ app.post("/api/export/infographic", async (c) => {
|
|
|
5940
6042
|
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
5941
6043
|
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
5942
6044
|
const { FRAMES_DIR: fDir, EXPORTS_DIR: eDir, PROJECTS_DIR: pDir } = await import("./db-5ECN3O7F.js");
|
|
5943
|
-
const {
|
|
6045
|
+
const { buildInfographicData, generateInfographicHtml, resolveInfographicFrameInputs } = await import("./infographic-OSDIJM5M.js");
|
|
5944
6046
|
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(20);
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6047
|
+
const resolvedFrames = resolveInfographicFrameInputs(
|
|
6048
|
+
dbFrames,
|
|
6049
|
+
join6(fDir, projectId),
|
|
6050
|
+
project.videoPath,
|
|
6051
|
+
pDir,
|
|
6052
|
+
projectId
|
|
6053
|
+
);
|
|
6054
|
+
if (resolvedFrames.frameFiles.length === 0) {
|
|
6055
|
+
return c.json({
|
|
6056
|
+
error: resolvedFrames.candidateCount > 0 ? "No readable frames found for infographic export." : "No frames found. Run analyzer first.",
|
|
6057
|
+
debug: {
|
|
6058
|
+
frameCandidates: resolvedFrames.candidateCount,
|
|
6059
|
+
validFrames: 0,
|
|
6060
|
+
source: resolvedFrames.source,
|
|
6061
|
+
invalidFrames: resolvedFrames.invalidFrames.slice(0, 5)
|
|
6062
|
+
}
|
|
6063
|
+
}, 400);
|
|
5956
6064
|
}
|
|
5957
6065
|
const cached = annotationCache.get(projectId);
|
|
5958
6066
|
const annotations = cached?.steps?.map((s) => ({ label: s }));
|
|
5959
|
-
const data = buildInfographicData(project, frameFiles, frameOcr, annotations);
|
|
6067
|
+
const data = buildInfographicData(project, resolvedFrames.frameFiles, resolvedFrames.frameOcr, annotations);
|
|
6068
|
+
if (data.frames.length === 0) {
|
|
6069
|
+
return c.json({
|
|
6070
|
+
error: "Infographic export produced no embeddable frames.",
|
|
6071
|
+
debug: {
|
|
6072
|
+
frameCandidates: resolvedFrames.candidateCount,
|
|
6073
|
+
validFrames: resolvedFrames.frameFiles.length,
|
|
6074
|
+
source: resolvedFrames.source,
|
|
6075
|
+
invalidFrames: resolvedFrames.invalidFrames.slice(0, 5)
|
|
6076
|
+
}
|
|
6077
|
+
}, 400);
|
|
6078
|
+
}
|
|
5960
6079
|
const slug = (project.marketingTitle || project.name || projectId).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
5961
6080
|
const outputPath = join6(eDir, `${slug}-infographic.html`);
|
|
5962
6081
|
const result = generateInfographicHtml(data, outputPath);
|
|
5963
|
-
if (!result.success)
|
|
6082
|
+
if (!result.success) {
|
|
6083
|
+
return c.json({
|
|
6084
|
+
error: result.error,
|
|
6085
|
+
debug: {
|
|
6086
|
+
frameCandidates: resolvedFrames.candidateCount,
|
|
6087
|
+
validFrames: resolvedFrames.frameFiles.length,
|
|
6088
|
+
source: resolvedFrames.source,
|
|
6089
|
+
invalidFrames: resolvedFrames.invalidFrames.slice(0, 5)
|
|
6090
|
+
}
|
|
6091
|
+
}, 500);
|
|
6092
|
+
}
|
|
5964
6093
|
if (open) {
|
|
5965
6094
|
const { exec: exec2 } = await import("child_process");
|
|
5966
6095
|
exec2(`open "${result.outputPath}"`);
|
|
@@ -6006,9 +6135,9 @@ app.get("/api/export/document/:projectId", async (c) => {
|
|
|
6006
6135
|
const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(20);
|
|
6007
6136
|
let frameData = projectFrames.map((f) => ({ imagePath: f.imagePath, ocrText: f.ocrText }));
|
|
6008
6137
|
if (frameData.length === 0 && project.videoPath) {
|
|
6009
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
6138
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
6010
6139
|
const dirs = [
|
|
6011
|
-
join6(
|
|
6140
|
+
join6(FRAMES_DIR2, projectId),
|
|
6012
6141
|
join6(project.videoPath, "screenshots"),
|
|
6013
6142
|
join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
|
|
6014
6143
|
join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
|
|
@@ -6085,7 +6214,7 @@ app.post("/api/export/batch", async (c) => {
|
|
|
6085
6214
|
if (!manifest.destination?.type) {
|
|
6086
6215
|
return c.json({ error: "Destination type required" }, 400);
|
|
6087
6216
|
}
|
|
6088
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
6217
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
6089
6218
|
const dataProvider = {
|
|
6090
6219
|
async getProject(projectId) {
|
|
6091
6220
|
const db2 = getDatabase();
|
|
@@ -6093,7 +6222,7 @@ app.post("/api/export/batch", async (c) => {
|
|
|
6093
6222
|
return p || null;
|
|
6094
6223
|
},
|
|
6095
6224
|
getFramesDir(projectId) {
|
|
6096
|
-
return join6(
|
|
6225
|
+
return join6(FRAMES_DIR2, projectId);
|
|
6097
6226
|
}
|
|
6098
6227
|
};
|
|
6099
6228
|
const result = await executeBatchExport(manifest, dataProvider, (progress) => {
|
|
@@ -10112,7 +10241,7 @@ app.get("/api/mobile-chat/providers", async (c) => {
|
|
|
10112
10241
|
});
|
|
10113
10242
|
app.get("/api/setup/status", async (c) => {
|
|
10114
10243
|
try {
|
|
10115
|
-
const { setupStatusTool } = await import("./setup-
|
|
10244
|
+
const { setupStatusTool } = await import("./setup-SMN7FJNZ.js");
|
|
10116
10245
|
const result = await setupStatusTool.handler({});
|
|
10117
10246
|
const data = JSON.parse(result.content[0].text);
|
|
10118
10247
|
const idbInstalled = await isIdbInstalled().catch(() => false);
|