markupr 2.6.3 → 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.
- package/README.md +21 -21
- package/dist/cli/index.mjs +104 -39
- package/dist/main/index.mjs +321 -61
- package/dist/mcp/index.mjs +357 -55
- package/package.json +1 -1
package/dist/mcp/index.mjs
CHANGED
|
@@ -24,7 +24,7 @@ import { resolve } from "path";
|
|
|
24
24
|
|
|
25
25
|
// src/mcp/utils/Logger.ts
|
|
26
26
|
function log(message) {
|
|
27
|
-
process.stderr.write(`[
|
|
27
|
+
process.stderr.write(`[markupR-mcp] ${message}
|
|
28
28
|
`);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -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 = ``;
|
|
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
|
|
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
|
|
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
|
-
|
|
498
|
+
execFileCb3(
|
|
342
499
|
"ffmpeg",
|
|
343
500
|
args,
|
|
344
|
-
{ env:
|
|
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 =
|
|
526
|
+
const child = execFileCb3(
|
|
370
527
|
"ffmpeg",
|
|
371
528
|
args,
|
|
372
|
-
{ env:
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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) {
|
|
@@ -817,13 +974,13 @@ var MarkdownGeneratorImpl = class {
|
|
|
817
974
|
const filename = this.generateFilename(projectName, session.startTime);
|
|
818
975
|
if (items.length === 0) {
|
|
819
976
|
const content2 = `# ${projectName} Feedback Report
|
|
820
|
-
> Generated by
|
|
977
|
+
> Generated by markupR on ${timestamp}
|
|
821
978
|
> Duration: ${duration}
|
|
822
979
|
|
|
823
980
|
_No feedback items were captured during this session._
|
|
824
981
|
|
|
825
982
|
---
|
|
826
|
-
*Generated by [
|
|
983
|
+
*Generated by [markupR](https://markupr.com)*
|
|
827
984
|
${REPORT_SUPPORT_LINE}
|
|
828
985
|
`;
|
|
829
986
|
return {
|
|
@@ -844,7 +1001,7 @@ ${REPORT_SUPPORT_LINE}
|
|
|
844
1001
|
const highImpactCount = (severityCounts.Critical || 0) + (severityCounts.High || 0);
|
|
845
1002
|
const platform = session.metadata?.os || process?.platform || "Unknown";
|
|
846
1003
|
let content = `# ${projectName} Feedback Report
|
|
847
|
-
> Generated by
|
|
1004
|
+
> Generated by markupR on ${timestamp}
|
|
848
1005
|
> Duration: ${duration} | Items: ${items.length} | Screenshots: ${screenshotCount}
|
|
849
1006
|
|
|
850
1007
|
## Session Overview
|
|
@@ -941,7 +1098,7 @@ _No screenshot captured for this item._
|
|
|
941
1098
|
`;
|
|
942
1099
|
content += `
|
|
943
1100
|
---
|
|
944
|
-
*Generated by [
|
|
1101
|
+
*Generated by [markupR](https://markupr.com)*
|
|
945
1102
|
${REPORT_SUPPORT_LINE}
|
|
946
1103
|
`;
|
|
947
1104
|
return {
|
|
@@ -974,7 +1131,7 @@ ${REPORT_SUPPORT_LINE}
|
|
|
974
1131
|
const sessionDuration = transcriptSegments.length > 0 ? this.formatDuration(
|
|
975
1132
|
(transcriptSegments[transcriptSegments.length - 1].endTime - transcriptSegments[0].startTime) * 1e3
|
|
976
1133
|
) : "0:00";
|
|
977
|
-
let md = `#
|
|
1134
|
+
let md = `# markupR Session \u2014 ${sessionTimestamp}
|
|
978
1135
|
`;
|
|
979
1136
|
md += `> Segments: ${transcriptSegments.length} | Frames: ${extractedFrames.length} | Duration: ${sessionDuration}
|
|
980
1137
|
|
|
@@ -1005,11 +1162,17 @@ ${REPORT_SUPPORT_LINE}
|
|
|
1005
1162
|
md += `
|
|
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
|
}
|
|
1011
1174
|
md += `---
|
|
1012
|
-
*Generated by [
|
|
1175
|
+
*Generated by [markupR](https://markupr.com)*
|
|
1013
1176
|
${REPORT_SUPPORT_LINE}
|
|
1014
1177
|
`;
|
|
1015
1178
|
return md;
|
|
@@ -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"
|
|
@@ -2004,7 +2177,7 @@ var markdownTemplate = {
|
|
|
2004
2177
|
const { transcriptSegments, extractedFrames } = result;
|
|
2005
2178
|
const sessionTimestamp = formatDate(new Date(timestamp ?? Date.now()));
|
|
2006
2179
|
const sessionDuration = computeSessionDuration(transcriptSegments);
|
|
2007
|
-
let md = `#
|
|
2180
|
+
let md = `# markupR Session \u2014 ${sessionTimestamp}
|
|
2008
2181
|
`;
|
|
2009
2182
|
md += `> Segments: ${transcriptSegments.length} | Frames: ${extractedFrames.length} | Duration: ${sessionDuration}
|
|
2010
2183
|
|
|
@@ -2035,11 +2208,20 @@ var markdownTemplate = {
|
|
|
2035
2208
|
md += `
|
|
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
|
}
|
|
2041
2223
|
md += `---
|
|
2042
|
-
*Generated by [
|
|
2224
|
+
*Generated by [markupR](https://markupr.com)*
|
|
2043
2225
|
${REPORT_SUPPORT_LINE2}
|
|
2044
2226
|
`;
|
|
2045
2227
|
return { content: md, fileExtension: ".md" };
|
|
@@ -2057,7 +2239,7 @@ var jsonTemplate = {
|
|
|
2057
2239
|
const segmentFrameMap = mapFramesToSegments(transcriptSegments, extractedFrames);
|
|
2058
2240
|
const output = {
|
|
2059
2241
|
version: "1.0",
|
|
2060
|
-
generator: "
|
|
2242
|
+
generator: "markupR",
|
|
2061
2243
|
timestamp: new Date(timestamp ?? Date.now()).toISOString(),
|
|
2062
2244
|
summary: {
|
|
2063
2245
|
segments: transcriptSegments.length,
|
|
@@ -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
|
})
|
|
@@ -2099,7 +2282,7 @@ var githubIssueTemplate = {
|
|
|
2099
2282
|
let md = `## Feedback Report
|
|
2100
2283
|
|
|
2101
2284
|
`;
|
|
2102
|
-
md += `> Captured by [
|
|
2285
|
+
md += `> Captured by [markupR](https://markupr.com) on ${sessionTimestamp}
|
|
2103
2286
|
`;
|
|
2104
2287
|
md += `> ${transcriptSegments.length} segments | ${extractedFrames.length} frames | Duration: ${duration}
|
|
2105
2288
|
|
|
@@ -2149,7 +2332,7 @@ var githubIssueTemplate = {
|
|
|
2149
2332
|
`;
|
|
2150
2333
|
}
|
|
2151
2334
|
md += `---
|
|
2152
|
-
_Generated by [
|
|
2335
|
+
_Generated by [markupR](https://markupr.com)_
|
|
2153
2336
|
`;
|
|
2154
2337
|
return { content: md, fileExtension: ".md" };
|
|
2155
2338
|
}
|
|
@@ -2209,7 +2392,7 @@ var linearTemplate = {
|
|
|
2209
2392
|
}
|
|
2210
2393
|
}
|
|
2211
2394
|
md += `---
|
|
2212
|
-
_Captured by [
|
|
2395
|
+
_Captured by [markupR](https://markupr.com)_
|
|
2213
2396
|
`;
|
|
2214
2397
|
return { content: md, fileExtension: ".md" };
|
|
2215
2398
|
}
|
|
@@ -2283,7 +2466,7 @@ ${segment.text}
|
|
|
2283
2466
|
}
|
|
2284
2467
|
}
|
|
2285
2468
|
content += `----
|
|
2286
|
-
_Generated by [
|
|
2469
|
+
_Generated by [markupR|https://markupr.com]_
|
|
2287
2470
|
`;
|
|
2288
2471
|
return { content, fileExtension: ".jira" };
|
|
2289
2472
|
}
|
|
@@ -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 =
|
|
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,10 +2931,33 @@ 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",
|
|
2715
|
-
"Record screen and voice for a specified duration, then run the full
|
|
2960
|
+
"Record screen and voice for a specified duration, then run the full markupR pipeline to produce a structured feedback report.",
|
|
2716
2961
|
{
|
|
2717
2962
|
duration: z2.number().min(3).max(300).describe("Recording duration in seconds (3-300)"),
|
|
2718
2963
|
outputDir: z2.string().optional().describe("Output directory (default: session directory)"),
|
|
@@ -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: [
|
|
@@ -2779,7 +3043,7 @@ import { stat as stat4 } from "fs/promises";
|
|
|
2779
3043
|
function register3(server) {
|
|
2780
3044
|
server.tool(
|
|
2781
3045
|
"analyze_video",
|
|
2782
|
-
"Process an existing video file through the
|
|
3046
|
+
"Process an existing video file through the markupR pipeline. Generates a structured markdown report with transcript, key moments, and extracted frames.",
|
|
2783
3047
|
{
|
|
2784
3048
|
videoPath: z3.string().describe("Absolute path to the video file"),
|
|
2785
3049
|
audioPath: z3.string().optional().describe("Separate audio file path (if not embedded)"),
|
|
@@ -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,10 +3290,33 @@ 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",
|
|
3027
|
-
"Stop an active recording and run the full
|
|
3319
|
+
"Stop an active recording and run the full markupR pipeline on the captured video.",
|
|
3028
3320
|
{
|
|
3029
3321
|
sessionId: z6.string().optional().describe("Session ID (default: current active recording)"),
|
|
3030
3322
|
skipFrames: z6.boolean().optional().default(false).describe("Skip frame extraction"),
|
|
@@ -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: [
|
|
@@ -3128,7 +3430,7 @@ var LinearIssueCreator = class {
|
|
|
3128
3430
|
this.token = token;
|
|
3129
3431
|
}
|
|
3130
3432
|
/**
|
|
3131
|
-
* Push a
|
|
3433
|
+
* Push a markupR report to Linear, creating one issue per feedback item.
|
|
3132
3434
|
*/
|
|
3133
3435
|
async pushReport(reportPath, options) {
|
|
3134
3436
|
const markdown = await readFile4(reportPath, "utf-8");
|
|
@@ -3292,7 +3594,7 @@ var LinearIssueCreator = class {
|
|
|
3292
3594
|
* Build markdown description for a Linear issue from a feedback item.
|
|
3293
3595
|
*/
|
|
3294
3596
|
buildIssueDescription(item) {
|
|
3295
|
-
let desc = `##
|
|
3597
|
+
let desc = `## markupR Feedback: ${item.id}
|
|
3296
3598
|
|
|
3297
3599
|
`;
|
|
3298
3600
|
desc += `**Severity:** ${item.severity}
|
|
@@ -3327,7 +3629,7 @@ ${item.suggestedAction}
|
|
|
3327
3629
|
}
|
|
3328
3630
|
desc += `
|
|
3329
3631
|
---
|
|
3330
|
-
*Created by [
|
|
3632
|
+
*Created by [markupR](https://markupr.com)*`;
|
|
3331
3633
|
return desc;
|
|
3332
3634
|
}
|
|
3333
3635
|
/**
|
|
@@ -3417,9 +3719,9 @@ function extractSuggestedAction(section) {
|
|
|
3417
3719
|
function register7(server) {
|
|
3418
3720
|
server.tool(
|
|
3419
3721
|
"push_to_linear",
|
|
3420
|
-
"Push a
|
|
3722
|
+
"Push a markupR feedback report to Linear. Creates one issue per feedback item with priority mapping, labels, and full context.",
|
|
3421
3723
|
{
|
|
3422
|
-
reportPath: z7.string().describe("Absolute path to the
|
|
3724
|
+
reportPath: z7.string().describe("Absolute path to the markupR markdown report"),
|
|
3423
3725
|
teamKey: z7.string().describe('Linear team key (e.g., "ENG", "DES")'),
|
|
3424
3726
|
token: z7.string().optional().describe("Linear API key (or set LINEAR_API_KEY env var)"),
|
|
3425
3727
|
projectName: z7.string().optional().describe("Linear project name to assign issues to"),
|
|
@@ -3530,9 +3832,9 @@ var SEVERITY_LABELS = {
|
|
|
3530
3832
|
Low: { name: "priority: low", color: "0e8a16", description: "Low priority" }
|
|
3531
3833
|
};
|
|
3532
3834
|
var MARKUPR_LABEL = {
|
|
3533
|
-
name: "
|
|
3835
|
+
name: "markupR",
|
|
3534
3836
|
color: "6f42c1",
|
|
3535
|
-
description: "Created from
|
|
3837
|
+
description: "Created from markupR feedback session"
|
|
3536
3838
|
};
|
|
3537
3839
|
|
|
3538
3840
|
// src/integrations/github/GitHubIssueCreator.ts
|
|
@@ -3658,7 +3960,7 @@ function formatIssueBody(item, reportPath) {
|
|
|
3658
3960
|
body += `### Screenshots
|
|
3659
3961
|
|
|
3660
3962
|
`;
|
|
3661
|
-
body += `_${item.screenshotPaths.length} screenshot(s) captured \u2014 see the
|
|
3963
|
+
body += `_${item.screenshotPaths.length} screenshot(s) captured \u2014 see the markupR report for images._
|
|
3662
3964
|
|
|
3663
3965
|
`;
|
|
3664
3966
|
}
|
|
@@ -3676,7 +3978,7 @@ function formatIssueBody(item, reportPath) {
|
|
|
3676
3978
|
body += `_Source: \`${reportPath}\`_
|
|
3677
3979
|
`;
|
|
3678
3980
|
}
|
|
3679
|
-
body += `_Created by [
|
|
3981
|
+
body += `_Created by [markupR](https://markupr.com)_
|
|
3680
3982
|
`;
|
|
3681
3983
|
return body;
|
|
3682
3984
|
}
|
|
@@ -3796,7 +4098,7 @@ async function pushToGitHub(options) {
|
|
|
3796
4098
|
const markdown = await readFile5(reportPath, "utf-8");
|
|
3797
4099
|
let items = parseMarkuprReport(markdown);
|
|
3798
4100
|
if (items.length === 0) {
|
|
3799
|
-
throw new Error("No feedback items found in the report. Is this a valid
|
|
4101
|
+
throw new Error("No feedback items found in the report. Is this a valid markupR report?");
|
|
3800
4102
|
}
|
|
3801
4103
|
if (filterIds && filterIds.length > 0) {
|
|
3802
4104
|
const filterSet = new Set(filterIds.map((id) => id.toUpperCase()));
|
|
@@ -3866,9 +4168,9 @@ function parseRepoString(repoStr) {
|
|
|
3866
4168
|
function register8(server) {
|
|
3867
4169
|
server.tool(
|
|
3868
4170
|
"push_to_github",
|
|
3869
|
-
"Create GitHub issues from a
|
|
4171
|
+
"Create GitHub issues from a markupR feedback report. Each feedback item becomes a separate issue with labels and structured markdown.",
|
|
3870
4172
|
{
|
|
3871
|
-
reportPath: z8.string().describe("Absolute path to the
|
|
4173
|
+
reportPath: z8.string().describe("Absolute path to the markupR markdown report"),
|
|
3872
4174
|
repo: z8.string().describe('Target GitHub repository in "owner/repo" format'),
|
|
3873
4175
|
token: z8.string().optional().describe("GitHub token (falls back to GITHUB_TOKEN env or gh CLI)"),
|
|
3874
4176
|
items: z8.array(z8.string()).optional().describe("Specific FB-XXX item IDs to push (default: all)"),
|
|
@@ -4222,10 +4524,10 @@ function registerResources(server) {
|
|
|
4222
4524
|
}
|
|
4223
4525
|
|
|
4224
4526
|
// src/mcp/server.ts
|
|
4225
|
-
var VERSION = true ? "2.6.
|
|
4527
|
+
var VERSION = true ? "2.6.5" : "0.0.0-dev";
|
|
4226
4528
|
function createServer() {
|
|
4227
4529
|
const server = new McpServer2({
|
|
4228
|
-
name: "
|
|
4530
|
+
name: "markupR",
|
|
4229
4531
|
version: VERSION
|
|
4230
4532
|
});
|
|
4231
4533
|
register(server);
|
|
@@ -4242,8 +4544,8 @@ function createServer() {
|
|
|
4242
4544
|
}
|
|
4243
4545
|
|
|
4244
4546
|
// src/mcp/index.ts
|
|
4245
|
-
var VERSION2 = true ? "2.6.
|
|
4246
|
-
log(`
|
|
4547
|
+
var VERSION2 = true ? "2.6.5" : "0.0.0-dev";
|
|
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)}`);
|
|
4249
4551
|
process.exit(1);
|