@veolab/discoverylab 1.3.3 → 1.4.0

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.
Files changed (37) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/chunk-34KRJWZL.js +477 -0
  4. package/dist/chunk-4VNS5WPM.js +42 -0
  5. package/dist/{chunk-FIL7IWEL.js → chunk-DGXAP477.js} +1 -1
  6. package/dist/{chunk-HGWEHWKJ.js → chunk-DKAX5RCX.js} +1 -1
  7. package/dist/{chunk-7EDIUVIO.js → chunk-EU63HPKT.js} +1 -1
  8. package/dist/chunk-QMUEC6B5.js +288 -0
  9. package/dist/{chunk-FNUN7EPB.js → chunk-RCY26WEK.js} +2 -2
  10. package/dist/{chunk-ZLHIHMSL.js → chunk-SWZIBO2R.js} +1 -1
  11. package/dist/chunk-VYYAP5G5.js +265 -0
  12. package/dist/{chunk-VVIOB362.js → chunk-XAMA3JJG.js} +18 -1
  13. package/dist/{chunk-LXSWDEXV.js → chunk-XWBFSSNB.js} +10224 -389
  14. package/dist/{chunk-AHVBE25Y.js → chunk-YNLUOZSZ.js} +274 -667
  15. package/dist/cli.js +33 -31
  16. package/dist/{db-6WLEVKUV.js → db-745LC5YC.js} +2 -2
  17. package/dist/document-AE4XI2CP.js +104 -0
  18. package/dist/{esvp-KVOWYW6G.js → esvp-4LIAU76K.js} +3 -3
  19. package/dist/{esvp-mobile-GZ5EMYPG.js → esvp-mobile-FKFHDS5Q.js} +4 -4
  20. package/dist/frames-RCNLSDD6.js +24 -0
  21. package/dist/{gridCompositor-M3K3LCLZ.js → gridCompositor-VUWBZXYL.js} +262 -3
  22. package/dist/index.d.ts +32 -0
  23. package/dist/index.html +1197 -9
  24. package/dist/index.js +15 -10
  25. package/dist/notion-api-OXSWOJPZ.js +190 -0
  26. package/dist/{ocr-QDYNCSPE.js → ocr-FXRLEP66.js} +1 -1
  27. package/dist/{playwright-VZ7PXDC5.js → playwright-GYKUH34L.js} +3 -3
  28. package/dist/renderer-D22GCMMD.js +17 -0
  29. package/dist/{server-6N3KIEGP.js → server-NTT2XGCC.js} +1 -1
  30. package/dist/server-TKYRIYJ6.js +24 -0
  31. package/dist/{setup-2SQC5UHJ.js → setup-O6WQQAGP.js} +3 -3
  32. package/dist/templates/bundle/bundle.js +4 -2
  33. package/dist/{tools-YGM5HRIB.js → tools-FVVWKEGC.js} +15 -7
  34. package/package.json +2 -2
  35. package/skills/knowledge-brain/SKILL.md +64 -0
  36. package/dist/chunk-MLKGABMK.js +0 -9
  37. package/dist/server-T5X6GGOO.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-HGWEHWKJ.js";
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-FIL7IWEL.js";
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-7EDIUVIO.js";
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-VVIOB362.js";
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
- // src/core/analyze/frames.ts
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 (!existsSync4(videoPath)) {
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 (!existsSync4(imagePath)) {
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 (!existsSync4(params.videoPath)) {
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(existsSync4);
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: basename2(r.imagePath),
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 (!existsSync4(params.videoPath)) {
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: basename2(params.videoPath),
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 (!existsSync4(params.videoPath)) {
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 (!existsSync4(params.videoPath)) {
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 spawn4 } from "child_process";
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 = spawn4("ffmpeg", args, { stdio: ["pipe", "pipe", "pipe"] });
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 fs10 from "fs";
4155
+ import * as fs9 from "fs";
4353
4156
 
4354
- // src/integrations/notion.ts
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", "notion");
4361
- async function getNotionSessionPath() {
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 checkNotionAuth() {
4366
- const sessionPath = await getNotionSessionPath();
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 = '${SESSION_DIR2}';
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 = '${SESSION_DIR2}';
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 = path8.join(SESSION_DIR2, "login.mjs");
4916
- await fs8.promises.mkdir(SESSION_DIR2, { recursive: true });
4917
- await fs8.promises.writeFile(scriptPath, script);
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 execAsync4(`npx playwright install chromium`);
4920
- await execAsync4(`node "${scriptPath}"`, { timeout: 3e5 });
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) => fs8.existsSync(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 = path8.join(SESSION_DIR2, "upload.mjs");
4948
- await fs8.promises.writeFile(scriptPath, script);
4376
+ const scriptPath = path7.join(SESSION_DIR, "upload.mjs");
4377
+ await fs7.promises.writeFile(scriptPath, script);
4949
4378
  try {
4950
- const { stdout } = await execAsync4(`node "${scriptPath}"`, { timeout: 18e4 });
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 exec5 } from "child_process";
4982
- import { promisify as promisify5 } from "util";
4983
- import * as fs9 from "fs";
4984
- import * as path9 from "path";
4985
- var execAsync5 = promisify5(exec5);
4986
- var SESSION_DIR3 = path9.join(DATA_DIR, "sessions", "jira");
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 fs9.promises.mkdir(SESSION_DIR3, { recursive: true });
4989
- return SESSION_DIR3;
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 = path9.join(sessionPath, "config.json");
4994
- const cookiesPath = path9.join(sessionPath, "cookies.json");
4422
+ const configPath = path8.join(sessionPath, "config.json");
4423
+ const cookiesPath = path8.join(sessionPath, "cookies.json");
4995
4424
  let baseUrl;
4996
- if (fs9.existsSync(configPath)) {
4425
+ if (fs8.existsSync(configPath)) {
4997
4426
  try {
4998
- const config = JSON.parse(await fs9.promises.readFile(configPath, "utf-8"));
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 (!fs9.existsSync(cookiesPath)) {
4432
+ if (!fs8.existsSync(cookiesPath)) {
5004
4433
  return { authenticated: false, sessionPath, baseUrl };
5005
4434
  }
5006
4435
  try {
5007
- const cookies = JSON.parse(await fs9.promises.readFile(cookiesPath, "utf-8"));
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 = path9.join(sessionPath, "config.json");
5025
- await fs9.promises.writeFile(configPath, JSON.stringify({ baseUrl }, null, 2));
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 = '${SESSION_DIR3}';
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 = '${SESSION_DIR3}';
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 = '${SESSION_DIR3}';
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 = path9.join(SESSION_DIR3, "login.mjs");
5319
- await fs9.promises.mkdir(SESSION_DIR3, { recursive: true });
5320
- await fs9.promises.writeFile(scriptPath, script);
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 execAsync5(`npx playwright install chromium`);
5323
- await execAsync5(`node "${scriptPath}"`, { timeout: 3e5 });
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) => fs9.existsSync(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 = path9.join(SESSION_DIR3, "attach.mjs");
5356
- await fs9.promises.writeFile(scriptPath, script);
4784
+ const scriptPath = path8.join(SESSION_DIR2, "attach.mjs");
4785
+ await fs8.promises.writeFile(scriptPath, script);
5357
4786
  try {
5358
- const { stdout } = await execAsync5(`node "${scriptPath}"`, { timeout: 12e4 });
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 = path9.join(SESSION_DIR3, "create.mjs");
5389
- await fs9.promises.writeFile(scriptPath, script);
4817
+ const scriptPath = path8.join(SESSION_DIR2, "create.mjs");
4818
+ await fs8.promises.writeFile(scriptPath, script);
5390
4819
  try {
5391
- const { stdout } = await execAsync5(`node "${scriptPath}"`, { timeout: 12e4 });
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) => fs10.existsSync(f));
5471
- const validVideos = (params.videos || []).filter((f) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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) => fs10.existsSync(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 join13, resolve } from "path";
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 join13(PROJECTS_DIR, "maestro-recordings", recordingId);
5851
+ return join11(PROJECTS_DIR, "maestro-recordings", recordingId);
6423
5852
  }
6424
5853
  function getMobileRecordingSessionPath(recordingId) {
6425
- return join13(getMobileRecordingDir(recordingId), "session.json");
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(path10, init = {}, appLabUrl) {
5898
+ async function callAppLabJson(path9, init = {}, appLabUrl) {
6470
5899
  const baseUrl = resolveAppLabBaseUrl(appLabUrl);
6471
- const targetUrl = `${baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
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 path10 = `/api/testing/mobile/app-http-trace/bootstrap?${searchParams.toString()}`;
6732
+ const path9 = `/api/testing/mobile/app-http-trace/bootstrap?${searchParams.toString()}`;
7304
6733
  try {
7305
- const result = await callAppLabJson(path10, { method: "GET" }, params.appLabUrl);
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
  };