markupr 2.6.4 → 2.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1308,6 +1308,12 @@ ${REPORT_SUPPORT_LINE}
1308
1308
  md += `![Frame at ${frameTimestamp}](${relativePath})
1309
1309
 
1310
1310
  `;
1311
+ const contextLine = this.formatCaptureContextLine(frame.captureContext);
1312
+ if (contextLine) {
1313
+ md += `> ${contextLine}
1314
+
1315
+ `;
1316
+ }
1311
1317
  }
1312
1318
  }
1313
1319
  }
@@ -1359,6 +1365,16 @@ ${REPORT_SUPPORT_LINE}
1359
1365
  }
1360
1366
  return path.relative(sessionDir, framePath);
1361
1367
  }
1368
+ formatCaptureContextLine(context) {
1369
+ if (!context) {
1370
+ return void 0;
1371
+ }
1372
+ const cursor = context.cursor ? `Cursor: ${Math.round(context.cursor.x)}, ${Math.round(context.cursor.y)}` : void 0;
1373
+ const app = context.activeWindow?.appName || context.activeWindow?.sourceName;
1374
+ const focus = context.focusedElement?.textPreview || context.focusedElement?.label || context.focusedElement?.role;
1375
+ const parts = [cursor, app ? `App: ${app}` : void 0, focus ? `Focus: ${focus}` : void 0].filter((part) => Boolean(part));
1376
+ return parts.length ? parts.join(" | ") : void 0;
1377
+ }
1362
1378
  /**
1363
1379
  * Format a timestamp in seconds to M:SS format for post-process output.
1364
1380
  * Examples: 0 -> "0:00", 15.3 -> "0:15", 125 -> "2:05"
@@ -2338,6 +2354,15 @@ var markdownTemplate = {
2338
2354
  md += `![Frame at ${frameTimestamp}](${relativePath})
2339
2355
 
2340
2356
  `;
2357
+ const cursor = frame.captureContext?.cursor ? `Cursor: ${Math.round(frame.captureContext.cursor.x)}, ${Math.round(frame.captureContext.cursor.y)}` : void 0;
2358
+ const app = frame.captureContext?.activeWindow?.appName || frame.captureContext?.activeWindow?.sourceName;
2359
+ const focus = frame.captureContext?.focusedElement?.textPreview || frame.captureContext?.focusedElement?.label || frame.captureContext?.focusedElement?.role;
2360
+ const contextLine = [cursor, app ? `App: ${app}` : void 0, focus ? `Focus: ${focus}` : void 0].filter(Boolean).join(" | ");
2361
+ if (contextLine) {
2362
+ md += `> ${contextLine}
2363
+
2364
+ `;
2365
+ }
2341
2366
  }
2342
2367
  }
2343
2368
  }
@@ -2377,7 +2402,8 @@ var jsonTemplate = {
2377
2402
  frames: frames.map((f) => ({
2378
2403
  path: computeRelativeFramePath(f.path, sessionDir),
2379
2404
  timestamp: f.timestamp,
2380
- reason: f.reason
2405
+ reason: f.reason,
2406
+ captureContext: f.captureContext
2381
2407
  }))
2382
2408
  };
2383
2409
  })
@@ -2668,8 +2694,15 @@ var CLIPipeline = class _CLIPipeline {
2668
2694
  const result = {
2669
2695
  transcriptSegments: segments,
2670
2696
  extractedFrames,
2671
- reportPath: this.options.outputDir
2697
+ reportPath: this.options.outputDir,
2698
+ captureContexts: this.normalizeCaptureContexts(this.options.captureContexts || [])
2672
2699
  };
2700
+ if (result.captureContexts && result.captureContexts.length > 0 && result.extractedFrames.length > 0) {
2701
+ result.extractedFrames = this.attachCaptureContextsToFrames(
2702
+ result.extractedFrames,
2703
+ result.captureContexts
2704
+ );
2705
+ }
2673
2706
  let reportContent;
2674
2707
  let reportExtension = ".md";
2675
2708
  const templateName = this.options.template;
@@ -2755,6 +2788,38 @@ var CLIPipeline = class _CLIPipeline {
2755
2788
  this.activeProcesses.add(child);
2756
2789
  });
2757
2790
  }
2791
+ normalizeCaptureContexts(contexts) {
2792
+ return contexts.filter((context) => Number.isFinite(context.recordedAt)).slice().sort((a, b) => a.recordedAt - b.recordedAt);
2793
+ }
2794
+ attachCaptureContextsToFrames(frames, contexts) {
2795
+ const earliestContext = contexts[0]?.recordedAt;
2796
+ if (!Number.isFinite(earliestContext)) {
2797
+ return frames;
2798
+ }
2799
+ const maxDistanceMs = 5e3;
2800
+ return frames.map((frame) => {
2801
+ const frameAtMs = Number(earliestContext) + Math.round(frame.timestamp * 1e3);
2802
+ let bestMatch;
2803
+ let bestDistance = Number.POSITIVE_INFINITY;
2804
+ for (const context of contexts) {
2805
+ const distance = Math.abs(frameAtMs - context.recordedAt);
2806
+ if (distance < bestDistance) {
2807
+ bestDistance = distance;
2808
+ bestMatch = context;
2809
+ }
2810
+ if (context.recordedAt > frameAtMs && distance > bestDistance) {
2811
+ break;
2812
+ }
2813
+ }
2814
+ if (!bestMatch || bestDistance > maxDistanceMs) {
2815
+ return frame;
2816
+ }
2817
+ return {
2818
+ ...frame,
2819
+ captureContext: bestMatch
2820
+ };
2821
+ });
2822
+ }
2758
2823
  /**
2759
2824
  * Validate the video file is a real, non-empty file with a video stream.
2760
2825
  */
@@ -3558,7 +3623,7 @@ async function runInit(options) {
3558
3623
  }
3559
3624
 
3560
3625
  // src/cli/index.ts
