@veolab/discoverylab 1.3.4 → 1.4.1
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/dist/chunk-34KRJWZL.js +477 -0
- package/dist/chunk-4VNS5WPM.js +42 -0
- package/dist/{chunk-FIL7IWEL.js → chunk-DGXAP477.js} +1 -1
- package/dist/{chunk-HGWEHWKJ.js → chunk-DKAX5RCX.js} +1 -1
- package/dist/{chunk-7EDIUVIO.js → chunk-EU63HPKT.js} +1 -1
- package/dist/chunk-QMUEC6B5.js +288 -0
- package/dist/{chunk-FNUN7EPB.js → chunk-RCY26WEK.js} +2 -2
- package/dist/{chunk-ZLHIHMSL.js → chunk-SWZIBO2R.js} +1 -1
- package/dist/chunk-VYYAP5G5.js +265 -0
- package/dist/{chunk-VVIOB362.js → chunk-XAMA3JJG.js} +18 -1
- package/dist/{chunk-BE7BFMYC.js → chunk-XWBFSSNB.js} +10224 -393
- package/dist/{chunk-AHVBE25Y.js → chunk-YNLUOZSZ.js} +274 -667
- package/dist/cli.js +33 -31
- package/dist/{db-6WLEVKUV.js → db-745LC5YC.js} +2 -2
- package/dist/document-AE4XI2CP.js +104 -0
- package/dist/{esvp-KVOWYW6G.js → esvp-4LIAU76K.js} +3 -3
- package/dist/{esvp-mobile-GZ5EMYPG.js → esvp-mobile-FKFHDS5Q.js} +4 -4
- package/dist/frames-RCNLSDD6.js +24 -0
- package/dist/{gridCompositor-M3K3LCLZ.js → gridCompositor-VUWBZXYL.js} +262 -3
- package/dist/index.d.ts +32 -0
- package/dist/index.html +1197 -9
- package/dist/index.js +15 -10
- package/dist/notion-api-OXSWOJPZ.js +190 -0
- package/dist/{ocr-QDYNCSPE.js → ocr-FXRLEP66.js} +1 -1
- package/dist/{playwright-VZ7PXDC5.js → playwright-GYKUH34L.js} +3 -3
- package/dist/renderer-D22GCMMD.js +17 -0
- package/dist/{server-6N3KIEGP.js → server-NTT2XGCC.js} +1 -1
- package/dist/server-TKYRIYJ6.js +24 -0
- package/dist/{setup-2SQC5UHJ.js → setup-O6WQQAGP.js} +3 -3
- package/dist/templates/bundle/bundle.js +4 -2
- package/dist/{tools-YGM5HRIB.js → tools-FVVWKEGC.js} +15 -7
- package/package.json +2 -2
- package/skills/knowledge-brain/SKILL.md +81 -0
- package/dist/chunk-MLKGABMK.js +0 -9
- package/dist/server-QKZXPZRC.js +0 -22
|
@@ -1,12 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectKeyFrames,
|
|
3
|
+
extractFrames,
|
|
4
|
+
extractKeyFramesOnly,
|
|
5
|
+
generateThumbnail,
|
|
6
|
+
getVideoInfo
|
|
7
|
+
} from "./chunk-QMUEC6B5.js";
|
|
1
8
|
import {
|
|
2
9
|
createErrorResult,
|
|
3
10
|
createJsonResult,
|
|
4
11
|
createTextResult
|
|
5
12
|
} from "./chunk-XKX6NBHF.js";
|
|
13
|
+
import {
|
|
14
|
+
checkNotionAuth,
|
|
15
|
+
createNotionPage,
|
|
16
|
+
loginToNotion,
|
|
17
|
+
quickExportToNotion
|
|
18
|
+
} from "./chunk-34KRJWZL.js";
|
|
6
19
|
import {
|
|
7
20
|
getAvailableTemplates,
|
|
8
21
|
isTemplatesInstalled
|
|
9
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-DKAX5RCX.js";
|
|
10
23
|
import {
|
|
11
24
|
createFormSubmissionScript,
|
|
12
25
|
createLoginScript,
|
|
@@ -19,7 +32,7 @@ import {
|
|
|
19
32
|
savePlaywrightScript,
|
|
20
33
|
showPlaywrightReport,
|
|
21
34
|
startPlaywrightCodegen
|
|
22
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-DGXAP477.js";
|
|
23
36
|
import {
|
|
24
37
|
buildAppLabNetworkProfile
|
|
25
38
|
} from "./chunk-LB3RNE3O.js";
|
|
@@ -57,18 +70,17 @@ import {
|
|
|
57
70
|
runMaestroWithCapture,
|
|
58
71
|
startMaestroStudio,
|
|
59
72
|
validateESVPReplay
|
|
60
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-EU63HPKT.js";
|
|
61
74
|
import {
|
|
62
75
|
DATA_DIR,
|
|
63
76
|
EXPORTS_DIR,
|
|
64
|
-
FRAMES_DIR,
|
|
65
77
|
PROJECTS_DIR,
|
|
66
78
|
frames,
|
|
67
79
|
getDatabase,
|
|
68
80
|
projectExports,
|
|
69
81
|
projects,
|
|
70
82
|
testVariables
|
|
71
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-XAMA3JJG.js";
|
|
72
84
|
import {
|
|
73
85
|
analyzeText,
|
|
74
86
|
getAvailableOCREngines,
|
|
@@ -1204,217 +1216,8 @@ var captureTools = [
|
|
|
1204
1216
|
// src/mcp/tools/analyze.ts
|
|
1205
1217
|
import { z as z4 } from "zod";
|
|
1206
1218
|
import { eq as eq2 } from "drizzle-orm";
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
import { spawn as spawn3, execSync as execSync3 } from "child_process";
|
|
1210
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, statSync } from "fs";
|
|
1211
|
-
import { join as join3, basename } from "path";
|
|
1212
|
-
function getVideoInfo(videoPath) {
|
|
1213
|
-
if (!existsSync3(videoPath)) {
|
|
1214
|
-
return null;
|
|
1215
|
-
}
|
|
1216
|
-
try {
|
|
1217
|
-
const output = execSync3(
|
|
1218
|
-
`ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`,
|
|
1219
|
-
{ encoding: "utf-8" }
|
|
1220
|
-
);
|
|
1221
|
-
const data = JSON.parse(output);
|
|
1222
|
-
const videoStream = data.streams?.find((s) => s.codec_type === "video");
|
|
1223
|
-
if (!videoStream) {
|
|
1224
|
-
return null;
|
|
1225
|
-
}
|
|
1226
|
-
let fps = 30;
|
|
1227
|
-
if (videoStream.r_frame_rate) {
|
|
1228
|
-
const [num, den] = videoStream.r_frame_rate.split("/");
|
|
1229
|
-
fps = den ? parseInt(num, 10) / parseInt(den, 10) : parseFloat(num);
|
|
1230
|
-
}
|
|
1231
|
-
const duration = parseFloat(data.format?.duration || videoStream.duration || "0");
|
|
1232
|
-
const frameCount = parseInt(videoStream.nb_frames, 10) || Math.round(duration * fps);
|
|
1233
|
-
return {
|
|
1234
|
-
duration,
|
|
1235
|
-
frameCount,
|
|
1236
|
-
fps,
|
|
1237
|
-
width: videoStream.width,
|
|
1238
|
-
height: videoStream.height,
|
|
1239
|
-
codec: videoStream.codec_name
|
|
1240
|
-
};
|
|
1241
|
-
} catch (error) {
|
|
1242
|
-
console.error("Failed to get video info:", error);
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
async function extractFrames(options) {
|
|
1247
|
-
const { projectId, videoPath, outputFormat = "png" } = options;
|
|
1248
|
-
if (!existsSync3(videoPath)) {
|
|
1249
|
-
return { success: false, error: `Video file not found: ${videoPath}` };
|
|
1250
|
-
}
|
|
1251
|
-
const framesDir = join3(FRAMES_DIR, projectId);
|
|
1252
|
-
if (!existsSync3(framesDir)) {
|
|
1253
|
-
mkdirSync3(framesDir, { recursive: true });
|
|
1254
|
-
}
|
|
1255
|
-
const videoInfo = getVideoInfo(videoPath);
|
|
1256
|
-
if (!videoInfo) {
|
|
1257
|
-
return { success: false, error: "Failed to read video information" };
|
|
1258
|
-
}
|
|
1259
|
-
const args = buildFFmpegArgs(options, framesDir, videoInfo, outputFormat);
|
|
1260
|
-
return new Promise((resolve2) => {
|
|
1261
|
-
const proc = spawn3("ffmpeg", args);
|
|
1262
|
-
let stderr = "";
|
|
1263
|
-
proc.stderr.on("data", (data) => {
|
|
1264
|
-
stderr += data.toString();
|
|
1265
|
-
});
|
|
1266
|
-
proc.on("close", (code) => {
|
|
1267
|
-
if (code !== 0) {
|
|
1268
|
-
resolve2({ success: false, error: `FFmpeg failed: ${stderr.slice(-500)}` });
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
const frames2 = listExtractedFrames(framesDir, videoInfo.fps, options.fps || 1);
|
|
1272
|
-
resolve2({
|
|
1273
|
-
success: true,
|
|
1274
|
-
framesDir,
|
|
1275
|
-
frameCount: frames2.length,
|
|
1276
|
-
frames: frames2
|
|
1277
|
-
});
|
|
1278
|
-
});
|
|
1279
|
-
proc.on("error", (err) => {
|
|
1280
|
-
resolve2({ success: false, error: err.message });
|
|
1281
|
-
});
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
function buildFFmpegArgs(options, framesDir, videoInfo, outputFormat) {
|
|
1285
|
-
const args = ["-y"];
|
|
1286
|
-
args.push("-i", options.videoPath);
|
|
1287
|
-
if (options.startTime !== void 0) {
|
|
1288
|
-
args.push("-ss", options.startTime.toString());
|
|
1289
|
-
}
|
|
1290
|
-
if (options.endTime !== void 0) {
|
|
1291
|
-
args.push("-to", options.endTime.toString());
|
|
1292
|
-
}
|
|
1293
|
-
if (options.keyFramesOnly) {
|
|
1294
|
-
args.push("-vf", "select=eq(pict_type\\,I)");
|
|
1295
|
-
args.push("-vsync", "vfr");
|
|
1296
|
-
} else if (options.fps) {
|
|
1297
|
-
args.push("-vf", `fps=${options.fps}`);
|
|
1298
|
-
} else {
|
|
1299
|
-
args.push("-vf", "fps=1");
|
|
1300
|
-
}
|
|
1301
|
-
if (options.maxFrames) {
|
|
1302
|
-
args.push("-frames:v", options.maxFrames.toString());
|
|
1303
|
-
}
|
|
1304
|
-
if (outputFormat === "jpg" && options.quality) {
|
|
1305
|
-
args.push("-q:v", options.quality.toString());
|
|
1306
|
-
}
|
|
1307
|
-
const outputPattern = join3(framesDir, `frame-%04d.${outputFormat}`);
|
|
1308
|
-
args.push(outputPattern);
|
|
1309
|
-
return args;
|
|
1310
|
-
}
|
|
1311
|
-
function listExtractedFrames(framesDir, videoFps, extractFps) {
|
|
1312
|
-
const files = readdirSync(framesDir).filter((f) => f.startsWith("frame-") && (f.endsWith(".png") || f.endsWith(".jpg"))).sort();
|
|
1313
|
-
return files.map((filename, index) => {
|
|
1314
|
-
const timestamp = index / extractFps;
|
|
1315
|
-
const match = filename.match(/frame-(\d+)/);
|
|
1316
|
-
const frameNumber = match ? parseInt(match[1], 10) : index + 1;
|
|
1317
|
-
return {
|
|
1318
|
-
path: join3(framesDir, filename),
|
|
1319
|
-
frameNumber,
|
|
1320
|
-
timestamp,
|
|
1321
|
-
filename
|
|
1322
|
-
};
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
async function detectKeyFrames(videoPath, threshold = 0.3) {
|
|
1326
|
-
if (!existsSync3(videoPath)) {
|
|
1327
|
-
return [];
|
|
1328
|
-
}
|
|
1329
|
-
try {
|
|
1330
|
-
const output = execSync3(
|
|
1331
|
-
`ffmpeg -i "${videoPath}" -vf "select='gt(scene,${threshold})',showinfo" -f null - 2>&1`,
|
|
1332
|
-
{ encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
|
|
1333
|
-
);
|
|
1334
|
-
const keyFrames = [];
|
|
1335
|
-
const lines = output.split("\n");
|
|
1336
|
-
for (const line of lines) {
|
|
1337
|
-
const match = line.match(/n:\s*(\d+).*pts_time:\s*([\d.]+)/);
|
|
1338
|
-
if (match) {
|
|
1339
|
-
const frameNumber = parseInt(match[1], 10);
|
|
1340
|
-
const timestamp = parseFloat(match[2]);
|
|
1341
|
-
const scoreMatch = line.match(/scene:\s*([\d.]+)/);
|
|
1342
|
-
const score = scoreMatch ? parseFloat(scoreMatch[1]) : threshold;
|
|
1343
|
-
keyFrames.push({
|
|
1344
|
-
path: "",
|
|
1345
|
-
frameNumber,
|
|
1346
|
-
timestamp,
|
|
1347
|
-
filename: `keyframe-${frameNumber}.png`,
|
|
1348
|
-
score,
|
|
1349
|
-
isSceneChange: score > threshold
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
return keyFrames;
|
|
1354
|
-
} catch (error) {
|
|
1355
|
-
console.error("Failed to detect key frames:", error);
|
|
1356
|
-
return [];
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
async function extractKeyFramesOnly(projectId, videoPath, threshold = 0.3) {
|
|
1360
|
-
const keyFrames = await detectKeyFrames(videoPath, threshold);
|
|
1361
|
-
if (keyFrames.length === 0) {
|
|
1362
|
-
return extractFrames({
|
|
1363
|
-
projectId,
|
|
1364
|
-
videoPath,
|
|
1365
|
-
fps: 1,
|
|
1366
|
-
maxFrames: 30
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
const framesDir = join3(FRAMES_DIR, projectId);
|
|
1370
|
-
if (!existsSync3(framesDir)) {
|
|
1371
|
-
mkdirSync3(framesDir, { recursive: true });
|
|
1372
|
-
}
|
|
1373
|
-
const extractedFrames = [];
|
|
1374
|
-
for (let i = 0; i < keyFrames.length; i++) {
|
|
1375
|
-
const kf = keyFrames[i];
|
|
1376
|
-
const outputPath = join3(framesDir, `keyframe-${String(i + 1).padStart(4, "0")}.png`);
|
|
1377
|
-
try {
|
|
1378
|
-
execSync3(
|
|
1379
|
-
`ffmpeg -y -ss ${kf.timestamp} -i "${videoPath}" -frames:v 1 "${outputPath}"`,
|
|
1380
|
-
{ stdio: "ignore" }
|
|
1381
|
-
);
|
|
1382
|
-
if (existsSync3(outputPath)) {
|
|
1383
|
-
extractedFrames.push({
|
|
1384
|
-
path: outputPath,
|
|
1385
|
-
frameNumber: i + 1,
|
|
1386
|
-
timestamp: kf.timestamp,
|
|
1387
|
-
filename: basename(outputPath)
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
} catch {
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
return {
|
|
1394
|
-
success: true,
|
|
1395
|
-
framesDir,
|
|
1396
|
-
frameCount: extractedFrames.length,
|
|
1397
|
-
frames: extractedFrames
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
async function generateThumbnail(videoPath, outputPath, timestamp = 0, width = 320) {
|
|
1401
|
-
if (!existsSync3(videoPath)) {
|
|
1402
|
-
return false;
|
|
1403
|
-
}
|
|
1404
|
-
try {
|
|
1405
|
-
execSync3(
|
|
1406
|
-
`ffmpeg -y -ss ${timestamp} -i "${videoPath}" -vframes 1 -vf "scale=${width}:-1" "${outputPath}"`,
|
|
1407
|
-
{ stdio: "ignore" }
|
|
1408
|
-
);
|
|
1409
|
-
return existsSync3(outputPath);
|
|
1410
|
-
} catch {
|
|
1411
|
-
return false;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
// src/mcp/tools/analyze.ts
|
|
1416
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1417
|
-
import { basename as basename2 } from "path";
|
|
1219
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1220
|
+
import { basename } from "path";
|
|
1418
1221
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1419
1222
|
var analyzeVideoTool = {
|
|
1420
1223
|
name: "dlab.analyze.video",
|
|
@@ -1429,7 +1232,7 @@ var analyzeVideoTool = {
|
|
|
1429
1232
|
}),
|
|
1430
1233
|
handler: async (params) => {
|
|
1431
1234
|
const { projectId, videoPath, keyFramesOnly, fps, maxFrames, performOCR = true } = params;
|
|
1432
|
-
if (!
|
|
1235
|
+
if (!existsSync3(videoPath)) {
|
|
1433
1236
|
return createErrorResult(`Video file not found: ${videoPath}`);
|
|
1434
1237
|
}
|
|
1435
1238
|
const videoInfo = getVideoInfo(videoPath);
|
|
@@ -1525,7 +1328,7 @@ var analyzeScreenshotTool = {
|
|
|
1525
1328
|
}),
|
|
1526
1329
|
handler: async (params) => {
|
|
1527
1330
|
const { imagePath, languages } = params;
|
|
1528
|
-
if (!
|
|
1331
|
+
if (!existsSync3(imagePath)) {
|
|
1529
1332
|
return createErrorResult(`Image file not found: ${imagePath}`);
|
|
1530
1333
|
}
|
|
1531
1334
|
const ocrResult = await recognizeText(imagePath, { languages });
|
|
@@ -1557,7 +1360,7 @@ var extractFramesTool = {
|
|
|
1557
1360
|
endTime: z4.number().optional().describe("End time in seconds")
|
|
1558
1361
|
}),
|
|
1559
1362
|
handler: async (params) => {
|
|
1560
|
-
if (!
|
|
1363
|
+
if (!existsSync3(params.videoPath)) {
|
|
1561
1364
|
return createErrorResult(`Video file not found: ${params.videoPath}`);
|
|
1562
1365
|
}
|
|
1563
1366
|
let result;
|
|
@@ -1596,7 +1399,7 @@ var ocrBatchTool = {
|
|
|
1596
1399
|
languages: z4.array(z4.string()).optional().describe("Languages for OCR")
|
|
1597
1400
|
}),
|
|
1598
1401
|
handler: async (params) => {
|
|
1599
|
-
const validPaths = params.imagePaths.filter(
|
|
1402
|
+
const validPaths = params.imagePaths.filter(existsSync3);
|
|
1600
1403
|
if (validPaths.length === 0) {
|
|
1601
1404
|
return createErrorResult("No valid image files found");
|
|
1602
1405
|
}
|
|
@@ -1608,7 +1411,7 @@ var ocrBatchTool = {
|
|
|
1608
1411
|
failed: validPaths.length - successCount,
|
|
1609
1412
|
combinedText: result.totalText.slice(0, 2e3) + (result.totalText.length > 2e3 ? "..." : ""),
|
|
1610
1413
|
results: result.results.map((r) => ({
|
|
1611
|
-
file:
|
|
1414
|
+
file: basename(r.imagePath),
|
|
1612
1415
|
success: r.ocr.success,
|
|
1613
1416
|
engine: r.ocr.engine,
|
|
1614
1417
|
textLength: r.ocr.text?.length || 0,
|
|
@@ -1624,7 +1427,7 @@ var videoInfoTool = {
|
|
|
1624
1427
|
videoPath: z4.string().describe("Path to the video file")
|
|
1625
1428
|
}),
|
|
1626
1429
|
handler: async (params) => {
|
|
1627
|
-
if (!
|
|
1430
|
+
if (!existsSync3(params.videoPath)) {
|
|
1628
1431
|
return createErrorResult(`Video file not found: ${params.videoPath}`);
|
|
1629
1432
|
}
|
|
1630
1433
|
const info = getVideoInfo(params.videoPath);
|
|
@@ -1632,7 +1435,7 @@ var videoInfoTool = {
|
|
|
1632
1435
|
return createErrorResult("Failed to read video information");
|
|
1633
1436
|
}
|
|
1634
1437
|
return createJsonResult({
|
|
1635
|
-
file:
|
|
1438
|
+
file: basename(params.videoPath),
|
|
1636
1439
|
duration: `${info.duration.toFixed(1)} seconds`,
|
|
1637
1440
|
resolution: `${info.width}x${info.height}`,
|
|
1638
1441
|
fps: info.fps.toFixed(2),
|
|
@@ -1649,7 +1452,7 @@ var detectKeyFramesTool = {
|
|
|
1649
1452
|
threshold: z4.number().optional().describe("Scene change threshold 0-1 (default: 0.3)")
|
|
1650
1453
|
}),
|
|
1651
1454
|
handler: async (params) => {
|
|
1652
|
-
if (!
|
|
1455
|
+
if (!existsSync3(params.videoPath)) {
|
|
1653
1456
|
return createErrorResult(`Video file not found: ${params.videoPath}`);
|
|
1654
1457
|
}
|
|
1655
1458
|
const keyFrames = await detectKeyFrames(params.videoPath, params.threshold || 0.3);
|
|
@@ -1678,7 +1481,7 @@ var generateThumbnailTool = {
|
|
|
1678
1481
|
width: z4.number().optional().describe("Thumbnail width in pixels (default: 320)")
|
|
1679
1482
|
}),
|
|
1680
1483
|
handler: async (params) => {
|
|
1681
|
-
if (!
|
|
1484
|
+
if (!existsSync3(params.videoPath)) {
|
|
1682
1485
|
return createErrorResult(`Video file not found: ${params.videoPath}`);
|
|
1683
1486
|
}
|
|
1684
1487
|
const success = await generateThumbnail(
|
|
@@ -2872,7 +2675,7 @@ import * as path5 from "path";
|
|
|
2872
2675
|
import * as fs5 from "fs";
|
|
2873
2676
|
|
|
2874
2677
|
// src/core/export/video.ts
|
|
2875
|
-
import { exec, spawn as
|
|
2678
|
+
import { exec, spawn as spawn3 } from "child_process";
|
|
2876
2679
|
import { promisify } from "util";
|
|
2877
2680
|
import * as fs3 from "fs";
|
|
2878
2681
|
import * as path3 from "path";
|
|
@@ -3020,7 +2823,7 @@ function buildOverlayFilter(overlay) {
|
|
|
3020
2823
|
}
|
|
3021
2824
|
async function runFFmpegWithProgress(args, onProgress) {
|
|
3022
2825
|
return new Promise((resolve2, reject) => {
|
|
3023
|
-
const ffmpeg =
|
|
2826
|
+
const ffmpeg = spawn3("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3024
2827
|
ffmpeg.stderr?.on("data", (data) => {
|
|
3025
2828
|
const output = data.toString();
|
|
3026
2829
|
const frameMatch = output.match(/frame=\s*(\d+)/);
|
|
@@ -4349,401 +4152,27 @@ var testingTools = [
|
|
|
4349
4152
|
|
|
4350
4153
|
// src/mcp/tools/integrations.ts
|
|
4351
4154
|
import { z as z8 } from "zod";
|
|
4352
|
-
import * as
|
|
4155
|
+
import * as fs9 from "fs";
|
|
4353
4156
|
|
|
4354
|
-
// src/integrations/
|
|
4157
|
+
// src/integrations/drive.ts
|
|
4355
4158
|
import { exec as exec3 } from "child_process";
|
|
4356
4159
|
import { promisify as promisify3 } from "util";
|
|
4357
4160
|
import * as fs7 from "fs";
|
|
4358
4161
|
import * as path7 from "path";
|
|
4359
4162
|
var execAsync3 = promisify3(exec3);
|
|
4360
|
-
var SESSION_DIR = path7.join(DATA_DIR, "sessions", "
|
|
4361
|
-
async function
|
|
4163
|
+
var SESSION_DIR = path7.join(DATA_DIR, "sessions", "drive");
|
|
4164
|
+
async function getDriveSessionPath() {
|
|
4362
4165
|
await fs7.promises.mkdir(SESSION_DIR, { recursive: true });
|
|
4363
4166
|
return SESSION_DIR;
|
|
4364
4167
|
}
|
|
4365
|
-
async function
|
|
4366
|
-
const sessionPath = await
|
|
4168
|
+
async function checkDriveAuth() {
|
|
4169
|
+
const sessionPath = await getDriveSessionPath();
|
|
4367
4170
|
const cookiesPath = path7.join(sessionPath, "cookies.json");
|
|
4368
4171
|
if (!fs7.existsSync(cookiesPath)) {
|
|
4369
4172
|
return { authenticated: false, sessionPath };
|
|
4370
4173
|
}
|
|
4371
4174
|
try {
|
|
4372
4175
|
const cookies = JSON.parse(await fs7.promises.readFile(cookiesPath, "utf-8"));
|
|
4373
|
-
const notionCookies = cookies.filter((c) => c.domain?.includes("notion"));
|
|
4374
|
-
if (notionCookies.length > 0) {
|
|
4375
|
-
return {
|
|
4376
|
-
authenticated: true,
|
|
4377
|
-
sessionPath
|
|
4378
|
-
};
|
|
4379
|
-
}
|
|
4380
|
-
} catch {
|
|
4381
|
-
}
|
|
4382
|
-
return { authenticated: false, sessionPath };
|
|
4383
|
-
}
|
|
4384
|
-
function generateNotionLoginScript() {
|
|
4385
|
-
return `
|
|
4386
|
-
import { chromium } from 'playwright';
|
|
4387
|
-
import * as fs from 'fs';
|
|
4388
|
-
import * as path from 'path';
|
|
4389
|
-
|
|
4390
|
-
const SESSION_DIR = '${SESSION_DIR}';
|
|
4391
|
-
|
|
4392
|
-
async function login() {
|
|
4393
|
-
const browser = await chromium.launchPersistentContext(SESSION_DIR, {
|
|
4394
|
-
headless: false,
|
|
4395
|
-
viewport: { width: 1280, height: 800 },
|
|
4396
|
-
});
|
|
4397
|
-
|
|
4398
|
-
const page = browser.pages()[0] || await browser.newPage();
|
|
4399
|
-
|
|
4400
|
-
// Navigate to Notion login
|
|
4401
|
-
await page.goto('https://www.notion.so/login');
|
|
4402
|
-
|
|
4403
|
-
console.log('Please log in to Notion in the browser window...');
|
|
4404
|
-
console.log('The browser will close automatically once logged in.');
|
|
4405
|
-
|
|
4406
|
-
// Wait for successful login (redirect to workspace)
|
|
4407
|
-
await page.waitForURL(/notion\\.so\\/[^login]/, { timeout: 300000 });
|
|
4408
|
-
|
|
4409
|
-
// Save cookies
|
|
4410
|
-
const cookies = await browser.cookies();
|
|
4411
|
-
await fs.promises.writeFile(
|
|
4412
|
-
path.join(SESSION_DIR, 'cookies.json'),
|
|
4413
|
-
JSON.stringify(cookies, null, 2)
|
|
4414
|
-
);
|
|
4415
|
-
|
|
4416
|
-
console.log('Login successful! Session saved.');
|
|
4417
|
-
await browser.close();
|
|
4418
|
-
}
|
|
4419
|
-
|
|
4420
|
-
login().catch(console.error);
|
|
4421
|
-
`;
|
|
4422
|
-
}
|
|
4423
|
-
function generateNotionUploadScript(options) {
|
|
4424
|
-
const { title, content, parentPageId, files = [] } = options;
|
|
4425
|
-
return `
|
|
4426
|
-
import { chromium } from 'playwright';
|
|
4427
|
-
import * as fs from 'fs';
|
|
4428
|
-
import * as path from 'path';
|
|
4429
|
-
|
|
4430
|
-
const SESSION_DIR = '${SESSION_DIR}';
|
|
4431
|
-
const TITLE = ${JSON.stringify(title)};
|
|
4432
|
-
const CONTENT = ${JSON.stringify(content)};
|
|
4433
|
-
const FILES = ${JSON.stringify(files)};
|
|
4434
|
-
const PARENT_PAGE_ID = ${JSON.stringify(parentPageId || null)};
|
|
4435
|
-
|
|
4436
|
-
async function createPage() {
|
|
4437
|
-
const browser = await chromium.launchPersistentContext(SESSION_DIR, {
|
|
4438
|
-
headless: false,
|
|
4439
|
-
viewport: { width: 1280, height: 800 },
|
|
4440
|
-
});
|
|
4441
|
-
|
|
4442
|
-
const page = browser.pages()[0] || await browser.newPage();
|
|
4443
|
-
|
|
4444
|
-
try {
|
|
4445
|
-
// Navigate to parent page or workspace
|
|
4446
|
-
if (PARENT_PAGE_ID) {
|
|
4447
|
-
await page.goto(\`https://www.notion.so/\${PARENT_PAGE_ID}\`);
|
|
4448
|
-
} else {
|
|
4449
|
-
await page.goto('https://www.notion.so');
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
await page.waitForLoadState('networkidle');
|
|
4453
|
-
|
|
4454
|
-
// Create new page
|
|
4455
|
-
await page.keyboard.press('Control+N');
|
|
4456
|
-
await page.waitForTimeout(1000);
|
|
4457
|
-
|
|
4458
|
-
// Set title
|
|
4459
|
-
await page.keyboard.type(TITLE);
|
|
4460
|
-
await page.keyboard.press('Enter');
|
|
4461
|
-
await page.waitForTimeout(500);
|
|
4462
|
-
|
|
4463
|
-
// Add content blocks
|
|
4464
|
-
for (const block of CONTENT) {
|
|
4465
|
-
await addBlock(page, block);
|
|
4466
|
-
}
|
|
4467
|
-
|
|
4468
|
-
// Upload files
|
|
4469
|
-
for (const filePath of FILES) {
|
|
4470
|
-
if (fs.existsSync(filePath)) {
|
|
4471
|
-
await uploadFile(page, filePath);
|
|
4472
|
-
}
|
|
4473
|
-
}
|
|
4474
|
-
|
|
4475
|
-
// Get page URL
|
|
4476
|
-
const url = page.url();
|
|
4477
|
-
console.log(JSON.stringify({ success: true, pageUrl: url }));
|
|
4478
|
-
|
|
4479
|
-
} catch (error) {
|
|
4480
|
-
console.log(JSON.stringify({ success: false, error: error.message }));
|
|
4481
|
-
} finally {
|
|
4482
|
-
await browser.close();
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
|
|
4486
|
-
async function addBlock(page, block) {
|
|
4487
|
-
switch (block.type) {
|
|
4488
|
-
case 'heading1':
|
|
4489
|
-
await page.keyboard.type('/h1');
|
|
4490
|
-
await page.keyboard.press('Enter');
|
|
4491
|
-
await page.keyboard.type(block.content || '');
|
|
4492
|
-
await page.keyboard.press('Enter');
|
|
4493
|
-
break;
|
|
4494
|
-
|
|
4495
|
-
case 'heading2':
|
|
4496
|
-
await page.keyboard.type('/h2');
|
|
4497
|
-
await page.keyboard.press('Enter');
|
|
4498
|
-
await page.keyboard.type(block.content || '');
|
|
4499
|
-
await page.keyboard.press('Enter');
|
|
4500
|
-
break;
|
|
4501
|
-
|
|
4502
|
-
case 'heading3':
|
|
4503
|
-
await page.keyboard.type('/h3');
|
|
4504
|
-
await page.keyboard.press('Enter');
|
|
4505
|
-
await page.keyboard.type(block.content || '');
|
|
4506
|
-
await page.keyboard.press('Enter');
|
|
4507
|
-
break;
|
|
4508
|
-
|
|
4509
|
-
case 'paragraph':
|
|
4510
|
-
await page.keyboard.type(block.content || '');
|
|
4511
|
-
await page.keyboard.press('Enter');
|
|
4512
|
-
break;
|
|
4513
|
-
|
|
4514
|
-
case 'bulletList':
|
|
4515
|
-
for (const item of block.items || []) {
|
|
4516
|
-
await page.keyboard.type('/bullet');
|
|
4517
|
-
await page.keyboard.press('Enter');
|
|
4518
|
-
await page.keyboard.type(item);
|
|
4519
|
-
await page.keyboard.press('Enter');
|
|
4520
|
-
}
|
|
4521
|
-
break;
|
|
4522
|
-
|
|
4523
|
-
case 'numberedList':
|
|
4524
|
-
for (const item of block.items || []) {
|
|
4525
|
-
await page.keyboard.type('/numbered');
|
|
4526
|
-
await page.keyboard.press('Enter');
|
|
4527
|
-
await page.keyboard.type(item);
|
|
4528
|
-
await page.keyboard.press('Enter');
|
|
4529
|
-
}
|
|
4530
|
-
break;
|
|
4531
|
-
|
|
4532
|
-
case 'quote':
|
|
4533
|
-
await page.keyboard.type('/quote');
|
|
4534
|
-
await page.keyboard.press('Enter');
|
|
4535
|
-
await page.keyboard.type(block.content || '');
|
|
4536
|
-
await page.keyboard.press('Enter');
|
|
4537
|
-
break;
|
|
4538
|
-
|
|
4539
|
-
case 'callout':
|
|
4540
|
-
await page.keyboard.type('/callout');
|
|
4541
|
-
await page.keyboard.press('Enter');
|
|
4542
|
-
await page.keyboard.type(block.content || '');
|
|
4543
|
-
await page.keyboard.press('Enter');
|
|
4544
|
-
break;
|
|
4545
|
-
|
|
4546
|
-
case 'divider':
|
|
4547
|
-
await page.keyboard.type('/divider');
|
|
4548
|
-
await page.keyboard.press('Enter');
|
|
4549
|
-
break;
|
|
4550
|
-
|
|
4551
|
-
case 'code':
|
|
4552
|
-
await page.keyboard.type('/code');
|
|
4553
|
-
await page.keyboard.press('Enter');
|
|
4554
|
-
await page.keyboard.type(block.content || '');
|
|
4555
|
-
await page.keyboard.press('Enter');
|
|
4556
|
-
await page.keyboard.press('Escape');
|
|
4557
|
-
break;
|
|
4558
|
-
|
|
4559
|
-
case 'image':
|
|
4560
|
-
if (block.url) {
|
|
4561
|
-
await page.keyboard.type('/image');
|
|
4562
|
-
await page.keyboard.press('Enter');
|
|
4563
|
-
await page.waitForTimeout(500);
|
|
4564
|
-
// Click "Embed link"
|
|
4565
|
-
await page.click('text=Embed link');
|
|
4566
|
-
await page.keyboard.type(block.url);
|
|
4567
|
-
await page.keyboard.press('Enter');
|
|
4568
|
-
}
|
|
4569
|
-
break;
|
|
4570
|
-
|
|
4571
|
-
case 'gallery':
|
|
4572
|
-
await page.keyboard.type('/gallery');
|
|
4573
|
-
await page.keyboard.press('Enter');
|
|
4574
|
-
await page.waitForTimeout(500);
|
|
4575
|
-
// Upload images to gallery
|
|
4576
|
-
for (const imagePath of block.paths || []) {
|
|
4577
|
-
if (fs.existsSync(imagePath)) {
|
|
4578
|
-
await uploadFile(page, imagePath);
|
|
4579
|
-
}
|
|
4580
|
-
}
|
|
4581
|
-
break;
|
|
4582
|
-
}
|
|
4583
|
-
|
|
4584
|
-
await page.waitForTimeout(300);
|
|
4585
|
-
}
|
|
4586
|
-
|
|
4587
|
-
async function uploadFile(page, filePath) {
|
|
4588
|
-
await page.keyboard.type('/file');
|
|
4589
|
-
await page.keyboard.press('Enter');
|
|
4590
|
-
await page.waitForTimeout(500);
|
|
4591
|
-
|
|
4592
|
-
const fileInput = await page.waitForSelector('input[type="file"]', { timeout: 5000 });
|
|
4593
|
-
await fileInput.setInputFiles(filePath);
|
|
4594
|
-
await page.waitForTimeout(2000);
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
createPage().catch(console.error);
|
|
4598
|
-
`;
|
|
4599
|
-
}
|
|
4600
|
-
async function loginToNotion() {
|
|
4601
|
-
const script = generateNotionLoginScript();
|
|
4602
|
-
const scriptPath = path7.join(SESSION_DIR, "login.mjs");
|
|
4603
|
-
await fs7.promises.mkdir(SESSION_DIR, { recursive: true });
|
|
4604
|
-
await fs7.promises.writeFile(scriptPath, script);
|
|
4605
|
-
try {
|
|
4606
|
-
await execAsync3(`npx playwright install chromium`);
|
|
4607
|
-
await execAsync3(`node "${scriptPath}"`, { timeout: 3e5 });
|
|
4608
|
-
const auth = await checkNotionAuth();
|
|
4609
|
-
if (auth.authenticated) {
|
|
4610
|
-
return { success: true };
|
|
4611
|
-
}
|
|
4612
|
-
return { success: false, error: "Login was not completed" };
|
|
4613
|
-
} catch (error) {
|
|
4614
|
-
return {
|
|
4615
|
-
success: false,
|
|
4616
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4617
|
-
};
|
|
4618
|
-
}
|
|
4619
|
-
}
|
|
4620
|
-
async function createNotionPage(options) {
|
|
4621
|
-
const auth = await checkNotionAuth();
|
|
4622
|
-
if (!auth.authenticated) {
|
|
4623
|
-
return { success: false, error: "Not authenticated. Run dlab.notion.login first." };
|
|
4624
|
-
}
|
|
4625
|
-
const content = buildPageContent(options);
|
|
4626
|
-
const files = [...options.screenshots || [], ...options.videos || []];
|
|
4627
|
-
const script = generateNotionUploadScript({
|
|
4628
|
-
title: options.title,
|
|
4629
|
-
content,
|
|
4630
|
-
parentPageId: options.parentPageId,
|
|
4631
|
-
files
|
|
4632
|
-
});
|
|
4633
|
-
const scriptPath = path7.join(SESSION_DIR, "upload.mjs");
|
|
4634
|
-
await fs7.promises.writeFile(scriptPath, script);
|
|
4635
|
-
try {
|
|
4636
|
-
const { stdout } = await execAsync3(`node "${scriptPath}"`, { timeout: 12e4 });
|
|
4637
|
-
const lines = stdout.trim().split("\n");
|
|
4638
|
-
const lastLine = lines[lines.length - 1];
|
|
4639
|
-
const result = JSON.parse(lastLine);
|
|
4640
|
-
return result;
|
|
4641
|
-
} catch (error) {
|
|
4642
|
-
return {
|
|
4643
|
-
success: false,
|
|
4644
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4645
|
-
};
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
4648
|
-
function buildPageContent(options) {
|
|
4649
|
-
const { template = "evidence", description, notes, tags, screenshots, videos } = options;
|
|
4650
|
-
const blocks = [];
|
|
4651
|
-
switch (template) {
|
|
4652
|
-
case "evidence":
|
|
4653
|
-
if (description) {
|
|
4654
|
-
blocks.push({ type: "paragraph", content: description });
|
|
4655
|
-
}
|
|
4656
|
-
if (tags && tags.length > 0) {
|
|
4657
|
-
blocks.push({ type: "paragraph", content: `Tags: ${tags.join(", ")}` });
|
|
4658
|
-
}
|
|
4659
|
-
blocks.push({ type: "divider" });
|
|
4660
|
-
if (screenshots && screenshots.length > 0) {
|
|
4661
|
-
blocks.push({ type: "heading2", content: "Screenshots" });
|
|
4662
|
-
blocks.push({ type: "gallery", paths: screenshots });
|
|
4663
|
-
}
|
|
4664
|
-
if (videos && videos.length > 0) {
|
|
4665
|
-
blocks.push({ type: "heading2", content: "Videos" });
|
|
4666
|
-
for (const video of videos) {
|
|
4667
|
-
blocks.push({ type: "file", url: video });
|
|
4668
|
-
}
|
|
4669
|
-
}
|
|
4670
|
-
if (notes) {
|
|
4671
|
-
blocks.push({ type: "heading2", content: "Notes" });
|
|
4672
|
-
blocks.push({ type: "paragraph", content: notes });
|
|
4673
|
-
}
|
|
4674
|
-
break;
|
|
4675
|
-
case "testReport":
|
|
4676
|
-
blocks.push({ type: "callout", content: "Test Report", color: "blue" });
|
|
4677
|
-
if (description) {
|
|
4678
|
-
blocks.push({ type: "heading2", content: "Summary" });
|
|
4679
|
-
blocks.push({ type: "paragraph", content: description });
|
|
4680
|
-
}
|
|
4681
|
-
if (screenshots && screenshots.length > 0) {
|
|
4682
|
-
blocks.push({ type: "heading2", content: "Evidence" });
|
|
4683
|
-
blocks.push({ type: "gallery", paths: screenshots });
|
|
4684
|
-
}
|
|
4685
|
-
if (notes) {
|
|
4686
|
-
blocks.push({ type: "heading2", content: "Details" });
|
|
4687
|
-
blocks.push({ type: "paragraph", content: notes });
|
|
4688
|
-
}
|
|
4689
|
-
break;
|
|
4690
|
-
case "gallery":
|
|
4691
|
-
if (description) {
|
|
4692
|
-
blocks.push({ type: "paragraph", content: description });
|
|
4693
|
-
}
|
|
4694
|
-
if (screenshots && screenshots.length > 0) {
|
|
4695
|
-
blocks.push({ type: "gallery", paths: screenshots });
|
|
4696
|
-
}
|
|
4697
|
-
break;
|
|
4698
|
-
case "custom":
|
|
4699
|
-
default:
|
|
4700
|
-
if (description) {
|
|
4701
|
-
blocks.push({ type: "paragraph", content: description });
|
|
4702
|
-
}
|
|
4703
|
-
if (screenshots && screenshots.length > 0) {
|
|
4704
|
-
for (const img of screenshots) {
|
|
4705
|
-
blocks.push({ type: "image", url: img });
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
if (notes) {
|
|
4709
|
-
blocks.push({ type: "paragraph", content: notes });
|
|
4710
|
-
}
|
|
4711
|
-
break;
|
|
4712
|
-
}
|
|
4713
|
-
return blocks;
|
|
4714
|
-
}
|
|
4715
|
-
async function quickExportToNotion(title, files, notes) {
|
|
4716
|
-
const screenshots = files.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f));
|
|
4717
|
-
const videos = files.filter((f) => /\.(mp4|mov|webm|gif)$/i.test(f));
|
|
4718
|
-
return createNotionPage({
|
|
4719
|
-
projectId: "quick-export",
|
|
4720
|
-
title,
|
|
4721
|
-
screenshots,
|
|
4722
|
-
videos,
|
|
4723
|
-
notes,
|
|
4724
|
-
template: screenshots.length > 3 ? "gallery" : "evidence"
|
|
4725
|
-
});
|
|
4726
|
-
}
|
|
4727
|
-
|
|
4728
|
-
// src/integrations/drive.ts
|
|
4729
|
-
import { exec as exec4 } from "child_process";
|
|
4730
|
-
import { promisify as promisify4 } from "util";
|
|
4731
|
-
import * as fs8 from "fs";
|
|
4732
|
-
import * as path8 from "path";
|
|
4733
|
-
var execAsync4 = promisify4(exec4);
|
|
4734
|
-
var SESSION_DIR2 = path8.join(DATA_DIR, "sessions", "drive");
|
|
4735
|
-
async function getDriveSessionPath() {
|
|
4736
|
-
await fs8.promises.mkdir(SESSION_DIR2, { recursive: true });
|
|
4737
|
-
return SESSION_DIR2;
|
|
4738
|
-
}
|
|
4739
|
-
async function checkDriveAuth() {
|
|
4740
|
-
const sessionPath = await getDriveSessionPath();
|
|
4741
|
-
const cookiesPath = path8.join(sessionPath, "cookies.json");
|
|
4742
|
-
if (!fs8.existsSync(cookiesPath)) {
|
|
4743
|
-
return { authenticated: false, sessionPath };
|
|
4744
|
-
}
|
|
4745
|
-
try {
|
|
4746
|
-
const cookies = JSON.parse(await fs8.promises.readFile(cookiesPath, "utf-8"));
|
|
4747
4176
|
const googleCookies = cookies.filter(
|
|
4748
4177
|
(c) => c.domain?.includes("google.com") || c.domain?.includes("drive.google.com")
|
|
4749
4178
|
);
|
|
@@ -4763,7 +4192,7 @@ import { chromium } from 'playwright';
|
|
|
4763
4192
|
import * as fs from 'fs';
|
|
4764
4193
|
import * as path from 'path';
|
|
4765
4194
|
|
|
4766
|
-
const SESSION_DIR = '${
|
|
4195
|
+
const SESSION_DIR = '${SESSION_DIR}';
|
|
4767
4196
|
|
|
4768
4197
|
async function login() {
|
|
4769
4198
|
const browser = await chromium.launchPersistentContext(SESSION_DIR, {
|
|
@@ -4803,7 +4232,7 @@ import { chromium } from 'playwright';
|
|
|
4803
4232
|
import * as fs from 'fs';
|
|
4804
4233
|
import * as path from 'path';
|
|
4805
4234
|
|
|
4806
|
-
const SESSION_DIR = '${
|
|
4235
|
+
const SESSION_DIR = '${SESSION_DIR}';
|
|
4807
4236
|
const FILES = ${JSON.stringify(files)};
|
|
4808
4237
|
const FOLDER_NAME = ${JSON.stringify(folderName || null)};
|
|
4809
4238
|
const PARENT_FOLDER_ID = ${JSON.stringify(parentFolderId || null)};
|
|
@@ -4912,12 +4341,12 @@ upload().catch(console.error);
|
|
|
4912
4341
|
}
|
|
4913
4342
|
async function loginToDrive() {
|
|
4914
4343
|
const script = generateDriveLoginScript();
|
|
4915
|
-
const scriptPath =
|
|
4916
|
-
await
|
|
4917
|
-
await
|
|
4344
|
+
const scriptPath = path7.join(SESSION_DIR, "login.mjs");
|
|
4345
|
+
await fs7.promises.mkdir(SESSION_DIR, { recursive: true });
|
|
4346
|
+
await fs7.promises.writeFile(scriptPath, script);
|
|
4918
4347
|
try {
|
|
4919
|
-
await
|
|
4920
|
-
await
|
|
4348
|
+
await execAsync3(`npx playwright install chromium`);
|
|
4349
|
+
await execAsync3(`node "${scriptPath}"`, { timeout: 3e5 });
|
|
4921
4350
|
const auth = await checkDriveAuth();
|
|
4922
4351
|
if (auth.authenticated) {
|
|
4923
4352
|
return { success: true };
|
|
@@ -4935,7 +4364,7 @@ async function uploadToDrive(options) {
|
|
|
4935
4364
|
if (!auth.authenticated) {
|
|
4936
4365
|
return { success: false, error: "Not authenticated. Run dlab.drive.login first." };
|
|
4937
4366
|
}
|
|
4938
|
-
const existingFiles = options.files.filter((f) =>
|
|
4367
|
+
const existingFiles = options.files.filter((f) => fs7.existsSync(f));
|
|
4939
4368
|
if (existingFiles.length === 0) {
|
|
4940
4369
|
return { success: false, error: "No valid files to upload" };
|
|
4941
4370
|
}
|
|
@@ -4944,10 +4373,10 @@ async function uploadToDrive(options) {
|
|
|
4944
4373
|
folderName: options.folderName,
|
|
4945
4374
|
parentFolderId: options.parentFolderId
|
|
4946
4375
|
});
|
|
4947
|
-
const scriptPath =
|
|
4948
|
-
await
|
|
4376
|
+
const scriptPath = path7.join(SESSION_DIR, "upload.mjs");
|
|
4377
|
+
await fs7.promises.writeFile(scriptPath, script);
|
|
4949
4378
|
try {
|
|
4950
|
-
const { stdout } = await
|
|
4379
|
+
const { stdout } = await execAsync3(`node "${scriptPath}"`, { timeout: 18e4 });
|
|
4951
4380
|
const lines = stdout.trim().split("\n");
|
|
4952
4381
|
const lastLine = lines[lines.length - 1];
|
|
4953
4382
|
const result = JSON.parse(lastLine);
|
|
@@ -4978,33 +4407,33 @@ function generateShareableLink(folderId) {
|
|
|
4978
4407
|
}
|
|
4979
4408
|
|
|
4980
4409
|
// src/integrations/jira.ts
|
|
4981
|
-
import { exec as
|
|
4982
|
-
import { promisify as
|
|
4983
|
-
import * as
|
|
4984
|
-
import * as
|
|
4985
|
-
var
|
|
4986
|
-
var
|
|
4410
|
+
import { exec as exec4 } from "child_process";
|
|
4411
|
+
import { promisify as promisify4 } from "util";
|
|
4412
|
+
import * as fs8 from "fs";
|
|
4413
|
+
import * as path8 from "path";
|
|
4414
|
+
var execAsync4 = promisify4(exec4);
|
|
4415
|
+
var SESSION_DIR2 = path8.join(DATA_DIR, "sessions", "jira");
|
|
4987
4416
|
async function getJiraSessionPath() {
|
|
4988
|
-
await
|
|
4989
|
-
return
|
|
4417
|
+
await fs8.promises.mkdir(SESSION_DIR2, { recursive: true });
|
|
4418
|
+
return SESSION_DIR2;
|
|
4990
4419
|
}
|
|
4991
4420
|
async function checkJiraAuth() {
|
|
4992
4421
|
const sessionPath = await getJiraSessionPath();
|
|
4993
|
-
const configPath =
|
|
4994
|
-
const cookiesPath =
|
|
4422
|
+
const configPath = path8.join(sessionPath, "config.json");
|
|
4423
|
+
const cookiesPath = path8.join(sessionPath, "cookies.json");
|
|
4995
4424
|
let baseUrl;
|
|
4996
|
-
if (
|
|
4425
|
+
if (fs8.existsSync(configPath)) {
|
|
4997
4426
|
try {
|
|
4998
|
-
const config = JSON.parse(await
|
|
4427
|
+
const config = JSON.parse(await fs8.promises.readFile(configPath, "utf-8"));
|
|
4999
4428
|
baseUrl = config.baseUrl;
|
|
5000
4429
|
} catch {
|
|
5001
4430
|
}
|
|
5002
4431
|
}
|
|
5003
|
-
if (!
|
|
4432
|
+
if (!fs8.existsSync(cookiesPath)) {
|
|
5004
4433
|
return { authenticated: false, sessionPath, baseUrl };
|
|
5005
4434
|
}
|
|
5006
4435
|
try {
|
|
5007
|
-
const cookies = JSON.parse(await
|
|
4436
|
+
const cookies = JSON.parse(await fs8.promises.readFile(cookiesPath, "utf-8"));
|
|
5008
4437
|
const jiraCookies = cookies.filter(
|
|
5009
4438
|
(c) => c.domain?.includes("atlassian.net") || c.domain?.includes("jira")
|
|
5010
4439
|
);
|
|
@@ -5021,8 +4450,8 @@ async function checkJiraAuth() {
|
|
|
5021
4450
|
}
|
|
5022
4451
|
async function saveJiraConfig(baseUrl) {
|
|
5023
4452
|
const sessionPath = await getJiraSessionPath();
|
|
5024
|
-
const configPath =
|
|
5025
|
-
await
|
|
4453
|
+
const configPath = path8.join(sessionPath, "config.json");
|
|
4454
|
+
await fs8.promises.writeFile(configPath, JSON.stringify({ baseUrl }, null, 2));
|
|
5026
4455
|
}
|
|
5027
4456
|
function generateJiraLoginScript(baseUrl) {
|
|
5028
4457
|
return `
|
|
@@ -5030,7 +4459,7 @@ import { chromium } from 'playwright';
|
|
|
5030
4459
|
import * as fs from 'fs';
|
|
5031
4460
|
import * as path from 'path';
|
|
5032
4461
|
|
|
5033
|
-
const SESSION_DIR = '${
|
|
4462
|
+
const SESSION_DIR = '${SESSION_DIR2}';
|
|
5034
4463
|
const BASE_URL = '${baseUrl}';
|
|
5035
4464
|
|
|
5036
4465
|
async function login() {
|
|
@@ -5077,7 +4506,7 @@ import { chromium } from 'playwright';
|
|
|
5077
4506
|
import * as fs from 'fs';
|
|
5078
4507
|
import * as path from 'path';
|
|
5079
4508
|
|
|
5080
|
-
const SESSION_DIR = '${
|
|
4509
|
+
const SESSION_DIR = '${SESSION_DIR2}';
|
|
5081
4510
|
const BASE_URL = '${baseUrl}';
|
|
5082
4511
|
const ISSUE_KEY = '${issueKey}';
|
|
5083
4512
|
const FILES = ${JSON.stringify(files)};
|
|
@@ -5193,7 +4622,7 @@ import { chromium } from 'playwright';
|
|
|
5193
4622
|
import * as fs from 'fs';
|
|
5194
4623
|
import * as path from 'path';
|
|
5195
4624
|
|
|
5196
|
-
const SESSION_DIR = '${
|
|
4625
|
+
const SESSION_DIR = '${SESSION_DIR2}';
|
|
5197
4626
|
const BASE_URL = '${baseUrl}';
|
|
5198
4627
|
const PROJECT_KEY = '${projectKey}';
|
|
5199
4628
|
const SUMMARY = ${JSON.stringify(summary)};
|
|
@@ -5315,12 +4744,12 @@ createIssue().catch(console.error);
|
|
|
5315
4744
|
}
|
|
5316
4745
|
async function loginToJira(baseUrl) {
|
|
5317
4746
|
const script = generateJiraLoginScript(baseUrl);
|
|
5318
|
-
const scriptPath =
|
|
5319
|
-
await
|
|
5320
|
-
await
|
|
4747
|
+
const scriptPath = path8.join(SESSION_DIR2, "login.mjs");
|
|
4748
|
+
await fs8.promises.mkdir(SESSION_DIR2, { recursive: true });
|
|
4749
|
+
await fs8.promises.writeFile(scriptPath, script);
|
|
5321
4750
|
try {
|
|
5322
|
-
await
|
|
5323
|
-
await
|
|
4751
|
+
await execAsync4(`npx playwright install chromium`);
|
|
4752
|
+
await execAsync4(`node "${scriptPath}"`, { timeout: 3e5 });
|
|
5324
4753
|
const auth = await checkJiraAuth();
|
|
5325
4754
|
if (auth.authenticated) {
|
|
5326
4755
|
await saveJiraConfig(baseUrl);
|
|
@@ -5342,7 +4771,7 @@ async function attachToJiraIssue(options) {
|
|
|
5342
4771
|
if (!auth.baseUrl) {
|
|
5343
4772
|
return { success: false, error: "Jira base URL not configured." };
|
|
5344
4773
|
}
|
|
5345
|
-
const existingFiles = options.files.filter((f) =>
|
|
4774
|
+
const existingFiles = options.files.filter((f) => fs8.existsSync(f));
|
|
5346
4775
|
if (existingFiles.length === 0 && !options.comment) {
|
|
5347
4776
|
return { success: false, error: "No valid files to attach and no comment provided" };
|
|
5348
4777
|
}
|
|
@@ -5352,10 +4781,10 @@ async function attachToJiraIssue(options) {
|
|
|
5352
4781
|
files: existingFiles,
|
|
5353
4782
|
comment: options.comment
|
|
5354
4783
|
});
|
|
5355
|
-
const scriptPath =
|
|
5356
|
-
await
|
|
4784
|
+
const scriptPath = path8.join(SESSION_DIR2, "attach.mjs");
|
|
4785
|
+
await fs8.promises.writeFile(scriptPath, script);
|
|
5357
4786
|
try {
|
|
5358
|
-
const { stdout } = await
|
|
4787
|
+
const { stdout } = await execAsync4(`node "${scriptPath}"`, { timeout: 12e4 });
|
|
5359
4788
|
const lines = stdout.trim().split("\n");
|
|
5360
4789
|
const lastLine = lines[lines.length - 1];
|
|
5361
4790
|
const result = JSON.parse(lastLine);
|
|
@@ -5385,10 +4814,10 @@ async function createJiraIssue(options) {
|
|
|
5385
4814
|
labels: options.labels,
|
|
5386
4815
|
attachments: options.attachments
|
|
5387
4816
|
});
|
|
5388
|
-
const scriptPath =
|
|
5389
|
-
await
|
|
4817
|
+
const scriptPath = path8.join(SESSION_DIR2, "create.mjs");
|
|
4818
|
+
await fs8.promises.writeFile(scriptPath, script);
|
|
5390
4819
|
try {
|
|
5391
|
-
const { stdout } = await
|
|
4820
|
+
const { stdout } = await execAsync4(`node "${scriptPath}"`, { timeout: 12e4 });
|
|
5392
4821
|
const lines = stdout.trim().split("\n");
|
|
5393
4822
|
const lastLine = lines[lines.length - 1];
|
|
5394
4823
|
const result = JSON.parse(lastLine);
|
|
@@ -5467,8 +4896,8 @@ var notionExportTool = {
|
|
|
5467
4896
|
if (!auth.authenticated) {
|
|
5468
4897
|
return createErrorResult("Not authenticated. Run dlab.notion.login first.");
|
|
5469
4898
|
}
|
|
5470
|
-
const validScreenshots = (params.screenshots || []).filter((f) =>
|
|
5471
|
-
const validVideos = (params.videos || []).filter((f) =>
|
|
4899
|
+
const validScreenshots = (params.screenshots || []).filter((f) => fs9.existsSync(f));
|
|
4900
|
+
const validVideos = (params.videos || []).filter((f) => fs9.existsSync(f));
|
|
5472
4901
|
const result = await createNotionPage({
|
|
5473
4902
|
projectId: "mcp-export",
|
|
5474
4903
|
title: params.title,
|
|
@@ -5503,7 +4932,7 @@ var notionQuickExportTool = {
|
|
|
5503
4932
|
if (!auth.authenticated) {
|
|
5504
4933
|
return createErrorResult("Not authenticated. Run dlab.notion.login first.");
|
|
5505
4934
|
}
|
|
5506
|
-
const validFiles = params.files.filter((f) =>
|
|
4935
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5507
4936
|
if (validFiles.length === 0) {
|
|
5508
4937
|
return createErrorResult("No valid files to upload");
|
|
5509
4938
|
}
|
|
@@ -5559,7 +4988,7 @@ var driveUploadTool = {
|
|
|
5559
4988
|
if (!auth.authenticated) {
|
|
5560
4989
|
return createErrorResult("Not authenticated. Run dlab.drive.login first.");
|
|
5561
4990
|
}
|
|
5562
|
-
const validFiles = params.files.filter((f) =>
|
|
4991
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5563
4992
|
if (validFiles.length === 0) {
|
|
5564
4993
|
return createErrorResult("No valid files to upload");
|
|
5565
4994
|
}
|
|
@@ -5592,7 +5021,7 @@ var driveQuickExportTool = {
|
|
|
5592
5021
|
if (!auth.authenticated) {
|
|
5593
5022
|
return createErrorResult("Not authenticated. Run dlab.drive.login first.");
|
|
5594
5023
|
}
|
|
5595
|
-
const validFiles = params.files.filter((f) =>
|
|
5024
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5596
5025
|
if (validFiles.length === 0) {
|
|
5597
5026
|
return createErrorResult("No valid files to upload");
|
|
5598
5027
|
}
|
|
@@ -5680,7 +5109,7 @@ var jiraAttachTool = {
|
|
|
5680
5109
|
if (!issueKey) {
|
|
5681
5110
|
return createErrorResult("Invalid issue key format");
|
|
5682
5111
|
}
|
|
5683
|
-
const validFiles = params.files.filter((f) =>
|
|
5112
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5684
5113
|
const result = await attachToJiraIssue({
|
|
5685
5114
|
issueKey,
|
|
5686
5115
|
files: validFiles,
|
|
@@ -5713,7 +5142,7 @@ var jiraCreateTool = {
|
|
|
5713
5142
|
if (!auth.authenticated) {
|
|
5714
5143
|
return createErrorResult("Not authenticated. Run dlab.jira.login first.");
|
|
5715
5144
|
}
|
|
5716
|
-
const validAttachments = (params.attachments || []).filter((f) =>
|
|
5145
|
+
const validAttachments = (params.attachments || []).filter((f) => fs9.existsSync(f));
|
|
5717
5146
|
const result = await createJiraIssue({
|
|
5718
5147
|
projectKey: params.projectKey,
|
|
5719
5148
|
summary: params.summary,
|
|
@@ -5750,7 +5179,7 @@ var jiraCommentTool = {
|
|
|
5750
5179
|
if (!issueKey) {
|
|
5751
5180
|
return createErrorResult("Invalid issue key format");
|
|
5752
5181
|
}
|
|
5753
|
-
const validAttachments = (params.attachments || []).filter((f) =>
|
|
5182
|
+
const validAttachments = (params.attachments || []).filter((f) => fs9.existsSync(f));
|
|
5754
5183
|
const result = await addJiraComment({
|
|
5755
5184
|
issueKey,
|
|
5756
5185
|
comment: params.comment,
|
|
@@ -5783,7 +5212,7 @@ var jiraQuickExportTool = {
|
|
|
5783
5212
|
if (!issueKey) {
|
|
5784
5213
|
return createErrorResult("Invalid issue key format");
|
|
5785
5214
|
}
|
|
5786
|
-
const validFiles = params.files.filter((f) =>
|
|
5215
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5787
5216
|
if (validFiles.length === 0) {
|
|
5788
5217
|
return createErrorResult("No valid files to attach");
|
|
5789
5218
|
}
|
|
@@ -5809,7 +5238,7 @@ var exportToTool = {
|
|
|
5809
5238
|
jiraIssueKey: z8.string().optional().describe("Jira issue key (required if jira is a destination)")
|
|
5810
5239
|
}),
|
|
5811
5240
|
handler: async (params) => {
|
|
5812
|
-
const validFiles = params.files.filter((f) =>
|
|
5241
|
+
const validFiles = params.files.filter((f) => fs9.existsSync(f));
|
|
5813
5242
|
if (validFiles.length === 0) {
|
|
5814
5243
|
return createErrorResult("No valid files to export");
|
|
5815
5244
|
}
|
|
@@ -6376,7 +5805,7 @@ var taskHubTools = [
|
|
|
6376
5805
|
|
|
6377
5806
|
// src/mcp/tools/esvp.ts
|
|
6378
5807
|
import { readFile } from "fs/promises";
|
|
6379
|
-
import { join as
|
|
5808
|
+
import { join as join11, resolve } from "path";
|
|
6380
5809
|
import { z as z10 } from "zod";
|
|
6381
5810
|
var jsonObjectSchema = z10.record(z10.string(), z10.any());
|
|
6382
5811
|
var actionSchema = z10.object({
|
|
@@ -6419,10 +5848,10 @@ var projectValidationNetworkSchema = z10.object({
|
|
|
6419
5848
|
faults: jsonObjectSchema.optional()
|
|
6420
5849
|
}).nullable();
|
|
6421
5850
|
function getMobileRecordingDir(recordingId) {
|
|
6422
|
-
return
|
|
5851
|
+
return join11(PROJECTS_DIR, "maestro-recordings", recordingId);
|
|
6423
5852
|
}
|
|
6424
5853
|
function getMobileRecordingSessionPath(recordingId) {
|
|
6425
|
-
return
|
|
5854
|
+
return join11(getMobileRecordingDir(recordingId), "session.json");
|
|
6426
5855
|
}
|
|
6427
5856
|
async function readMobileRecordingSession(recordingId) {
|
|
6428
5857
|
const sessionPath = getMobileRecordingSessionPath(recordingId);
|
|
@@ -6466,9 +5895,9 @@ function resolveAppLabBaseUrl(appLabUrl) {
|
|
|
6466
5895
|
const raw = String(appLabUrl || process.env.DISCOVERYLAB_APP_URL || "http://127.0.0.1:3847").trim();
|
|
6467
5896
|
return raw.replace(/\/+$/, "");
|
|
6468
5897
|
}
|
|
6469
|
-
async function callAppLabJson(
|
|
5898
|
+
async function callAppLabJson(path9, init = {}, appLabUrl) {
|
|
6470
5899
|
const baseUrl = resolveAppLabBaseUrl(appLabUrl);
|
|
6471
|
-
const targetUrl = `${baseUrl}${
|
|
5900
|
+
const targetUrl = `${baseUrl}${path9.startsWith("/") ? path9 : `/${path9}`}`;
|
|
6472
5901
|
const response = await fetch(targetUrl, init).catch((error) => {
|
|
6473
5902
|
throw new Error(`Failed to reach App Lab at ${baseUrl}. Start the local App Lab server before using project-scoped ESVP tools. ${error instanceof Error ? error.message : String(error)}`);
|
|
6474
5903
|
});
|
|
@@ -7300,9 +6729,9 @@ var projectESVPAppTraceBootstrapTool = {
|
|
|
7300
6729
|
const searchParams = new URLSearchParams();
|
|
7301
6730
|
if (params.recordingId) searchParams.set("recordingId", params.recordingId);
|
|
7302
6731
|
if (params.appId) searchParams.set("appId", params.appId);
|
|
7303
|
-
const
|
|
6732
|
+
const path9 = `/api/testing/mobile/app-http-trace/bootstrap?${searchParams.toString()}`;
|
|
7304
6733
|
try {
|
|
7305
|
-
const result = await callAppLabJson(
|
|
6734
|
+
const result = await callAppLabJson(path9, { method: "GET" }, params.appLabUrl);
|
|
7306
6735
|
return createJsonResult({
|
|
7307
6736
|
appLabUrl: result.appLabUrl,
|
|
7308
6737
|
recordingId: params.recordingId,
|
|
@@ -7398,6 +6827,181 @@ var templateTools = [
|
|
|
7398
6827
|
templateRenderTool
|
|
7399
6828
|
];
|
|
7400
6829
|
|
|
6830
|
+
// src/mcp/tools/knowledge.ts
|
|
6831
|
+
import { z as z12 } from "zod";
|
|
6832
|
+
import { desc as desc2 } from "drizzle-orm";
|
|
6833
|
+
var knowledgeSearchTool = {
|
|
6834
|
+
name: "dlab.knowledge.search",
|
|
6835
|
+
description: `Search across all DiscoveryLab projects for app flows, UI elements, screens, and behaviors. Use this when the user asks about how an app works, what a specific screen looks like, or any question about captured app flows. Returns relevant projects with their analysis, OCR text, and context.`,
|
|
6836
|
+
inputSchema: z12.object({
|
|
6837
|
+
query: z12.string().describe("Search query - app name, screen name, UI element, flow description, or any keyword"),
|
|
6838
|
+
limit: z12.number().optional().describe("Max results (default: 5)")
|
|
6839
|
+
}),
|
|
6840
|
+
handler: async (params) => {
|
|
6841
|
+
try {
|
|
6842
|
+
const db = getDatabase();
|
|
6843
|
+
const allProjects = await db.select().from(projects).orderBy(desc2(projects.updatedAt));
|
|
6844
|
+
if (allProjects.length === 0) {
|
|
6845
|
+
return createTextResult("No projects in DiscoveryLab. The user needs to capture some app flows first.");
|
|
6846
|
+
}
|
|
6847
|
+
const query = params.query.toLowerCase();
|
|
6848
|
+
const queryTerms = query.split(/\s+/).filter((t) => t.length > 1);
|
|
6849
|
+
const limit = params.limit || 5;
|
|
6850
|
+
const scored = allProjects.map((p) => {
|
|
6851
|
+
let score = 0;
|
|
6852
|
+
const searchFields = [
|
|
6853
|
+
{ text: p.name || "", weight: 3 },
|
|
6854
|
+
{ text: p.marketingTitle || "", weight: 3 },
|
|
6855
|
+
{ text: p.marketingDescription || "", weight: 2 },
|
|
6856
|
+
{ text: p.aiSummary || "", weight: 2 },
|
|
6857
|
+
{ text: p.ocrText || "", weight: 1 },
|
|
6858
|
+
{ text: p.tags || "", weight: 2 },
|
|
6859
|
+
{ text: p.linkedTicket || "", weight: 2 },
|
|
6860
|
+
{ text: p.taskHubLinks || "", weight: 1 },
|
|
6861
|
+
{ text: p.platform || "", weight: 1 }
|
|
6862
|
+
];
|
|
6863
|
+
for (const field of searchFields) {
|
|
6864
|
+
const text = field.text.toLowerCase();
|
|
6865
|
+
for (const term of queryTerms) {
|
|
6866
|
+
if (text.includes(term)) {
|
|
6867
|
+
score += field.weight;
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
if (text.includes(query)) {
|
|
6871
|
+
score += field.weight * 2;
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
return { project: p, score };
|
|
6875
|
+
}).filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
6876
|
+
if (scored.length === 0) {
|
|
6877
|
+
const names = allProjects.slice(0, 15).map(
|
|
6878
|
+
(p) => `- ${p.marketingTitle || p.name} (${p.platform || "unknown"}, ${p.status})`
|
|
6879
|
+
).join("\n");
|
|
6880
|
+
return createTextResult(`No projects matched "${params.query}". Available projects:
|
|
6881
|
+
${names}
|
|
6882
|
+
|
|
6883
|
+
Try searching with different keywords or ask about a specific app/flow.`);
|
|
6884
|
+
}
|
|
6885
|
+
const results = scored.map(({ project: p, score }) => {
|
|
6886
|
+
let userFlow = "";
|
|
6887
|
+
if (p.aiSummary) {
|
|
6888
|
+
const flowMatch = p.aiSummary.match(/## (?:User Flow|Likely User Flow|App Flow)\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
6889
|
+
if (flowMatch) userFlow = flowMatch[1].trim().slice(0, 500);
|
|
6890
|
+
}
|
|
6891
|
+
let uiElements = "";
|
|
6892
|
+
if (p.aiSummary) {
|
|
6893
|
+
const uiMatch = p.aiSummary.match(/## (?:UI Elements Found|Key UI Elements)\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
6894
|
+
if (uiMatch) uiElements = uiMatch[1].trim().slice(0, 400);
|
|
6895
|
+
}
|
|
6896
|
+
let overview = "";
|
|
6897
|
+
if (p.aiSummary) {
|
|
6898
|
+
const overviewMatch = p.aiSummary.match(/## (?:App Overview|Page \/ App Overview)\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
6899
|
+
if (overviewMatch) overview = overviewMatch[1].trim().slice(0, 300);
|
|
6900
|
+
}
|
|
6901
|
+
return {
|
|
6902
|
+
projectId: p.id,
|
|
6903
|
+
name: p.marketingTitle || p.name,
|
|
6904
|
+
platform: p.platform,
|
|
6905
|
+
status: p.status,
|
|
6906
|
+
overview: overview || p.marketingDescription || "",
|
|
6907
|
+
userFlow: userFlow || "",
|
|
6908
|
+
uiElements: uiElements || "",
|
|
6909
|
+
ocrSample: p.ocrText?.slice(0, 300) || "",
|
|
6910
|
+
frameCount: p.frameCount || 0,
|
|
6911
|
+
linkedTicket: p.linkedTicket || "",
|
|
6912
|
+
tags: p.tags || "",
|
|
6913
|
+
relevanceScore: score
|
|
6914
|
+
};
|
|
6915
|
+
});
|
|
6916
|
+
let response = `Found ${results.length} project(s) matching "${params.query}":
|
|
6917
|
+
|
|
6918
|
+
`;
|
|
6919
|
+
for (const r of results) {
|
|
6920
|
+
response += `### ${r.name}
|
|
6921
|
+
`;
|
|
6922
|
+
response += `Platform: ${r.platform || "unknown"} | Screens: ${r.frameCount} | Status: ${r.status}
|
|
6923
|
+
`;
|
|
6924
|
+
if (r.linkedTicket) response += `Ticket: ${r.linkedTicket}
|
|
6925
|
+
`;
|
|
6926
|
+
if (r.overview) response += `
|
|
6927
|
+
**Overview:** ${r.overview}
|
|
6928
|
+
`;
|
|
6929
|
+
if (r.userFlow) response += `
|
|
6930
|
+
**User Flow:**
|
|
6931
|
+
${r.userFlow}
|
|
6932
|
+
`;
|
|
6933
|
+
if (r.uiElements) response += `
|
|
6934
|
+
**UI Elements:**
|
|
6935
|
+
${r.uiElements}
|
|
6936
|
+
`;
|
|
6937
|
+
if (r.ocrSample) response += `
|
|
6938
|
+
**Screen Text (OCR):** ${r.ocrSample}
|
|
6939
|
+
`;
|
|
6940
|
+
response += `
|
|
6941
|
+
Project ID: ${r.projectId} (use dlab.project.get for full details)
|
|
6942
|
+
`;
|
|
6943
|
+
response += `---
|
|
6944
|
+
`;
|
|
6945
|
+
}
|
|
6946
|
+
return createTextResult(response);
|
|
6947
|
+
} catch (error) {
|
|
6948
|
+
return createErrorResult(`Knowledge search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6949
|
+
}
|
|
6950
|
+
}
|
|
6951
|
+
};
|
|
6952
|
+
var knowledgeSummaryTool = {
|
|
6953
|
+
name: "dlab.knowledge.summary",
|
|
6954
|
+
description: "Get a high-level summary of all captured app knowledge in DiscoveryLab. Lists all projects grouped by app/platform with key stats.",
|
|
6955
|
+
inputSchema: z12.object({}),
|
|
6956
|
+
handler: async () => {
|
|
6957
|
+
try {
|
|
6958
|
+
const db = getDatabase();
|
|
6959
|
+
const allProjects = await db.select().from(projects).orderBy(desc2(projects.updatedAt));
|
|
6960
|
+
if (allProjects.length === 0) {
|
|
6961
|
+
return createTextResult("No projects in DiscoveryLab yet.");
|
|
6962
|
+
}
|
|
6963
|
+
const appGroups = /* @__PURE__ */ new Map();
|
|
6964
|
+
for (const p of allProjects) {
|
|
6965
|
+
const appName = (p.marketingTitle || p.name || "Unknown").split(/\s+/)[0];
|
|
6966
|
+
if (!appGroups.has(appName)) appGroups.set(appName, []);
|
|
6967
|
+
appGroups.get(appName).push(p);
|
|
6968
|
+
}
|
|
6969
|
+
let response = `# DiscoveryLab Knowledge Base
|
|
6970
|
+
`;
|
|
6971
|
+
response += `**${allProjects.length} projects** across **${appGroups.size} app(s)**
|
|
6972
|
+
|
|
6973
|
+
`;
|
|
6974
|
+
for (const [app, projs] of appGroups) {
|
|
6975
|
+
const platforms = [...new Set(projs.map((p) => p.platform).filter(Boolean))];
|
|
6976
|
+
const analyzed = projs.filter((p) => p.status === "analyzed").length;
|
|
6977
|
+
const totalFrames = projs.reduce((sum, p) => sum + (p.frameCount || 0), 0);
|
|
6978
|
+
response += `## ${app} (${platforms.join(", ")})
|
|
6979
|
+
`;
|
|
6980
|
+
response += `${projs.length} flows captured | ${analyzed} analyzed | ${totalFrames} screens
|
|
6981
|
+
`;
|
|
6982
|
+
for (const p of projs.slice(0, 5)) {
|
|
6983
|
+
const title = p.marketingTitle || p.name;
|
|
6984
|
+
response += `- **${title}** (${p.frameCount || 0} screens, ${p.status})`;
|
|
6985
|
+
if (p.linkedTicket) response += ` [${p.linkedTicket}]`;
|
|
6986
|
+
response += "\n";
|
|
6987
|
+
}
|
|
6988
|
+
if (projs.length > 5) response += ` ... and ${projs.length - 5} more
|
|
6989
|
+
`;
|
|
6990
|
+
response += "\n";
|
|
6991
|
+
}
|
|
6992
|
+
response += `
|
|
6993
|
+
Use \`dlab.knowledge.search\` with a query to find specific flows or screens.`;
|
|
6994
|
+
return createTextResult(response);
|
|
6995
|
+
} catch (error) {
|
|
6996
|
+
return createErrorResult(`Summary failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
};
|
|
7000
|
+
var knowledgeTools = [
|
|
7001
|
+
knowledgeSearchTool,
|
|
7002
|
+
knowledgeSummaryTool
|
|
7003
|
+
];
|
|
7004
|
+
|
|
7401
7005
|
export {
|
|
7402
7006
|
uiOpenTool,
|
|
7403
7007
|
uiStatusTool,
|
|
@@ -7503,5 +7107,8 @@ export {
|
|
|
7503
7107
|
esvpTools,
|
|
7504
7108
|
templateListTool,
|
|
7505
7109
|
templateRenderTool,
|
|
7506
|
-
templateTools
|
|
7110
|
+
templateTools,
|
|
7111
|
+
knowledgeSearchTool,
|
|
7112
|
+
knowledgeSummaryTool,
|
|
7113
|
+
knowledgeTools
|
|
7507
7114
|
};
|