@veolab/discoverylab 1.4.4 → 1.6.5
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/README.md +70 -211
- 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-CUBQRT5L.js → chunk-JAA53ES7.js} +111 -2
- package/dist/{chunk-HB3YPWF3.js → chunk-Q7Q3A2ZI.js} +301 -10
- package/dist/{chunk-XKX6NBHF.js → chunk-TWRWARU4.js} +52 -2
- package/dist/{chunk-2UUMLAVR.js → chunk-V6RREMYD.js} +332 -38
- package/dist/cli.js +164 -28
- package/dist/export/infographic-template.html +254 -0
- package/dist/import-W2JEW254.js +180 -0
- package/dist/index.d.ts +30 -6
- package/dist/index.html +473 -11
- package/dist/index.js +5 -5
- package/dist/infographic-GQAHEOAA.js +183 -0
- 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-QFNKZCOJ.js → server-C2NZM2RV.js} +1 -1
- package/dist/{server-OVOACIOJ.js → server-WN6DCCUA.js} +1 -1
- package/dist/{setup-6JJYKKBS.js → setup-SMN7FJNZ.js} +5 -2
- package/dist/{tools-Q7OZO732.js → tools-VXU3JEQP.js} +6 -4
- package/doc/esvp-protocol.md +116 -0
- package/package.json +9 -3
- package/skills/knowledge-brain/SKILL.md +44 -43
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
createNotionPage
|
|
4
4
|
} from "./chunk-34GGYFXX.js";
|
|
5
5
|
import {
|
|
6
|
+
getCachedRender,
|
|
6
7
|
getRenderJob,
|
|
7
8
|
startRender
|
|
8
9
|
} from "./chunk-6GK5K6CS.js";
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
import {
|
|
67
68
|
DATA_DIR,
|
|
68
69
|
EXPORTS_DIR,
|
|
70
|
+
FRAMES_DIR,
|
|
69
71
|
PROJECTS_DIR,
|
|
70
72
|
frames,
|
|
71
73
|
getDatabase,
|
|
@@ -3706,7 +3708,7 @@ app.post("/api/upload", async (c) => {
|
|
|
3706
3708
|
const { exec: exec2 } = await import("child_process");
|
|
3707
3709
|
const { promisify } = await import("util");
|
|
3708
3710
|
const execAsync = promisify(exec2);
|
|
3709
|
-
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");
|
|
3710
3712
|
const id = crypto.randomUUID();
|
|
3711
3713
|
const now = /* @__PURE__ */ new Date();
|
|
3712
3714
|
const projectDir = join6(PROJECTS_DIR2, id);
|
|
@@ -3735,7 +3737,7 @@ app.post("/api/upload", async (c) => {
|
|
|
3735
3737
|
let framePaths = [];
|
|
3736
3738
|
try {
|
|
3737
3739
|
if (isVideo) {
|
|
3738
|
-
const projectFramesDir = join6(
|
|
3740
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
3739
3741
|
if (!fsExists(projectFramesDir)) {
|
|
3740
3742
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
3741
3743
|
}
|
|
@@ -4502,8 +4504,8 @@ app.post("/api/analyze/:id", async (c) => {
|
|
|
4502
4504
|
const { promisify } = await import("util");
|
|
4503
4505
|
const { mkdirSync: mkdirSync5, readdirSync: readdirSync5 } = await import("fs");
|
|
4504
4506
|
const execAsync = promisify(exec2);
|
|
4505
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
4506
|
-
const projectFramesDir = join6(
|
|
4507
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
4508
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
4507
4509
|
if (!existsSync5(projectFramesDir)) {
|
|
4508
4510
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
4509
4511
|
}
|
|
@@ -5006,11 +5008,11 @@ app.get("/api/grid/project-frames/:id", async (c) => {
|
|
|
5006
5008
|
return c.json({ error: "Project not found" }, 404);
|
|
5007
5009
|
}
|
|
5008
5010
|
const project = result[0];
|
|
5009
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
5011
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
5010
5012
|
const { readdirSync: readdirSync5 } = await import("fs");
|
|
5011
5013
|
const availableFrames = [];
|
|
5012
5014
|
const { isBlankFrame } = await import("./frames-2NFCSKXQ.js");
|
|
5013
|
-
const projectFramesDir = join6(
|
|
5015
|
+
const projectFramesDir = join6(FRAMES_DIR2, id);
|
|
5014
5016
|
if (existsSync5(projectFramesDir)) {
|
|
5015
5017
|
const frameFiles = readdirSync5(projectFramesDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
|
|
5016
5018
|
for (const f of frameFiles) {
|
|
@@ -5137,18 +5139,43 @@ function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath)
|
|
|
5137
5139
|
}
|
|
5138
5140
|
return null;
|
|
5139
5141
|
}
|
|
5140
|
-
function
|
|
5141
|
-
|
|
5142
|
-
|
|
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
|
+
}
|
|
5143
5164
|
}
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
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;
|
|
5149
5176
|
}
|
|
5150
5177
|
}
|
|
5151
|
-
return
|
|
5178
|
+
return null;
|
|
5152
5179
|
}
|
|
5153
5180
|
async function runExportCommand(command, args, cwd) {
|
|
5154
5181
|
await new Promise((resolve, reject) => {
|
|
@@ -5281,7 +5308,6 @@ app.post("/api/export", async (c) => {
|
|
|
5281
5308
|
const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber);
|
|
5282
5309
|
const exportRecords = await db.select().from(projectExports).where(eq(projectExports.projectId, projectId)).orderBy(desc(projectExports.createdAt));
|
|
5283
5310
|
const recordingBaseDir = resolveProjectBundleRecordingDir(rawProject.videoPath, resolvedVideoPath);
|
|
5284
|
-
const exportArtifactsDir = join6(EXPORTS_DIR, projectId);
|
|
5285
5311
|
const sessionPath = recordingBaseDir ? join6(recordingBaseDir, "session.json") : null;
|
|
5286
5312
|
let sessionData = null;
|
|
5287
5313
|
let networkEntries = [];
|
|
@@ -5306,8 +5332,7 @@ app.post("/api/export", async (c) => {
|
|
|
5306
5332
|
const summaryPath = rawProject.aiSummary ? "analysis/app-intelligence.md" : null;
|
|
5307
5333
|
const ocrPath = rawProject.ocrText ? "analysis/ocr.txt" : null;
|
|
5308
5334
|
const thumbnailName = rawProject.thumbnailPath ? basename3(rawProject.thumbnailPath) : null;
|
|
5309
|
-
|
|
5310
|
-
const bundledFrames = projectFrames.map((frame) => {
|
|
5335
|
+
let bundledFrames = projectFrames.map((frame) => {
|
|
5311
5336
|
const extensionMatch = basename3(frame.imagePath).match(/(\.[^.]+)$/);
|
|
5312
5337
|
const extension = extensionMatch ? extensionMatch[1] : ".png";
|
|
5313
5338
|
const relativeImagePath = `frames/frame-${String(frame.frameNumber).padStart(4, "0")}${extension}`;
|
|
@@ -5317,19 +5342,39 @@ app.post("/api/export", async (c) => {
|
|
|
5317
5342
|
imagePath: relativeImagePath
|
|
5318
5343
|
};
|
|
5319
5344
|
});
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5345
|
+
if (bundledFrames.length === 0) {
|
|
5346
|
+
const fallbackFramePaths = collectProjectExportFramePaths(projectId, recordingBaseDir);
|
|
5347
|
+
bundledFrames = fallbackFramePaths.map((framePath, index) => {
|
|
5348
|
+
const extensionMatch = basename3(framePath).match(/(\.[^.]+)$/);
|
|
5349
|
+
const extension = extensionMatch ? extensionMatch[1] : ".png";
|
|
5350
|
+
const relativeImagePath = `frames/frame-${String(index + 1).padStart(4, "0")}${extension}`;
|
|
5351
|
+
copyPathIntoExportBundle(framePath, join6(bundleRoot, relativeImagePath));
|
|
5352
|
+
return {
|
|
5353
|
+
id: `${projectId}-fallback-frame-${index + 1}`,
|
|
5354
|
+
projectId,
|
|
5355
|
+
frameNumber: index + 1,
|
|
5356
|
+
imagePath: relativeImagePath,
|
|
5357
|
+
ocrText: null,
|
|
5358
|
+
timestamp,
|
|
5359
|
+
createdAt: new Date(timestamp),
|
|
5360
|
+
isKeyFrame: null
|
|
5361
|
+
};
|
|
5362
|
+
});
|
|
5325
5363
|
}
|
|
5364
|
+
const mediaFiles = [];
|
|
5326
5365
|
if (rawProject.thumbnailPath && existsSync5(rawProject.thumbnailPath) && thumbnailName) {
|
|
5327
5366
|
const relativePath = `media/${thumbnailName}`;
|
|
5328
5367
|
copyPathIntoExportBundle(rawProject.thumbnailPath, join6(bundleRoot, relativePath));
|
|
5329
5368
|
mediaFiles.push({ role: "thumbnail", path: relativePath });
|
|
5330
5369
|
}
|
|
5331
|
-
const
|
|
5332
|
-
const
|
|
5370
|
+
const canonicalBundleIconPath = resolveAppLabBundleIconPath();
|
|
5371
|
+
const bundleIconRelativePath = canonicalBundleIconPath ? "media/icon.png" : null;
|
|
5372
|
+
if (canonicalBundleIconPath && bundleIconRelativePath) {
|
|
5373
|
+
copyPathIntoExportBundle(canonicalBundleIconPath, join6(bundleRoot, bundleIconRelativePath));
|
|
5374
|
+
mediaFiles.push({ role: "icon", path: bundleIconRelativePath });
|
|
5375
|
+
}
|
|
5376
|
+
const recordingIncluded = false;
|
|
5377
|
+
const exportArtifactCount = 0;
|
|
5333
5378
|
const esvpSessionId = resolveProjectESVPSessionId(esvp);
|
|
5334
5379
|
const esvpServerUrl = resolveProjectESVPServerUrl(esvp);
|
|
5335
5380
|
let esvpSnapshot = null;
|
|
@@ -5351,9 +5396,13 @@ app.post("/api/export", async (c) => {
|
|
|
5351
5396
|
}
|
|
5352
5397
|
const packagedProject = {
|
|
5353
5398
|
...normalizedProject,
|
|
5354
|
-
videoPath:
|
|
5399
|
+
videoPath: null,
|
|
5355
5400
|
thumbnailPath: thumbnailName ? `media/${thumbnailName}` : normalizedProject.thumbnailPath,
|
|
5356
|
-
frames: bundledFrames
|
|
5401
|
+
frames: bundledFrames,
|
|
5402
|
+
icon: bundleIconRelativePath ? {
|
|
5403
|
+
path: bundleIconRelativePath,
|
|
5404
|
+
kind: "app-icon"
|
|
5405
|
+
} : null
|
|
5357
5406
|
};
|
|
5358
5407
|
writeExportJson(join6(bundleRoot, "manifest.json"), {
|
|
5359
5408
|
bundleVersion: 1,
|
|
@@ -5367,6 +5416,10 @@ app.post("/api/export", async (c) => {
|
|
|
5367
5416
|
id: rawProject.id,
|
|
5368
5417
|
name: rawProject.name,
|
|
5369
5418
|
platform: rawProject.platform || null,
|
|
5419
|
+
icon: bundleIconRelativePath ? {
|
|
5420
|
+
path: bundleIconRelativePath,
|
|
5421
|
+
kind: "app-icon"
|
|
5422
|
+
} : null,
|
|
5370
5423
|
frameCount: bundledFrames.length,
|
|
5371
5424
|
hasRecordingFolder: recordingIncluded,
|
|
5372
5425
|
hasNetworkTrace: networkEntries.length > 0 || !!networkCapture,
|
|
@@ -5418,6 +5471,11 @@ app.post("/api/export", async (c) => {
|
|
|
5418
5471
|
if (esvpSnapshot) {
|
|
5419
5472
|
writeExportJson(join6(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
|
|
5420
5473
|
}
|
|
5474
|
+
const templateContentPath = join6(PROJECTS_DIR, projectId, "template-content.json");
|
|
5475
|
+
if (existsSync5(templateContentPath)) {
|
|
5476
|
+
mkdirSync4(join6(bundleRoot, "templates"), { recursive: true });
|
|
5477
|
+
cpSync(templateContentPath, join6(bundleRoot, "templates", "content.json"));
|
|
5478
|
+
}
|
|
5421
5479
|
writeExportText(join6(bundleRoot, "README.txt"), [
|
|
5422
5480
|
`${rawProject.name}`,
|
|
5423
5481
|
`Exported from DiscoveryLab ${APP_VERSION} on ${new Date(timestamp).toISOString()}.`,
|
|
@@ -5425,12 +5483,16 @@ app.post("/api/export", async (c) => {
|
|
|
5425
5483
|
"This package bundles the local project context for sharing or re-analysis.",
|
|
5426
5484
|
"",
|
|
5427
5485
|
"Included when available:",
|
|
5428
|
-
"-
|
|
5429
|
-
"-
|
|
5486
|
+
"- selected thumbnail and analyzed frames",
|
|
5487
|
+
"- lightweight project/session metadata",
|
|
5430
5488
|
"- OCR text and app intelligence summary",
|
|
5431
5489
|
"- network trace, capture metadata, and ESVP snapshot",
|
|
5432
|
-
"-
|
|
5433
|
-
"
|
|
5490
|
+
"- Task Hub links, requirements, and test map",
|
|
5491
|
+
"",
|
|
5492
|
+
"Excluded by default to keep the bundle Claude-friendly:",
|
|
5493
|
+
"- original long-form media",
|
|
5494
|
+
"- recording folder",
|
|
5495
|
+
"- generated export assets and renders"
|
|
5434
5496
|
].join("\n"));
|
|
5435
5497
|
outputPath = join6(exportDir, `export-${timestamp}.${format}`);
|
|
5436
5498
|
mimeType = "application/zip";
|
|
@@ -5591,8 +5653,8 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
|
|
|
5591
5653
|
const db = getDatabase();
|
|
5592
5654
|
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
5593
5655
|
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
5594
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
5595
|
-
const projectFramesDir = join6(
|
|
5656
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
5657
|
+
const projectFramesDir = join6(FRAMES_DIR2, projectId);
|
|
5596
5658
|
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(10);
|
|
5597
5659
|
let frameImages = [];
|
|
5598
5660
|
if (dbFrames.length > 0) {
|
|
@@ -5913,6 +5975,72 @@ app.post("/api/visualization/screenshot", async (c) => {
|
|
|
5913
5975
|
return c.json({ error: message }, 500);
|
|
5914
5976
|
}
|
|
5915
5977
|
});
|
|
5978
|
+
app.post("/api/export/infographic", async (c) => {
|
|
5979
|
+
try {
|
|
5980
|
+
const body = await c.req.json();
|
|
5981
|
+
const { projectId, open } = body;
|
|
5982
|
+
if (!projectId) return c.json({ error: "projectId required" }, 400);
|
|
5983
|
+
const db = getDatabase();
|
|
5984
|
+
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
5985
|
+
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
5986
|
+
const { FRAMES_DIR: fDir, EXPORTS_DIR: eDir, PROJECTS_DIR: pDir } = await import("./db-5ECN3O7F.js");
|
|
5987
|
+
const { collectFrameImages, buildInfographicData, generateInfographicHtml } = await import("./infographic-GQAHEOAA.js");
|
|
5988
|
+
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(20);
|
|
5989
|
+
let frameFiles;
|
|
5990
|
+
let frameOcr;
|
|
5991
|
+
if (dbFrames.length > 0) {
|
|
5992
|
+
frameFiles = dbFrames.map((f) => f.imagePath);
|
|
5993
|
+
frameOcr = dbFrames;
|
|
5994
|
+
} else {
|
|
5995
|
+
frameFiles = collectFrameImages(join6(fDir, projectId), project.videoPath, pDir, projectId);
|
|
5996
|
+
frameOcr = frameFiles.map(() => ({ ocrText: null }));
|
|
5997
|
+
}
|
|
5998
|
+
if (frameFiles.length === 0) {
|
|
5999
|
+
return c.json({ error: "No frames found. Run analyzer first." }, 400);
|
|
6000
|
+
}
|
|
6001
|
+
const cached = annotationCache.get(projectId);
|
|
6002
|
+
const annotations = cached?.steps?.map((s) => ({ label: s }));
|
|
6003
|
+
const data = buildInfographicData(project, frameFiles, frameOcr, annotations);
|
|
6004
|
+
const slug = (project.marketingTitle || project.name || projectId).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
6005
|
+
const outputPath = join6(eDir, `${slug}-infographic.html`);
|
|
6006
|
+
const result = generateInfographicHtml(data, outputPath);
|
|
6007
|
+
if (!result.success) return c.json({ error: result.error }, 500);
|
|
6008
|
+
if (open) {
|
|
6009
|
+
const { exec: exec2 } = await import("child_process");
|
|
6010
|
+
exec2(`open "${result.outputPath}"`);
|
|
6011
|
+
}
|
|
6012
|
+
return c.json({
|
|
6013
|
+
success: true,
|
|
6014
|
+
path: result.outputPath,
|
|
6015
|
+
downloadUrl: `/api/file?path=${encodeURIComponent(result.outputPath)}&download=true`,
|
|
6016
|
+
size: result.size,
|
|
6017
|
+
frameCount: result.frameCount
|
|
6018
|
+
});
|
|
6019
|
+
} catch (error) {
|
|
6020
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6021
|
+
return c.json({ error: message }, 500);
|
|
6022
|
+
}
|
|
6023
|
+
});
|
|
6024
|
+
app.post("/api/import", async (c) => {
|
|
6025
|
+
try {
|
|
6026
|
+
const body = await c.req.json();
|
|
6027
|
+
const { filePath } = body;
|
|
6028
|
+
if (!filePath) return c.json({ error: "filePath required" }, 400);
|
|
6029
|
+
const { importApplabBundle } = await import("./import-W2JEW254.js");
|
|
6030
|
+
const { FRAMES_DIR: fDir, PROJECTS_DIR: pDir } = await import("./db-5ECN3O7F.js");
|
|
6031
|
+
const db = getDatabase();
|
|
6032
|
+
const result = await importApplabBundle(filePath, db, { projects, frames }, {
|
|
6033
|
+
dataDir: DATA_DIR,
|
|
6034
|
+
framesDir: fDir,
|
|
6035
|
+
projectsDir: pDir
|
|
6036
|
+
});
|
|
6037
|
+
if (!result.success) return c.json({ error: result.error }, 400);
|
|
6038
|
+
return c.json(result);
|
|
6039
|
+
} catch (error) {
|
|
6040
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6041
|
+
return c.json({ error: message }, 500);
|
|
6042
|
+
}
|
|
6043
|
+
});
|
|
5916
6044
|
app.get("/api/export/document/:projectId", async (c) => {
|
|
5917
6045
|
try {
|
|
5918
6046
|
const projectId = c.req.param("projectId");
|
|
@@ -5922,9 +6050,9 @@ app.get("/api/export/document/:projectId", async (c) => {
|
|
|
5922
6050
|
const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(20);
|
|
5923
6051
|
let frameData = projectFrames.map((f) => ({ imagePath: f.imagePath, ocrText: f.ocrText }));
|
|
5924
6052
|
if (frameData.length === 0 && project.videoPath) {
|
|
5925
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
6053
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
5926
6054
|
const dirs = [
|
|
5927
|
-
join6(
|
|
6055
|
+
join6(FRAMES_DIR2, projectId),
|
|
5928
6056
|
join6(project.videoPath, "screenshots"),
|
|
5929
6057
|
join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
|
|
5930
6058
|
join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
|
|
@@ -6001,7 +6129,7 @@ app.post("/api/export/batch", async (c) => {
|
|
|
6001
6129
|
if (!manifest.destination?.type) {
|
|
6002
6130
|
return c.json({ error: "Destination type required" }, 400);
|
|
6003
6131
|
}
|
|
6004
|
-
const { FRAMES_DIR } = await import("./db-5ECN3O7F.js");
|
|
6132
|
+
const { FRAMES_DIR: FRAMES_DIR2 } = await import("./db-5ECN3O7F.js");
|
|
6005
6133
|
const dataProvider = {
|
|
6006
6134
|
async getProject(projectId) {
|
|
6007
6135
|
const db2 = getDatabase();
|
|
@@ -6009,7 +6137,7 @@ app.post("/api/export/batch", async (c) => {
|
|
|
6009
6137
|
return p || null;
|
|
6010
6138
|
},
|
|
6011
6139
|
getFramesDir(projectId) {
|
|
6012
|
-
return join6(
|
|
6140
|
+
return join6(FRAMES_DIR2, projectId);
|
|
6013
6141
|
}
|
|
6014
6142
|
};
|
|
6015
6143
|
const result = await executeBatchExport(manifest, dataProvider, (progress) => {
|
|
@@ -6253,6 +6381,158 @@ app.get("/api/testing/status", async (c) => {
|
|
|
6253
6381
|
return c.json({ error: message }, 500);
|
|
6254
6382
|
}
|
|
6255
6383
|
});
|
|
6384
|
+
function getClaudeDesktopConfigPath() {
|
|
6385
|
+
const home = homedir3();
|
|
6386
|
+
return process.platform === "win32" ? join6(process.env.APPDATA || join6(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : join6(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
6387
|
+
}
|
|
6388
|
+
function getClaudeDesktopAppCandidates() {
|
|
6389
|
+
const home = homedir3();
|
|
6390
|
+
if (process.platform === "darwin") {
|
|
6391
|
+
return [
|
|
6392
|
+
"/Applications/Claude.app",
|
|
6393
|
+
join6(home, "Applications", "Claude.app")
|
|
6394
|
+
];
|
|
6395
|
+
}
|
|
6396
|
+
if (process.platform === "win32") {
|
|
6397
|
+
return [
|
|
6398
|
+
process.env.LOCALAPPDATA ? join6(process.env.LOCALAPPDATA, "Programs", "Claude", "Claude.exe") : "",
|
|
6399
|
+
process.env.PROGRAMFILES ? join6(process.env.PROGRAMFILES, "Claude", "Claude.exe") : "",
|
|
6400
|
+
process.env["PROGRAMFILES(X86)"] ? join6(process.env["PROGRAMFILES(X86)"], "Claude", "Claude.exe") : "",
|
|
6401
|
+
process.env.APPDATA ? join6(process.env.APPDATA, "Claude", "Claude.exe") : ""
|
|
6402
|
+
].filter(Boolean);
|
|
6403
|
+
}
|
|
6404
|
+
return [];
|
|
6405
|
+
}
|
|
6406
|
+
function findClaudeDesktopApp() {
|
|
6407
|
+
if (process.platform === "darwin") {
|
|
6408
|
+
try {
|
|
6409
|
+
execSync2('open -Ra "Claude"', { stdio: "pipe", timeout: 2e3 });
|
|
6410
|
+
return {
|
|
6411
|
+
detected: true,
|
|
6412
|
+
launchTarget: "Claude",
|
|
6413
|
+
installPath: getClaudeDesktopAppCandidates().find((candidate2) => existsSync5(candidate2)) || null
|
|
6414
|
+
};
|
|
6415
|
+
} catch {
|
|
6416
|
+
const candidate2 = getClaudeDesktopAppCandidates().find((path) => existsSync5(path));
|
|
6417
|
+
return {
|
|
6418
|
+
detected: Boolean(candidate2),
|
|
6419
|
+
launchTarget: candidate2 ? "Claude" : null,
|
|
6420
|
+
installPath: candidate2 || null
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
const candidate = getClaudeDesktopAppCandidates().find((path) => existsSync5(path));
|
|
6425
|
+
return {
|
|
6426
|
+
detected: Boolean(candidate),
|
|
6427
|
+
launchTarget: candidate || null,
|
|
6428
|
+
installPath: candidate || null
|
|
6429
|
+
};
|
|
6430
|
+
}
|
|
6431
|
+
function detectDiscoveryLabClaudeDesktopMcp() {
|
|
6432
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
6433
|
+
if (!existsSync5(configPath)) {
|
|
6434
|
+
return {
|
|
6435
|
+
configured: false,
|
|
6436
|
+
serverName: null,
|
|
6437
|
+
source: "none",
|
|
6438
|
+
configPath
|
|
6439
|
+
};
|
|
6440
|
+
}
|
|
6441
|
+
try {
|
|
6442
|
+
const raw = readFileSync2(configPath, "utf8");
|
|
6443
|
+
const parsed = JSON.parse(raw);
|
|
6444
|
+
const servers = parsed?.mcpServers;
|
|
6445
|
+
if (!servers || typeof servers !== "object") {
|
|
6446
|
+
return {
|
|
6447
|
+
configured: false,
|
|
6448
|
+
serverName: null,
|
|
6449
|
+
source: "none",
|
|
6450
|
+
configPath
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6453
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
6454
|
+
const configString = [
|
|
6455
|
+
name,
|
|
6456
|
+
config?.command || "",
|
|
6457
|
+
...Array.isArray(config?.args) ? config.args : [],
|
|
6458
|
+
config?.url || ""
|
|
6459
|
+
].join(" ").toLowerCase();
|
|
6460
|
+
if (name === "discoverylab" || configString.includes("@veolab/discoverylab") || configString.includes("discoverylab") || configString.includes("applab-discovery")) {
|
|
6461
|
+
return {
|
|
6462
|
+
configured: true,
|
|
6463
|
+
serverName: name || "discoverylab",
|
|
6464
|
+
source: "settings",
|
|
6465
|
+
configPath
|
|
6466
|
+
};
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
} catch {
|
|
6470
|
+
}
|
|
6471
|
+
return {
|
|
6472
|
+
configured: false,
|
|
6473
|
+
serverName: null,
|
|
6474
|
+
source: "none",
|
|
6475
|
+
configPath
|
|
6476
|
+
};
|
|
6477
|
+
}
|
|
6478
|
+
app.get("/api/integrations/claude-desktop/status", async (c) => {
|
|
6479
|
+
try {
|
|
6480
|
+
const { platform } = await import("os");
|
|
6481
|
+
const app2 = findClaudeDesktopApp();
|
|
6482
|
+
const mcp = detectDiscoveryLabClaudeDesktopMcp();
|
|
6483
|
+
const launcherSupported = platform() === "darwin" || platform() === "win32";
|
|
6484
|
+
const ready = app2.detected && launcherSupported && mcp.configured;
|
|
6485
|
+
let message = "Claude Desktop launcher unavailable on this platform.";
|
|
6486
|
+
if (platform() === "darwin" || platform() === "win32") {
|
|
6487
|
+
if (!app2.detected) {
|
|
6488
|
+
message = "Claude Desktop was not detected on this machine.";
|
|
6489
|
+
} else if (!mcp.configured) {
|
|
6490
|
+
message = "Claude Desktop is installed, but the DiscoveryLab local MCP is not configured yet.";
|
|
6491
|
+
} else {
|
|
6492
|
+
message = "Claude Desktop is ready to open this project with the local DiscoveryLab MCP.";
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
return c.json({
|
|
6496
|
+
ready,
|
|
6497
|
+
appDetected: app2.detected,
|
|
6498
|
+
launcherSupported,
|
|
6499
|
+
launchTarget: app2.launchTarget,
|
|
6500
|
+
installPath: app2.installPath,
|
|
6501
|
+
mcpConfigured: mcp.configured,
|
|
6502
|
+
serverName: mcp.serverName,
|
|
6503
|
+
source: mcp.source,
|
|
6504
|
+
configPath: mcp.configPath,
|
|
6505
|
+
installCommand: "npx -y @veolab/discoverylab@latest install --target desktop",
|
|
6506
|
+
message
|
|
6507
|
+
});
|
|
6508
|
+
} catch (error) {
|
|
6509
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6510
|
+
return c.json({ error: message }, 500);
|
|
6511
|
+
}
|
|
6512
|
+
});
|
|
6513
|
+
app.post("/api/integrations/claude-desktop/launch", async (c) => {
|
|
6514
|
+
try {
|
|
6515
|
+
const { platform } = await import("os");
|
|
6516
|
+
const { exec: exec2 } = await import("child_process");
|
|
6517
|
+
const { promisify } = await import("util");
|
|
6518
|
+
const app2 = findClaudeDesktopApp();
|
|
6519
|
+
if (!app2.detected || !app2.launchTarget) {
|
|
6520
|
+
return c.json({ success: false, error: "Claude Desktop was not detected on this machine." }, 404);
|
|
6521
|
+
}
|
|
6522
|
+
const execAsync = promisify(exec2);
|
|
6523
|
+
if (platform() === "darwin") {
|
|
6524
|
+
await execAsync(`open -a "${app2.launchTarget}"`);
|
|
6525
|
+
} else if (platform() === "win32") {
|
|
6526
|
+
await execAsync(`cmd /c start "" "${app2.launchTarget}"`);
|
|
6527
|
+
} else {
|
|
6528
|
+
return c.json({ success: false, error: "Claude Desktop launcher is not supported on this platform." }, 400);
|
|
6529
|
+
}
|
|
6530
|
+
return c.json({ success: true });
|
|
6531
|
+
} catch (error) {
|
|
6532
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6533
|
+
return c.json({ success: false, error: message }, 500);
|
|
6534
|
+
}
|
|
6535
|
+
});
|
|
6256
6536
|
app.get("/api/integrations/jira-mcp/status", async (c) => {
|
|
6257
6537
|
try {
|
|
6258
6538
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -9876,7 +10156,7 @@ app.get("/api/mobile-chat/providers", async (c) => {
|
|
|
9876
10156
|
});
|
|
9877
10157
|
app.get("/api/setup/status", async (c) => {
|
|
9878
10158
|
try {
|
|
9879
|
-
const { setupStatusTool } = await import("./setup-
|
|
10159
|
+
const { setupStatusTool } = await import("./setup-SMN7FJNZ.js");
|
|
9880
10160
|
const result = await setupStatusTool.handler({});
|
|
9881
10161
|
const data = JSON.parse(result.content[0].text);
|
|
9882
10162
|
const idbInstalled = await isIdbInstalled().catch(() => false);
|
|
@@ -11286,6 +11566,20 @@ app.post("/api/templates/render", async (c) => {
|
|
|
11286
11566
|
return c.json({ error: templateState.eligibility.reason || `Templates are limited to videos up to ${TEMPLATE_MAX_DURATION_SECONDS} seconds.` }, 400);
|
|
11287
11567
|
}
|
|
11288
11568
|
const { props } = templateState;
|
|
11569
|
+
const forceRender = body.force === true;
|
|
11570
|
+
if (!forceRender) {
|
|
11571
|
+
const cached = getCachedRender(projectId, templateId);
|
|
11572
|
+
if (cached && existsSync5(cached)) {
|
|
11573
|
+
return c.json({
|
|
11574
|
+
jobId: "cached",
|
|
11575
|
+
status: "completed",
|
|
11576
|
+
outputPath: cached,
|
|
11577
|
+
downloadUrl: `/api/file?path=${encodeURIComponent(cached)}&download=true`,
|
|
11578
|
+
previewUrl: `/api/file?path=${encodeURIComponent(cached)}`,
|
|
11579
|
+
cached: true
|
|
11580
|
+
});
|
|
11581
|
+
}
|
|
11582
|
+
}
|
|
11289
11583
|
const job = await startRender(projectId, templateId, props, (progress) => {
|
|
11290
11584
|
broadcastToClients({
|
|
11291
11585
|
type: "templateRenderProgress",
|