3561
- var VERSION = true ? "2.6.4" : "0.0.0-dev";
3626
+ var VERSION = true ? "2.6.5" : "0.0.0-dev";
3562
3627
  var SYMBOLS = {
3563
3628
  check: "\u2714",
3564
3629
  // checkmark
@@ -3696,18 +3696,44 @@ class SessionController {
3696
3696
  this.emitStatus();
3697
3697
  return true;
3698
3698
  }
3699
- registerCaptureCue(trigger = "manual") {
3699
+ registerCaptureCue(trigger = "manual", context) {
3700
3700
  if (this.state !== "recording" || this.isPaused || !this.session) {
3701
3701
  return null;
3702
3702
  }
3703
3703
  this.captureCount += 1;
3704
+ const timestamp = Date.now();
3705
+ const sourceType = this.session.sourceId.startsWith("window") ? "window" : "screen";
3706
+ const mergedContext = {
3707
+ recordedAt: timestamp,
3708
+ trigger,
3709
+ activeWindow: {
3710
+ sourceId: this.session.sourceId,
3711
+ sourceName: this.session.metadata?.sourceName,
3712
+ sourceType
3713
+ }
3714
+ };
3715
+ if (context) {
3716
+ Object.assign(mergedContext, context);
3717
+ mergedContext.trigger = context.trigger || trigger;
3718
+ mergedContext.recordedAt = context.recordedAt ?? timestamp;
3719
+ mergedContext.activeWindow = {
3720
+ sourceId: this.session.sourceId,
3721
+ sourceName: this.session.metadata?.sourceName,
3722
+ sourceType,
3723
+ ...context.activeWindow || {}
3724
+ };
3725
+ }
3726
+ const existingContexts = this.session.metadata.captureContexts || [];
3727
+ this.session.metadata.captureContexts = [...existingContexts, mergedContext].slice(-400);
3704
3728
  const payload = {
3705
3729
  id: randomUUID(),
3706
- timestamp: Date.now(),
3730
+ timestamp,
3707
3731
  count: this.captureCount,
3708
- trigger
3732
+ trigger,
3733
+ context: mergedContext
3709
3734
  };
3710
3735
  this.emitToRenderer(IPC_CHANNELS.SCREENSHOT_CAPTURED, payload);
3736
+ this.persistSession();
3711
3737
  this.emitStatus();
3712
3738
  return payload;
3713
3739
  }
@@ -5190,7 +5216,8 @@ class FileManager {
5190
5216
  environment: {
5191
5217
  os: process.platform,
5192
5218
  version: app.getVersion()
5193
- }
5219
+ },
5220
+ captureContexts: session.metadata?.captureContexts?.slice(-400)
5194
5221
  };
5195
5222
  await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
5196
5223
  }
@@ -5441,6 +5468,12 @@ ${REPORT_SUPPORT_LINE}
5441
5468
  md += `![Frame at ${frameTimestamp}](${relativePath})
5442
5469
 
5443
5470
  `;
5471
+ const contextLine = this.formatCaptureContextLine(frame.captureContext);
5472
+ if (contextLine) {
5473
+ md += `> ${contextLine}
5474
+
5475
+ `;
5476
+ }
5444
5477
  }
5445
5478
  }
5446
5479
  }
@@ -5492,6 +5525,16 @@ ${REPORT_SUPPORT_LINE}
5492
5525
  }
5493
5526
  return path.relative(sessionDir, framePath);
5494
5527
  }
5528
+ formatCaptureContextLine(context) {
5529
+ if (!context) {
5530
+ return void 0;
5531
+ }
5532
+ const cursor = context.cursor ? `Cursor: ${Math.round(context.cursor.x)}, ${Math.round(context.cursor.y)}` : void 0;
5533
+ const app2 = context.activeWindow?.appName || context.activeWindow?.sourceName;
5534
+ const focus = context.focusedElement?.textPreview || context.focusedElement?.label || context.focusedElement?.role;
5535
+ const parts = [cursor, app2 ? `App: ${app2}` : void 0, focus ? `Focus: ${focus}` : void 0].filter((part) => Boolean(part));
5536
+ return parts.length ? parts.join(" | ") : void 0;
5537
+ }
5495
5538
  /**
5496
5539
  * Format a timestamp in seconds to M:SS format for post-process output.
5497
5540
  * Examples: 0 -> "0:00", 15.3 -> "0:15", 125 -> "2:05"
@@ -6178,7 +6221,8 @@ function adaptSessionForMarkdown(session) {
6178
6221
  metadata: {
6179
6222
  os: process.platform,
6180
6223
  sourceName: session.metadata?.sourceName || "Screen",
6181
- sourceType: session.sourceId.startsWith("screen") ? "screen" : "window"
6224
+ sourceType: session.sourceId.startsWith("screen") ? "screen" : "window",
6225
+ captureContexts: session.metadata?.captureContexts
6182
6226
  }
6183
6227
  };
6184
6228
  }
@@ -8310,7 +8354,7 @@ const FFMPEG_FAST_FRAME_TIMEOUT_MS = 1e4;
8310
8354
  const FFMPEG_CHECK_TIMEOUT_MS = 5e3;
8311
8355
  const FRAME_EDGE_MARGIN_SECONDS = 0.35;
8312
8356
  const TIMESTAMP_DEDUPE_WINDOW_SECONDS = 0.15;
8313
- const SAFE_CHILD_ENV = {
8357
+ const SAFE_CHILD_ENV$1 = {
8314
8358
  PATH: process.env.PATH,
8315
8359
  HOME: process.env.HOME || process.env.USERPROFILE,
8316
8360
  USERPROFILE: process.env.USERPROFILE,
@@ -8334,7 +8378,7 @@ class FrameExtractor {
8334
8378
  try {
8335
8379
  await execFile(this.ffmpegPath, ["-version"], {
8336
8380
  timeout: FFMPEG_CHECK_TIMEOUT_MS,
8337
- env: SAFE_CHILD_ENV
8381
+ env: SAFE_CHILD_ENV$1
8338
8382
  });
8339
8383
  this.ffmpegAvailable = true;
8340
8384
  this.log("ffmpeg is available");
@@ -8433,7 +8477,7 @@ class FrameExtractor {
8433
8477
  ];
8434
8478
  await execFile(this.ffmpegPath, args, {
8435
8479
  timeout: FFMPEG_ACCURATE_FRAME_TIMEOUT_MS,
8436
- env: SAFE_CHILD_ENV
8480
+ env: SAFE_CHILD_ENV$1
8437
8481
  });
8438
8482
  }
8439
8483
  async extractSingleFrameFast(videoPath, timestamp, outputPath) {
@@ -8454,7 +8498,7 @@ class FrameExtractor {
8454
8498
  ];
8455
8499
  await execFile(this.ffmpegPath, args, {
8456
8500
  timeout: FFMPEG_FAST_FRAME_TIMEOUT_MS,
8457
- env: SAFE_CHILD_ENV
8501
+ env: SAFE_CHILD_ENV$1
8458
8502
  });
8459
8503
  }
8460
8504
  /**
@@ -8493,7 +8537,7 @@ class FrameExtractor {
8493
8537
  "default=noprint_wrappers=1:nokey=1",
8494
8538
  videoPath
8495
8539
  ],
8496
- { timeout: FFMPEG_CHECK_TIMEOUT_MS, env: SAFE_CHILD_ENV }
8540
+ { timeout: FFMPEG_CHECK_TIMEOUT_MS, env: SAFE_CHILD_ENV$1 }
8497
8541
  );
8498
8542
  const parsed = Number.parseFloat(String(stdout).trim());
8499
8543
  if (Number.isFinite(parsed) && parsed > 0) {
@@ -10584,6 +10628,162 @@ function registerSessionHandlers(ctx, actions) {
10584
10628
  }
10585
10629
  });
10586
10630
  }
10631
+ const SAFE_CHILD_ENV = {
10632
+ PATH: process.env.PATH,
10633
+ HOME: process.env.HOME || process.env.USERPROFILE,
10634
+ USERPROFILE: process.env.USERPROFILE,
10635
+ LANG: process.env.LANG,
10636
+ TMPDIR: process.env.TMPDIR || process.env.TEMP,
10637
+ TEMP: process.env.TEMP
10638
+ };
10639
+ const MAC_CONTEXT_PROBE_JXA = `
10640
+ ObjC.import('AppKit');
10641
+ ObjC.import('CoreGraphics');
10642
+ ObjC.import('ApplicationServices');
10643
+
10644
+ function unwrap(v) {
10645
+ try { return ObjC.unwrap(v); } catch (_) { return null; }
10646
+ }
10647
+
10648
+ function readAttr(el, attr) {
10649
+ var ref = Ref();
10650
+ var err = $.AXUIElementCopyAttributeValue(el, attr, ref);
10651
+ if (err !== 0) return null;
10652
+ return unwrap(ref[0]);
10653
+ }
10654
+
10655
+ var out = {};
10656
+ var event = $.CGEventCreate(null);
10657
+ if (event) {
10658
+ var p = $.CGEventGetLocation(event);
10659
+ out.cursor = { x: Math.round(p.x), y: Math.round(p.y) };
10660
+ }
10661
+
10662
+ var front = $.NSWorkspace.sharedWorkspace.frontmostApplication;
10663
+ if (front) {
10664
+ var pid = Number(front.processIdentifier);
10665
+ out.activeWindow = {
10666
+ appName: unwrap(front.localizedName) || undefined,
10667
+ pid: pid
10668
+ };
10669
+
10670
+ var appEl = $.AXUIElementCreateApplication(pid);
10671
+ var focusedWindow = readAttr(appEl, $.kAXFocusedWindowAttribute);
10672
+ if (focusedWindow) {
10673
+ var winTitle = readAttr(focusedWindow, $.kAXTitleAttribute);
10674
+ if (winTitle) out.activeWindow.title = String(winTitle);
10675
+ }
10676
+
10677
+ var focused = readAttr(appEl, $.kAXFocusedUIElementAttribute);
10678
+ if (focused) {
10679
+ out.focusedElement = {
10680
+ role: readAttr(focused, $.kAXRoleAttribute) || undefined,
10681
+ subrole: readAttr(focused, $.kAXSubroleAttribute) || undefined,
10682
+ title: readAttr(focused, $.kAXTitleAttribute) || undefined,
10683
+ description: readAttr(focused, $.kAXDescriptionAttribute) || undefined,
10684
+ value: readAttr(focused, $.kAXValueAttribute) || undefined,
10685
+ };
10686
+ }
10687
+ }
10688
+
10689
+ JSON.stringify(out);
10690
+ `;
10691
+ function runMacContextProbe(timeoutMs) {
10692
+ return new Promise((resolve2) => {
10693
+ execFile$1(
10694
+ "osascript",
10695
+ ["-l", "JavaScript", "-e", MAC_CONTEXT_PROBE_JXA],
10696
+ { env: SAFE_CHILD_ENV, timeout: timeoutMs },
10697
+ (error, stdout) => {
10698
+ if (error) {
10699
+ resolve2(null);
10700
+ return;
10701
+ }
10702
+ try {
10703
+ const parsed = JSON.parse(stdout.toString().trim());
10704
+ resolve2(parsed);
10705
+ } catch {
10706
+ resolve2(null);
10707
+ }
10708
+ }
10709
+ );
10710
+ });
10711
+ }
10712
+ function sanitizeText(value, maxLength = 140) {
10713
+ if (!value) return void 0;
10714
+ const normalized = value.replace(/\s+/g, " ").trim();
10715
+ if (!normalized) return void 0;
10716
+ return normalized.slice(0, maxLength);
10717
+ }
10718
+ function mergeFocusedHints(osHint, rendererHint) {
10719
+ if (!osHint && !rendererHint) return void 0;
10720
+ if (!osHint && rendererHint) return rendererHint;
10721
+ if (osHint && !rendererHint) return osHint;
10722
+ return {
10723
+ ...rendererHint,
10724
+ ...osHint,
10725
+ source: osHint?.source || rendererHint?.source || "unknown"
10726
+ };
10727
+ }
10728
+ async function probeCaptureContext(input) {
10729
+ const cursorPoint = screen.getCursorScreenPoint();
10730
+ const nearestDisplay = screen.getDisplayNearestPoint(cursorPoint);
10731
+ const sourceType = input.sourceId?.startsWith("window") ? "window" : "screen";
10732
+ const baseContext = {
10733
+ recordedAt: Date.now(),
10734
+ trigger: input.trigger,
10735
+ cursor: {
10736
+ x: cursorPoint.x,
10737
+ y: cursorPoint.y,
10738
+ displayId: String(nearestDisplay.id),
10739
+ displayLabel: nearestDisplay.label || void 0,
10740
+ relativeX: cursorPoint.x - nearestDisplay.bounds.x,
10741
+ relativeY: cursorPoint.y - nearestDisplay.bounds.y
10742
+ },
10743
+ activeWindow: {
10744
+ sourceId: input.sourceId,
10745
+ sourceName: input.sourceName,
10746
+ sourceType
10747
+ }
10748
+ };
10749
+ if (process.platform !== "darwin") {
10750
+ baseContext.focusedElement = mergeFocusedHints(void 0, input.focusedElementHint);
10751
+ return baseContext;
10752
+ }
10753
+ const macProbe = await runMacContextProbe(550);
10754
+ if (macProbe?.cursor) {
10755
+ baseContext.cursor = {
10756
+ ...baseContext.cursor,
10757
+ x: Number.isFinite(macProbe.cursor.x) ? Number(macProbe.cursor.x) : baseContext.cursor?.x ?? 0,
10758
+ y: Number.isFinite(macProbe.cursor.y) ? Number(macProbe.cursor.y) : baseContext.cursor?.y ?? 0
10759
+ };
10760
+ }
10761
+ if (macProbe?.activeWindow) {
10762
+ baseContext.activeWindow = {
10763
+ ...baseContext.activeWindow,
10764
+ appName: sanitizeText(macProbe.activeWindow.appName, 120),
10765
+ title: sanitizeText(macProbe.activeWindow.title, 160),
10766
+ pid: Number.isFinite(macProbe.activeWindow.pid) ? Number(macProbe.activeWindow.pid) : void 0
10767
+ };
10768
+ }
10769
+ const osFocusedHint = macProbe?.focusedElement ? {
10770
+ source: "os-accessibility",
10771
+ role: sanitizeText(macProbe.focusedElement.role, 80),
10772
+ textPreview: sanitizeText(macProbe.focusedElement.value, 120) || sanitizeText(macProbe.focusedElement.title, 120) || sanitizeText(macProbe.focusedElement.description, 120),
10773
+ appName: baseContext.activeWindow?.appName,
10774
+ windowTitle: baseContext.activeWindow?.title
10775
+ } : void 0;
10776
+ baseContext.focusedElement = mergeFocusedHints(osFocusedHint, input.focusedElementHint);
10777
+ if (!baseContext.focusedElement && (baseContext.activeWindow?.title || baseContext.activeWindow?.appName)) {
10778
+ baseContext.focusedElement = {
10779
+ source: "window-title",
10780
+ textPreview: baseContext.activeWindow?.title || baseContext.activeWindow?.appName,
10781
+ appName: baseContext.activeWindow?.appName,
10782
+ windowTitle: baseContext.activeWindow?.title
10783
+ };
10784
+ }
10785
+ return baseContext;
10786
+ }
10587
10787
  const activeScreenRecordings = /* @__PURE__ */ new Map();
10588
10788
  const finalizedScreenRecordings = /* @__PURE__ */ new Map();
10589
10789
  function sleep$1(ms) {
@@ -10673,8 +10873,15 @@ function registerCaptureHandlers(ctx) {
10673
10873
  return [];
10674
10874
  }
10675
10875
  });
10676
- ipcMain.handle(IPC_CHANNELS.CAPTURE_MANUAL_SCREENSHOT, async () => {
10677
- const cue = sessionController.registerCaptureCue("manual");
10876
+ ipcMain.handle(IPC_CHANNELS.CAPTURE_MANUAL_SCREENSHOT, async (_, payload) => {
10877
+ const session = sessionController.getSession();
10878
+ const captureContext = await probeCaptureContext({
10879
+ trigger: "manual",
10880
+ sourceId: session?.sourceId,
10881
+ sourceName: session?.metadata?.sourceName,
10882
+ focusedElementHint: payload?.focusedElementHint
10883
+ });
10884
+ const cue = sessionController.registerCaptureCue("manual", captureContext);
10678
10885
  if (!cue) {
10679
10886
  return { success: false, error: "Manual capture is only available while recording and not paused." };
10680
10887
  }
@@ -11903,9 +12110,15 @@ async function appendExtractedFramesToReport(markdownPath, extractedFrames) {
11903
12110
  const filename = basename(frame.path) || `frame-${String(index + 1).padStart(3, "0")}.png`;
11904
12111
  const timestamp = formatSecondsAsTimestamp(frame.timestamp);
11905
12112
  const reason = frame.reason?.trim() || "Auto-extracted frame";
12113
+ const cursor = frame.captureContext?.cursor ? `Cursor: ${Math.round(frame.captureContext.cursor.x)}, ${Math.round(frame.captureContext.cursor.y)}` : void 0;
12114
+ const app2 = frame.captureContext?.activeWindow?.appName || frame.captureContext?.activeWindow?.sourceName;
12115
+ const focused = frame.captureContext?.focusedElement?.textPreview || frame.captureContext?.focusedElement?.label || frame.captureContext?.focusedElement?.role;
12116
+ const contextLine = [cursor, app2 ? `App: ${app2}` : void 0, focused ? `Focus: ${focused}` : void 0].filter(Boolean).join(" | ");
11906
12117
  return `### [${timestamp}] ${reason}
11907
12118
 
11908
- ![${reason}](./screenshots/${filename})`;
12119
+ ![${reason}](./screenshots/${filename})${contextLine ? `
12120
+
12121
+ > ${contextLine}` : ""}`;
11909
12122
  }).join("\n\n");
11910
12123
  markdown += `
11911
12124
  ## Auto-Extracted Screenshots
@@ -12377,7 +12590,13 @@ async function handlePauseResume() {
12377
12590
  pauseSession();
12378
12591
  }
12379
12592
  async function handleManualScreenshot() {
12380
- const cue = sessionController.registerCaptureCue("manual");
12593
+ const session = sessionController.getSession();
12594
+ const captureContext = await probeCaptureContext({
12595
+ trigger: "manual",
12596
+ sourceId: session?.sourceId,
12597
+ sourceName: session?.metadata?.sourceName
12598
+ });
12599
+ const cue = sessionController.registerCaptureCue("manual", captureContext);
12381
12600
  if (!cue) {
12382
12601
  return;
12383
12602
  }
@@ -12443,6 +12662,38 @@ function buildPostProcessTranscriptSegments(session) {
12443
12662
  };
12444
12663
  });
12445
12664
  }
12665
+ function getSessionCaptureContexts(session) {
12666
+ const contexts = session.metadata?.captureContexts || [];
12667
+ return contexts.filter((context) => Number.isFinite(context.recordedAt)).slice().sort((a, b) => a.recordedAt - b.recordedAt);
12668
+ }
12669
+ function attachCaptureContextsToExtractedFrames(session, extractedFrames, captureContexts) {
12670
+ if (!captureContexts.length || !extractedFrames.length) {
12671
+ return extractedFrames;
12672
+ }
12673
+ const maxDistanceMs = 5e3;
12674
+ return extractedFrames.map((frame) => {
12675
+ const frameAtMs = session.startTime + Math.round(frame.timestamp * 1e3);
12676
+ let bestMatch;
12677
+ let bestDistance = Number.POSITIVE_INFINITY;
12678
+ for (const context of captureContexts) {
12679
+ const distance = Math.abs(frameAtMs - context.recordedAt);
12680
+ if (distance < bestDistance) {
12681
+ bestDistance = distance;
12682
+ bestMatch = context;
12683
+ }
12684
+ if (context.recordedAt > frameAtMs && distance > bestDistance) {
12685
+ break;
12686
+ }
12687
+ }
12688
+ if (!bestMatch || bestDistance > maxDistanceMs) {
12689
+ return frame;
12690
+ }
12691
+ return {
12692
+ ...frame,
12693
+ captureContext: bestMatch
12694
+ };
12695
+ });
12696
+ }
12446
12697
  async function copyReportPathToClipboard(path2) {
12447
12698
  const maxAttempts = 3;
12448
12699
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -12733,6 +12984,7 @@ async function stopSession() {
12733
12984
  emitProcessingProgress(71, "preparing");
12734
12985
  let postProcessResult = null;
12735
12986
  const providedTranscriptSegments = buildPostProcessTranscriptSegments(session);
12987
+ const captureContexts = getSessionCaptureContexts(session);
12736
12988
  const aiMomentHints = extractAiFrameHintsFromMarkdown(
12737
12989
  document.content,
12738
12990
  providedTranscriptSegments
@@ -12757,6 +13009,14 @@ async function stopSession() {
12757
13009
  emitProcessingProgress(mappedPercent, progress.step);
12758
13010
  }
12759
13011
  });
13012
+ if (postProcessResult) {
13013
+ postProcessResult.captureContexts = captureContexts;
13014
+ postProcessResult.extractedFrames = attachCaptureContextsToExtractedFrames(
13015
+ session,
13016
+ postProcessResult.extractedFrames,
13017
+ captureContexts
13018
+ );
13019
+ }
12760
13020
  console.log(
12761
13021
  `[Main:stopSession] Step 5/6 complete: post-processing took ${Date.now() - postProcessStartedAt}ms, ${postProcessResult?.transcriptSegments.length ?? 0} segments, ${postProcessResult?.extractedFrames.length ?? 0} frames extracted`
12762
13022
  );
@@ -246,6 +246,141 @@ var SessionStore = class {
246
246
  };
247
247
  var sessionStore = new SessionStore();
248
248
 
249
+ // src/mcp/utils/CaptureContext.ts
250
+ import { execFile as execFileCb2 } from "child_process";
251
+ var SAFE_CHILD_ENV2 = {
252
+ PATH: process.env.PATH,
253
+ HOME: process.env.HOME || process.env.USERPROFILE,
254
+ USERPROFILE: process.env.USERPROFILE,
255
+ LANG: process.env.LANG,
256
+ TMPDIR: process.env.TMPDIR || process.env.TEMP,
257
+ TEMP: process.env.TEMP
258
+ };
259
+ var MAC_CONTEXT_PROBE_JXA = `
260
+ ObjC.import('AppKit');
261
+ ObjC.import('CoreGraphics');
262
+ ObjC.import('ApplicationServices');
263
+
264
+ function unwrap(v) {
265
+ try { return ObjC.unwrap(v); } catch (_) { return null; }
266
+ }
267
+
268
+ function readAttr(el, attr) {
269
+ var ref = Ref();
270
+ var err = $.AXUIElementCopyAttributeValue(el, attr, ref);
271
+ if (err !== 0) return null;
272
+ return unwrap(ref[0]);
273
+ }
274
+
275
+ var out = {};
276
+ var event = $.CGEventCreate(null);
277
+ if (event) {
278
+ var p = $.CGEventGetLocation(event);
279
+ out.cursor = { x: Math.round(p.x), y: Math.round(p.y) };
280
+ }
281
+
282
+ var front = $.NSWorkspace.sharedWorkspace.frontmostApplication;
283
+ if (front) {
284
+ var pid = Number(front.processIdentifier);
285
+ out.activeWindow = {
286
+ appName: unwrap(front.localizedName) || undefined,
287
+ pid: pid
288
+ };
289
+
290
+ var appEl = $.AXUIElementCreateApplication(pid);
291
+ var focusedWindow = readAttr(appEl, $.kAXFocusedWindowAttribute);
292
+ if (focusedWindow) {
293
+ var winTitle = readAttr(focusedWindow, $.kAXTitleAttribute);
294
+ if (winTitle) out.activeWindow.title = String(winTitle);
295
+ }
296
+
297
+ var focused = readAttr(appEl, $.kAXFocusedUIElementAttribute);
298
+ if (focused) {
299
+ out.focusedElement = {
300
+ role: readAttr(focused, $.kAXRoleAttribute) || undefined,
301
+ title: readAttr(focused, $.kAXTitleAttribute) || undefined,
302
+ description: readAttr(focused, $.kAXDescriptionAttribute) || undefined,
303
+ value: readAttr(focused, $.kAXValueAttribute) || undefined,
304
+ };
305
+ }
306
+ }
307
+
308
+ JSON.stringify(out);
309
+ `;
310
+ function sanitizeText(value, maxLength = 140) {
311
+ if (!value) return void 0;
312
+ const normalized = value.replace(/\s+/g, " ").trim();
313
+ if (!normalized) return void 0;
314
+ return normalized.slice(0, maxLength);
315
+ }
316
+ function runMacContextProbe(timeoutMs) {
317
+ return new Promise((resolve4) => {
318
+ execFileCb2(
319
+ "osascript",
320
+ ["-l", "JavaScript", "-e", MAC_CONTEXT_PROBE_JXA],
321
+ { env: SAFE_CHILD_ENV2, timeout: timeoutMs },
322
+ (error, stdout) => {
323
+ if (error) {
324
+ resolve4(null);
325
+ return;
326
+ }
327
+ try {
328
+ const parsed = JSON.parse(stdout.toString().trim());
329
+ resolve4(parsed);
330
+ } catch {
331
+ resolve4(null);
332
+ }
333
+ }
334
+ );
335
+ });
336
+ }
337
+ async function captureContextSnapshot() {
338
+ const snapshot = {
339
+ recordedAt: Date.now()
340
+ };
341
+ if (process.platform !== "darwin") {
342
+ return snapshot;
343
+ }
344
+ const context = await runMacContextProbe(550);
345
+ if (!context) {
346
+ return snapshot;
347
+ }
348
+ if (context.cursor && Number.isFinite(context.cursor.x) && Number.isFinite(context.cursor.y)) {
349
+ snapshot.cursor = {
350
+ x: Number(context.cursor.x),
351
+ y: Number(context.cursor.y)
352
+ };
353
+ }
354
+ if (context.activeWindow) {
355
+ snapshot.activeWindow = {
356
+ appName: sanitizeText(context.activeWindow.appName, 120),
357
+ title: sanitizeText(context.activeWindow.title, 160),
358
+ pid: Number.isFinite(context.activeWindow.pid) ? Number(context.activeWindow.pid) : void 0
359
+ };
360
+ }
361
+ if (context.focusedElement) {
362
+ const textPreview = sanitizeText(context.focusedElement.value, 120) || sanitizeText(context.focusedElement.title, 120) || sanitizeText(context.focusedElement.description, 120);
363
+ if (textPreview || context.focusedElement.role) {
364
+ snapshot.focusedElement = {
365
+ source: "os-accessibility",
366
+ role: sanitizeText(context.focusedElement.role, 80),
367
+ textPreview,
368
+ appName: snapshot.activeWindow?.appName,
369
+ windowTitle: snapshot.activeWindow?.title
370
+ };
371
+ }
372
+ }
373
+ if (!snapshot.focusedElement && (snapshot.activeWindow?.title || snapshot.activeWindow?.appName)) {
374
+ snapshot.focusedElement = {
375
+ source: "window-title",
376
+ textPreview: snapshot.activeWindow?.title || snapshot.activeWindow?.appName,
377
+ appName: snapshot.activeWindow?.appName,
378
+ windowTitle: snapshot.activeWindow?.title
379
+ };
380
+ }
381
+ return snapshot;
382
+ }
383
+
249
384
  // src/mcp/tools/captureScreenshot.ts
250
385
  function register(server) {
251
386
  server.tool(
@@ -265,18 +400,40 @@ function register(server) {
265
400
  const index = existing.filter((f) => f.startsWith("screenshot-")).length + 1;
266
401
  const filename = `screenshot-${String(index).padStart(3, "0")}.png`;
267
402
  const outputPath = join2(screenshotsDir, filename);
403
+ const context = await captureContextSnapshot();
268
404
  log(`Capturing screenshot: display=${display}, label=${label ?? "none"}`);
269
405
  await capture({ display, outputPath });
270
406
  if (shouldOptimize) {
271
407
  await optimize(outputPath);
272
408
  }
409
+ const latestMetadata = await sessionStore.get(session.id);
410
+ const existingCaptures = latestMetadata?.captures ?? [];
411
+ await sessionStore.update(session.id, {
412
+ lastCaptureContext: context,
413
+ captures: [
414
+ ...existingCaptures,
415
+ {
416
+ file: filename,
417
+ label,
418
+ display,
419
+ capturedAt: context.recordedAt,
420
+ context
421
+ }
422
+ ].slice(-250)
423
+ });
273
424
  const markdownRef = `![${label ?? filename}](screenshots/${filename})`;
425
+ const contextSummary = [
426
+ context.cursor ? `Cursor: ${Math.round(context.cursor.x)}, ${Math.round(context.cursor.y)}` : "",
427
+ context.activeWindow?.appName ? `App: ${context.activeWindow.appName}` : "",
428
+ context.focusedElement?.textPreview ? `Focus: ${context.focusedElement.textPreview}` : ""
429
+ ].filter(Boolean).join(" | ");
274
430
  return {
275
431
  content: [
276
432
  {
277
433
  type: "text",
278
434
  text: `Screenshot saved: ${outputPath}
279
- ${markdownRef}`
435
+ ${markdownRef}${contextSummary ? `
436
+ Context: ${contextSummary}` : ""}`
280
437
  }
281
438
  ]
282
439
  };
@@ -295,10 +452,10 @@ import { z as z2 } from "zod";
295
452
  import { join as join6 } from "path";
296
453
 
297
454
  // src/mcp/capture/ScreenRecorder.ts
298
- import { execFile as execFileCb2 } from "child_process";
455
+ import { execFile as execFileCb3 } from "child_process";
299
456
  import { stat as stat2 } from "fs/promises";
300
457
  import { resolve as resolve3 } from "path";
301
- var SAFE_CHILD_ENV2 = {
458
+ var SAFE_CHILD_ENV3 = {
302
459
  PATH: process.env.PATH,
303
460
  HOME: process.env.HOME || process.env.USERPROFILE,
304
461
  USERPROFILE: process.env.USERPROFILE,
@@ -338,10 +495,10 @@ async function record(options) {
338
495
  const args = buildFfmpegArgs(outputPath, videoDevice, audioDevice, duration);
339
496
  log(`Recording screen+audio: duration=${duration}s, output=${outputPath}`);
340
497
  await new Promise((resolve4, reject) => {
341
- execFileCb2(
498
+ execFileCb3(
342
499
  "ffmpeg",
343
500
  args,
344
- { env: SAFE_CHILD_ENV2, timeout: (duration + 30) * 1e3 },
501
+ { env: SAFE_CHILD_ENV3, timeout: (duration + 30) * 1e3 },
345
502
  (error) => {
346
503
  if (error) {
347
504
  reject(
@@ -366,10 +523,10 @@ function start(options) {
366
523
  const audioDevice = options.audioDevice ?? "default";
367
524
  const args = buildFfmpegArgs(outputPath, videoDevice, audioDevice);
368
525
  log(`Starting long-form recording: output=${outputPath}`);
369
- const child = execFileCb2(
526
+ const child = execFileCb3(
370
527
  "ffmpeg",
371
528
  args,
372
- { env: SAFE_CHILD_ENV2 },
529
+ { env: SAFE_CHILD_ENV3 },
373
530
  (error) => {
374
531
  if (error && !error.killed) {
375
532
  log(`Recording process exited with error: ${error.message}`);
@@ -422,7 +579,7 @@ Check Screen Recording and Microphone permissions in System Settings \u2192 Priv
422
579
  import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
423
580
  import { stat as stat3, unlink as unlink2, writeFile as writeFile2, chmod as chmod2 } from "fs/promises";
424
581
  import { join as join5, basename as basename3 } from "path";
425
- import { execFile as execFileCb4 } from "child_process";
582
+ import { execFile as execFileCb5 } from "child_process";
426
583
  import { tmpdir as tmpdir2 } from "os";
427
584
  import { randomUUID as randomUUID2 } from "crypto";
428
585
 
@@ -564,19 +721,19 @@ var TranscriptAnalyzer = class {
564
721
  var transcriptAnalyzer = new TranscriptAnalyzer();
565
722
 
566
723
  // src/main/pipeline/FrameExtractor.ts
567
- import { execFile as execFileCb3 } from "child_process";
724
+ import { execFile as execFileCb4 } from "child_process";
568
725
  import { promisify } from "util";
569
726
  import { existsSync, mkdirSync } from "fs";
570
727
  import { stat as statFile } from "fs/promises";
571
728
  import { join as join3 } from "path";
572
- var execFile = promisify(execFileCb3);
729
+ var execFile = promisify(execFileCb4);
573
730
  var DEFAULT_MAX_FRAMES = 20;
574
731
  var FFMPEG_ACCURATE_FRAME_TIMEOUT_MS = 2e4;
575
732
  var FFMPEG_FAST_FRAME_TIMEOUT_MS = 1e4;
576
733
  var FFMPEG_CHECK_TIMEOUT_MS = 5e3;
577
734
  var FRAME_EDGE_MARGIN_SECONDS2 = 0.35;
578
735
  var TIMESTAMP_DEDUPE_WINDOW_SECONDS = 0.15;
579
- var SAFE_CHILD_ENV3 = {
736
+ var SAFE_CHILD_ENV4 = {
580
737
  PATH: process.env.PATH,
581
738
  HOME: process.env.HOME || process.env.USERPROFILE,
582
739
  USERPROFILE: process.env.USERPROFILE,
@@ -600,7 +757,7 @@ var FrameExtractor = class {
600
757
  try {
601
758
  await execFile(this.ffmpegPath, ["-version"], {
602
759
  timeout: FFMPEG_CHECK_TIMEOUT_MS,
603
- env: SAFE_CHILD_ENV3
760
+ env: SAFE_CHILD_ENV4
604
761
  });
605
762
  this.ffmpegAvailable = true;
606
763
  this.log("ffmpeg is available");
@@ -699,7 +856,7 @@ var FrameExtractor = class {
699
856
  ];
700
857
  await execFile(this.ffmpegPath, args, {
701
858
  timeout: FFMPEG_ACCURATE_FRAME_TIMEOUT_MS,
702
- env: SAFE_CHILD_ENV3
859
+ env: SAFE_CHILD_ENV4
703
860
  });
704
861
  }
705
862
  async extractSingleFrameFast(videoPath, timestamp, outputPath) {
@@ -720,7 +877,7 @@ var FrameExtractor = class {
720
877
  ];
721
878
  await execFile(this.ffmpegPath, args, {
722
879
  timeout: FFMPEG_FAST_FRAME_TIMEOUT_MS,
723
- env: SAFE_CHILD_ENV3
880
+ env: SAFE_CHILD_ENV4
724
881
  });
725
882
  }
726
883
  /**
@@ -759,7 +916,7 @@ var FrameExtractor = class {
759
916
  "default=noprint_wrappers=1:nokey=1",
760
917
  videoPath
761
918
  ],
762
- { timeout: FFMPEG_CHECK_TIMEOUT_MS, env: SAFE_CHILD_ENV3 }
919
+ { timeout: FFMPEG_CHECK_TIMEOUT_MS, env: SAFE_CHILD_ENV4 }
763
920
  );
764
921
  const parsed = Number.parseFloat(String(stdout).trim());
765
922
  if (Number.isFinite(parsed) && parsed > 0) {
@@ -1005,6 +1162,12 @@ ${REPORT_SUPPORT_LINE}
1005
1162
  md += `![Frame at ${frameTimestamp}](${relativePath})
1006
1163
 
1007
1164
  `;
1165
+ const contextLine = this.formatCaptureContextLine(frame.captureContext);
1166
+ if (contextLine) {
1167
+ md += `> ${contextLine}
1168
+
1169
+ `;
1170
+ }
1008
1171
  }
1009
1172
  }
1010
1173
  }
@@ -1056,6 +1219,16 @@ ${REPORT_SUPPORT_LINE}
1056
1219
  }
1057
1220
  return path2.relative(sessionDir, framePath);
1058
1221
  }
1222
+ formatCaptureContextLine(context) {
1223
+ if (!context) {
1224
+ return void 0;
1225
+ }
1226
+ const cursor = context.cursor ? `Cursor: ${Math.round(context.cursor.x)}, ${Math.round(context.cursor.y)}` : void 0;
1227
+ const app = context.activeWindow?.appName || context.activeWindow?.sourceName;
1228
+ const focus = context.focusedElement?.textPreview || context.focusedElement?.label || context.focusedElement?.role;
1229
+ const parts = [cursor, app ? `App: ${app}` : void 0, focus ? `Focus: ${focus}` : void 0].filter((part) => Boolean(part));
1230
+ return parts.length ? parts.join(" | ") : void 0;
1231
+ }
1059
1232
  /**
1060
1233
  * Format a timestamp in seconds to M:SS format for post-process output.
1061
1234
  * Examples: 0 -> "0:00", 15.3 -> "0:15", 125 -> "2:05"
@@ -2035,6 +2208,15 @@ var markdownTemplate = {
2035
2208
  md += `![Frame at ${frameTimestamp}](${relativePath})
2036
2209
 
2037
2210
  `;
2211
+ const cursor = frame.captureContext?.cursor ? `Cursor: ${Math.round(frame.captureContext.cursor.x)}, ${Math.round(frame.captureContext.cursor.y)}` : void 0;
2212
+ const app = frame.captureContext?.activeWindow?.appName || frame.captureContext?.activeWindow?.sourceName;
2213
+ const focus = frame.captureContext?.focusedElement?.textPreview || frame.captureContext?.focusedElement?.label || frame.captureContext?.focusedElement?.role;
2214
+ const contextLine = [cursor, app ? `App: ${app}` : void 0, focus ? `Focus: ${focus}` : void 0].filter(Boolean).join(" | ");
2215
+ if (contextLine) {
2216
+ md += `> ${contextLine}
2217
+
2218
+ `;
2219
+ }
2038
2220
  }
2039
2221
  }
2040
2222
  }
@@ -2074,7 +2256,8 @@ var jsonTemplate = {
2074
2256
  frames: frames.map((f) => ({
2075
2257
  path: computeRelativeFramePath(f.path, sessionDir),
2076
2258
  timestamp: f.timestamp,
2077
- reason: f.reason
2259
+ reason: f.reason,
2260
+ captureContext: f.captureContext
2078
2261
  }))
2079
2262
  };
2080
2263
  })
@@ -2361,8 +2544,15 @@ var CLIPipeline = class _CLIPipeline {
2361
2544
  const result = {
2362
2545
  transcriptSegments: segments,
2363
2546
  extractedFrames,
2364
- reportPath: this.options.outputDir
2547
+ reportPath: this.options.outputDir,
2548
+ captureContexts: this.normalizeCaptureContexts(this.options.captureContexts || [])
2365
2549
  };
2550
+ if (result.captureContexts && result.captureContexts.length > 0 && result.extractedFrames.length > 0) {
2551
+ result.extractedFrames = this.attachCaptureContextsToFrames(
2552
+ result.extractedFrames,
2553
+ result.captureContexts
2554
+ );
2555
+ }
2366
2556
  let reportContent;
2367
2557
  let reportExtension = ".md";
2368
2558
  const templateName = this.options.template;
@@ -2440,7 +2630,7 @@ var CLIPipeline = class _CLIPipeline {
2440
2630
  };
2441
2631
  execFileTracked(command, args) {
2442
2632
  return new Promise((resolve4, reject) => {
2443
- const child = execFileCb4(command, args, { env: _CLIPipeline.SAFE_CHILD_ENV }, (error, stdout, stderr) => {
2633
+ const child = execFileCb5(command, args, { env: _CLIPipeline.SAFE_CHILD_ENV }, (error, stdout, stderr) => {
2444
2634
  this.activeProcesses.delete(child);
2445
2635
  if (error) reject(error);
2446
2636
  else resolve4({ stdout: stdout?.toString() ?? "", stderr: stderr?.toString() ?? "" });
@@ -2448,6 +2638,38 @@ var CLIPipeline = class _CLIPipeline {
2448
2638
  this.activeProcesses.add(child);
2449
2639
  });
2450
2640
  }
2641
+ normalizeCaptureContexts(contexts) {
2642
+ return contexts.filter((context) => Number.isFinite(context.recordedAt)).slice().sort((a, b) => a.recordedAt - b.recordedAt);
2643
+ }
2644
+ attachCaptureContextsToFrames(frames, contexts) {
2645
+ const earliestContext = contexts[0]?.recordedAt;
2646
+ if (!Number.isFinite(earliestContext)) {
2647
+ return frames;
2648
+ }
2649
+ const maxDistanceMs = 5e3;
2650
+ return frames.map((frame) => {
2651
+ const frameAtMs = Number(earliestContext) + Math.round(frame.timestamp * 1e3);
2652
+ let bestMatch;
2653
+ let bestDistance = Number.POSITIVE_INFINITY;
2654
+ for (const context of contexts) {
2655
+ const distance = Math.abs(frameAtMs - context.recordedAt);
2656
+ if (distance < bestDistance) {
2657
+ bestDistance = distance;
2658
+ bestMatch = context;
2659
+ }
2660
+ if (context.recordedAt > frameAtMs && distance > bestDistance) {
2661
+ break;
2662
+ }
2663
+ }
2664
+ if (!bestMatch || bestDistance > maxDistanceMs) {
2665
+ return frame;
2666
+ }
2667
+ return {
2668
+ ...frame,
2669
+ captureContext: bestMatch
2670
+ };
2671
+ });
2672
+ }
2451
2673
  /**
2452
2674
  * Validate the video file is a real, non-empty file with a video stream.
2453
2675
  */
@@ -2709,6 +2931,29 @@ var CLIPipelineError = class extends Error {
2709
2931
  };
2710
2932
 
2711
2933
  // src/mcp/tools/captureWithVoice.ts
2934
+ function toSharedCaptureContext(context) {
2935
+ if (!context) {
2936
+ return void 0;
2937
+ }
2938
+ return {
2939
+ recordedAt: context.recordedAt,
2940
+ trigger: "manual",
2941
+ cursor: context.cursor,
2942
+ activeWindow: {
2943
+ appName: context.activeWindow?.appName,
2944
+ title: context.activeWindow?.title,
2945
+ pid: context.activeWindow?.pid,
2946
+ sourceType: "screen"
2947
+ },
2948
+ focusedElement: context.focusedElement ? {
2949
+ source: context.focusedElement.source,
2950
+ role: context.focusedElement.role,
2951
+ textPreview: context.focusedElement.textPreview,
2952
+ appName: context.focusedElement.appName,
2953
+ windowTitle: context.focusedElement.windowTitle
2954
+ } : void 0
2955
+ };
2956
+ }
2712
2957
  function register2(server) {
2713
2958
  server.tool(
2714
2959
  "capture_with_voice",
@@ -2724,10 +2969,26 @@ function register2(server) {
2724
2969
  async ({ duration, outputDir, skipFrames, template }) => {
2725
2970
  try {
2726
2971
  const session = await sessionStore.create();
2972
+ const startContext = await captureContextSnapshot();
2973
+ await sessionStore.update(session.id, {
2974
+ recordingContextStart: startContext,
2975
+ lastCaptureContext: startContext
2976
+ });
2727
2977
  const sessionDir = sessionStore.getSessionDir(session.id);
2728
2978
  const videoPath = join6(sessionDir, "recording.mp4");
2729
2979
  log(`Starting capture_with_voice: duration=${duration}s`);
2730
2980
  await record({ duration, outputPath: videoPath });
2981
+ const stopContext = await captureContextSnapshot();
2982
+ await sessionStore.update(session.id, {
2983
+ recordingContextStop: stopContext,
2984
+ lastCaptureContext: stopContext
2985
+ });
2986
+ const metadataBeforePipeline = await sessionStore.get(session.id);
2987
+ const captureContexts = [
2988
+ toSharedCaptureContext(metadataBeforePipeline?.recordingContextStart),
2989
+ ...(metadataBeforePipeline?.captures || []).map((capture2) => toSharedCaptureContext(capture2.context)),
2990
+ toSharedCaptureContext(stopContext)
2991
+ ].filter((context) => Boolean(context));
2731
2992
  const pipelineOutputDir = outputDir ?? sessionDir;
2732
2993
  const pipeline = new CLIPipeline(
2733
2994
  {
@@ -2735,7 +2996,8 @@ function register2(server) {
2735
2996
  outputDir: pipelineOutputDir,
2736
2997
  skipFrames,
2737
2998
  template,
2738
- verbose: false
2999
+ verbose: false,
3000
+ captureContexts
2739
3001
  },
2740
3002
  (msg) => log(msg)
2741
3003
  );
@@ -2744,7 +3006,9 @@ function register2(server) {
2744
3006
  status: "complete",
2745
3007
  endTime: Date.now(),
2746
3008
  videoPath,
2747
- reportPath: result.outputPath
3009
+ reportPath: result.outputPath,
3010
+ recordingContextStop: stopContext,
3011
+ lastCaptureContext: stopContext
2748
3012
  });
2749
3013
  return {
2750
3014
  content: [
@@ -2991,6 +3255,11 @@ function register5(server) {
2991
3255
  };
2992
3256
  }
2993
3257
  const session = await sessionStore.create(label);
3258
+ const startContext = await captureContextSnapshot();
3259
+ await sessionStore.update(session.id, {
3260
+ recordingContextStart: startContext,
3261
+ lastCaptureContext: startContext
3262
+ });
2994
3263
  const sessionDir = sessionStore.getSessionDir(session.id);
2995
3264
  const videoPath = join8(sessionDir, "recording.mp4");
2996
3265
  log(`Starting long-form recording: session=${session.id}`);
@@ -3021,6 +3290,29 @@ function register5(server) {
3021
3290
 
3022
3291
  // src/mcp/tools/stopRecording.ts
3023
3292
  import { z as z6 } from "zod";
3293
+ function toSharedCaptureContext2(context) {
3294
+ if (!context) {
3295
+ return void 0;
3296
+ }
3297
+ return {
3298
+ recordedAt: context.recordedAt,
3299
+ trigger: "manual",
3300
+ cursor: context.cursor,
3301
+ activeWindow: {
3302
+ appName: context.activeWindow?.appName,
3303
+ title: context.activeWindow?.title,
3304
+ pid: context.activeWindow?.pid,
3305
+ sourceType: "screen"
3306
+ },
3307
+ focusedElement: context.focusedElement ? {
3308
+ source: context.focusedElement.source,
3309
+ role: context.focusedElement.role,
3310
+ textPreview: context.focusedElement.textPreview,
3311
+ appName: context.focusedElement.appName,
3312
+ windowTitle: context.focusedElement.windowTitle
3313
+ } : void 0
3314
+ };
3315
+ }
3024
3316
  function register6(server) {
3025
3317
  server.tool(
3026
3318
  "stop_recording",
@@ -3050,7 +3342,14 @@ function register6(server) {
3050
3342
  log(`Stopping recording: session=${current.sessionId}`);
3051
3343
  await stop(current.process);
3052
3344
  const { sessionId, videoPath } = activeRecording.stop();
3345
+ const stopContext = await captureContextSnapshot();
3053
3346
  await sessionStore.update(sessionId, { status: "processing" });
3347
+ const metadataBeforePipeline = await sessionStore.get(sessionId);
3348
+ const captureContexts = [
3349
+ toSharedCaptureContext2(metadataBeforePipeline?.recordingContextStart),
3350
+ ...(metadataBeforePipeline?.captures || []).map((capture2) => toSharedCaptureContext2(capture2.context)),
3351
+ toSharedCaptureContext2(stopContext)
3352
+ ].filter((context) => Boolean(context));
3054
3353
  const sessionDir = sessionStore.getSessionDir(sessionId);
3055
3354
  const pipeline = new CLIPipeline(
3056
3355
  {
@@ -3058,7 +3357,8 @@ function register6(server) {
3058
3357
  outputDir: sessionDir,
3059
3358
  skipFrames,
3060
3359
  template,
3061
- verbose: false
3360
+ verbose: false,
3361
+ captureContexts
3062
3362
  },
3063
3363
  (msg) => log(msg)
3064
3364
  );
@@ -3067,7 +3367,9 @@ function register6(server) {
3067
3367
  status: "complete",
3068
3368
  endTime: Date.now(),
3069
3369
  videoPath,
3070
- reportPath: result.outputPath
3370
+ reportPath: result.outputPath,
3371
+ recordingContextStop: stopContext,
3372
+ lastCaptureContext: stopContext
3071
3373
  });
3072
3374
  return {
3073
3375
  content: [
@@ -4222,7 +4524,7 @@ function registerResources(server) {
4222
4524
  }
4223
4525
 
4224
4526
  // src/mcp/server.ts
4225
- var VERSION = true ? "2.6.4" : "0.0.0-dev";
4527
+ var VERSION = true ? "2.6.5" : "0.0.0-dev";
4226
4528
  function createServer() {
4227
4529
  const server = new McpServer2({
4228
4530
  name: "markupR",
@@ -4242,7 +4544,7 @@ function createServer() {
4242
4544
  }
4243
4545
 
4244
4546
  // src/mcp/index.ts
4245
- var VERSION2 = true ? "2.6.4" : "0.0.0-dev";
4547
+ var VERSION2 = true ? "2.6.5" : "0.0.0-dev";
4246
4548
  log(`markupR MCP server v${VERSION2} starting...`);
4247
4549
  process.on("uncaughtException", (error) => {
4248
4550
  log(`Uncaught exception: ${error instanceof Error ? error.message : String(error)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markupr",
3
- "version": "2.6.4",
3
+ "version": "2.6.5",
4
4
  "description": "Record your screen, narrate feedback, get structured Markdown with screenshots. Desktop app, CLI, and MCP server for AI coding agents like Claude Code, Cursor, and Windsurf.",
5
5
  "type": "module",
6
6
  "main": "dist/main/index.mjs",