markupr 2.6.5 → 2.6.8
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 +47 -21
- package/dist/cli/index.mjs +1 -1
- package/dist/main/index.mjs +38 -15
- package/dist/mcp/index.mjs +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<a href="#quick-start">Quick Start</a> ·
|
|
21
|
-
<a href="#
|
|
21
|
+
<a href="#context-aware-capture-what-your-agent-actually-gets">Context-Aware Capture</a> ·
|
|
22
|
+
<a href="#why-markupr">Why markupR</a> ·
|
|
22
23
|
<a href="#mcp-server">MCP Server</a> ·
|
|
23
24
|
<a href="#cli">CLI</a> ·
|
|
24
25
|
<a href="#integrations">Integrations</a> ·
|
|
@@ -27,7 +28,11 @@
|
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="assets/demo-cli.gif" alt="markupR desktop-to-report workflow demo" width="800">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
Desktop app workflow is the default: record + narrate + stop, then ship context-rich markdown (frames + cursor/window/focus hints when available) directly to your agent.
|
|
31
36
|
|
|
32
37
|
## The Problem
|
|
33
38
|
|
|
@@ -35,11 +40,12 @@ AI coding agents can't see your screen. When you find a bug, you context-switch
|
|
|
35
40
|
|
|
36
41
|
## The Solution
|
|
37
42
|
|
|
38
|
-
markupR
|
|
43
|
+
markupR is a desktop capture app first. You hit a hotkey, narrate what you see, and stop. Then it runs a post-session pipeline that aligns transcript timestamps with the recording, extracts the right frames, and outputs structured Markdown your agent can execute against immediately.
|
|
39
44
|
|
|
40
45
|
- **Record** -- press a hotkey, talk through what you see
|
|
41
46
|
- **Process** -- Whisper transcribes, ffmpeg extracts frames at the exact moments you described
|
|
42
|
-
- **
|
|
47
|
+
- **Enrich** -- capture context is attached to shot markers (cursor position, active window/app, focused element hints when available)
|
|
48
|
+
- **Output** -- structured Markdown with screenshots and context your agent can trust
|
|
43
49
|
|
|
44
50
|
```
|
|
45
51
|
Cmd+Shift+F --> talk --> Cmd+Shift+F --> Cmd+V into your agent
|
|
@@ -47,31 +53,49 @@ Cmd+Shift+F --> talk --> Cmd+Shift+F --> Cmd+V into your agent
|
|
|
47
53
|
|
|
48
54
|
## Quick Start
|
|
49
55
|
|
|
50
|
-
###
|
|
56
|
+
### Desktop App (recommended)
|
|
57
|
+
|
|
58
|
+
Download from [markupr.com](https://markupr.com) or [GitHub Releases](https://github.com/eddiesanjuan/markupr/releases).
|
|
59
|
+
|
|
60
|
+
> **macOS install note:** Apple notarization is currently rolling out. If macOS warns on first launch, use **Right-click -> Open** once to trust the app. If needed, run: `xattr -dr com.apple.quarantine /Applications/markupR.app`
|
|
61
|
+
|
|
62
|
+
1. Press `Cmd+Shift+F` (macOS) or `Ctrl+Shift+F` (Windows) to start
|
|
63
|
+
2. Narrate what you see and mark shots when needed
|
|
64
|
+
3. Press the hotkey again to stop
|
|
65
|
+
4. Paste the generated report path into Claude Code, Cursor, Windsurf, or any coding agent
|
|
66
|
+
|
|
67
|
+
### MCP Server (for AI coding agents)
|
|
51
68
|
|
|
52
69
|
```bash
|
|
53
|
-
npx markupr
|
|
70
|
+
npx -p markupr markupr-mcp
|
|
54
71
|
```
|
|
55
72
|
|
|
56
|
-
###
|
|
73
|
+
### CLI (for existing recordings / CI / automation)
|
|
57
74
|
|
|
58
75
|
```bash
|
|
59
|
-
npx markupr
|
|
76
|
+
npx markupr analyze ./recording.mov
|
|
60
77
|
```
|
|
61
78
|
|
|
62
|
-
|
|
79
|
+
Use this when you already have a video file. The desktop app remains the primary capture workflow.
|
|
63
80
|
|
|
64
|
-
|
|
81
|
+
## Context-Aware Capture: What Your Agent Actually Gets
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
Every important frame can carry extra machine-usable context, not just pixels.
|
|
84
|
+
|
|
85
|
+
- **Cursor coordinates** at capture time
|
|
86
|
+
- **Active app + window title** (best-effort from OS context)
|
|
87
|
+
- **Focused element hints** (role/text/title hints when available)
|
|
88
|
+
- **Trigger metadata** (`manual`, `pause`, or `voice-command`)
|
|
89
|
+
|
|
90
|
+
This makes the report a high-signal liaison between you and your agent: what you said, what you saw, and where your attention was.
|
|
67
91
|
|
|
68
92
|
## Why markupR?
|
|
69
93
|
|
|
70
94
|
**Local-first.** Whisper runs on your device. Your recordings, transcripts, and screenshots never leave your machine. No cloud dependency, no account required.
|
|
71
95
|
|
|
72
|
-
**AI-native output.** The Markdown output is structured for LLM consumption -- headings, categories, severity levels, and
|
|
96
|
+
**AI-native output.** The Markdown output is structured for LLM consumption -- headings, categories, severity levels, inline screenshots, and capture-context hints. Not a raw transcript with random images.
|
|
73
97
|
|
|
74
|
-
**Works everywhere.** Desktop app for daily
|
|
98
|
+
**Works everywhere.** Desktop app for daily flow. CLI for scripts and CI/CD. MCP server for agent integration. GitHub Action for PR feedback. Same pipeline, four interfaces.
|
|
75
99
|
|
|
76
100
|
**Open source.** MIT licensed. No telemetry, no tracking, no analytics. Read the source, fork it, ship it.
|
|
77
101
|
|
|
@@ -95,7 +119,7 @@ There's a visible layout shift -- the sidebar jumps left by about
|
|
|
95
119
|

|
|
96
120
|
```
|
|
97
121
|
|
|
98
|
-
Each screenshot is extracted from the exact video frame matching your narration timestamp. See full examples in [`examples/`](examples/).
|
|
122
|
+
Each screenshot is extracted from the exact video frame matching your narration timestamp, with context hints attached when available. See full examples in [`examples/`](examples/).
|
|
99
123
|
|
|
100
124
|
## MCP Server
|
|
101
125
|
|
|
@@ -110,7 +134,7 @@ Give your AI coding agent eyes and ears. Add markupR as an MCP server and it can
|
|
|
110
134
|
"mcpServers": {
|
|
111
135
|
"markupR": {
|
|
112
136
|
"command": "npx",
|
|
113
|
-
"args": ["-y", "markupr-mcp"]
|
|
137
|
+
"args": ["-y", "-p", "markupr", "markupr-mcp"]
|
|
114
138
|
}
|
|
115
139
|
}
|
|
116
140
|
}
|
|
@@ -122,9 +146,9 @@ Give your AI coding agent eyes and ears. Add markupR as an MCP server and it can
|
|
|
122
146
|
|
|
123
147
|
| Tool | Description |
|
|
124
148
|
|------|-------------|
|
|
125
|
-
| `capture_screenshot` | Grab the current screen
|
|
149
|
+
| `capture_screenshot` | Grab the current screen and attach context metadata (cursor + active app/window + focus hints when available). |
|
|
126
150
|
| `capture_with_voice` | Record screen + mic for a set duration. Returns a structured report. |
|
|
127
|
-
| `analyze_video` | Process any `.mov` or `.mp4` into Markdown with extracted frames. |
|
|
151
|
+
| `analyze_video` | Process any existing `.mov` or `.mp4` into Markdown with extracted frames (fallback path for externally captured recordings). |
|
|
128
152
|
| `analyze_screenshot` | Run a screenshot through the AI analysis pipeline. |
|
|
129
153
|
| `start_recording` | Begin an interactive recording session. |
|
|
130
154
|
| `stop_recording` | End the session and run the full pipeline. |
|
|
@@ -141,7 +165,7 @@ Agent: [calls capture_screenshot]
|
|
|
141
165
|
[fixes the code]
|
|
142
166
|
```
|
|
143
167
|
|
|
144
|
-
No copy-pasting screenshots. No
|
|
168
|
+
No copy-pasting screenshots. No rewriting what you already know. The agent gets structured report context and acts.
|
|
145
169
|
|
|
146
170
|
Full MCP documentation: [README-MCP.md](README-MCP.md)
|
|
147
171
|
|
|
@@ -159,7 +183,7 @@ npm install -g markupr
|
|
|
159
183
|
|
|
160
184
|
### Commands
|
|
161
185
|
|
|
162
|
-
**`markupR analyze <video>`** -- Process
|
|
186
|
+
**`markupR analyze <video>`** -- Process an existing screen recording into structured Markdown.
|
|
163
187
|
|
|
164
188
|
```bash
|
|
165
189
|
markupR analyze ./bug-demo.mov
|
|
@@ -209,10 +233,10 @@ Run markupR in CI to get visual feedback on pull requests:
|
|
|
209
233
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
210
234
|
```
|
|
211
235
|
|
|
212
|
-
### Desktop App Workflow
|
|
236
|
+
### Desktop App Workflow (Primary)
|
|
213
237
|
|
|
214
238
|
1. Press `Cmd+Shift+F` (macOS) or `Ctrl+Shift+F` (Windows)
|
|
215
|
-
2. Narrate what you see
|
|
239
|
+
2. Narrate what you see and mark shots as needed
|
|
216
240
|
3. Press the hotkey again to stop
|
|
217
241
|
4. Paste the file path from your clipboard into Claude Code, Cursor, or any AI agent
|
|
218
242
|
|
|
@@ -238,6 +262,8 @@ Run markupR in CI to get visual feedback on pull requests:
|
|
|
238
262
|
|
|
239
263
|
The pipeline degrades gracefully. No ffmpeg? Transcript-only output. No Whisper model? Timer-based screenshots. No API keys? Everything runs locally.
|
|
240
264
|
|
|
265
|
+
Desktop app capture remains the default path. CLI/MCP `analyze_video` remains available when you need to process an existing recording.
|
|
266
|
+
|
|
241
267
|
For architecture details, see [CLAUDE.md](CLAUDE.md).
|
|
242
268
|
|
|
243
269
|
## Development
|
package/dist/cli/index.mjs
CHANGED
package/dist/main/index.mjs
CHANGED
|
@@ -7445,6 +7445,7 @@ class AutoUpdaterManager {
|
|
|
7445
7445
|
updaterAvailable = false;
|
|
7446
7446
|
autoCheckEnabled = true;
|
|
7447
7447
|
isChecking = false;
|
|
7448
|
+
activeCheckUserInitiated = false;
|
|
7448
7449
|
startupCheckTimer = null;
|
|
7449
7450
|
periodicCheckTimer = null;
|
|
7450
7451
|
STARTUP_CHECK_DELAY_MS = 5e3;
|
|
@@ -7497,10 +7498,10 @@ class AutoUpdaterManager {
|
|
|
7497
7498
|
return;
|
|
7498
7499
|
}
|
|
7499
7500
|
this.startupCheckTimer = setTimeout(() => {
|
|
7500
|
-
void this.checkForUpdates();
|
|
7501
|
+
void this.checkForUpdates({ userInitiated: false });
|
|
7501
7502
|
}, this.STARTUP_CHECK_DELAY_MS);
|
|
7502
7503
|
this.periodicCheckTimer = setInterval(() => {
|
|
7503
|
-
void this.checkForUpdates();
|
|
7504
|
+
void this.checkForUpdates({ userInitiated: false });
|
|
7504
7505
|
}, this.PERIODIC_CHECK_INTERVAL_MS);
|
|
7505
7506
|
}
|
|
7506
7507
|
clearAutoCheckTimers() {
|
|
@@ -7554,14 +7555,14 @@ class AutoUpdaterManager {
|
|
|
7554
7555
|
});
|
|
7555
7556
|
});
|
|
7556
7557
|
autoUpdater.on("error", (error) => {
|
|
7557
|
-
if (this.shouldSuppressUpdateError(error)) {
|
|
7558
|
+
if (this.shouldSuppressUpdateError(error, this.activeCheckUserInitiated)) {
|
|
7558
7559
|
log$1.warn("[AutoUpdater] Suppressing expected updater error:", error.message);
|
|
7559
|
-
this.updateState("not-available");
|
|
7560
|
+
this.updateState(this.activeCheckUserInitiated ? "not-available" : "idle");
|
|
7560
7561
|
return;
|
|
7561
7562
|
}
|
|
7562
7563
|
log$1.error("[AutoUpdater] Error:", error);
|
|
7563
7564
|
this.sendStatus("error", {
|
|
7564
|
-
message: error
|
|
7565
|
+
message: this.getUserFacingUpdateErrorMessage(error)
|
|
7565
7566
|
});
|
|
7566
7567
|
});
|
|
7567
7568
|
}
|
|
@@ -7570,7 +7571,7 @@ class AutoUpdaterManager {
|
|
|
7570
7571
|
*/
|
|
7571
7572
|
setupIPCHandlers() {
|
|
7572
7573
|
ipcMain.handle(IPC_CHANNELS.UPDATE_CHECK, async () => {
|
|
7573
|
-
return this.checkForUpdates();
|
|
7574
|
+
return this.checkForUpdates({ userInitiated: true });
|
|
7574
7575
|
});
|
|
7575
7576
|
ipcMain.handle(IPC_CHANNELS.UPDATE_DOWNLOAD, async () => {
|
|
7576
7577
|
return this.downloadUpdate();
|
|
@@ -7592,7 +7593,8 @@ class AutoUpdaterManager {
|
|
|
7592
7593
|
/**
|
|
7593
7594
|
* Check for available updates
|
|
7594
7595
|
*/
|
|
7595
|
-
async checkForUpdates() {
|
|
7596
|
+
async checkForUpdates(options = {}) {
|
|
7597
|
+
const userInitiated = options.userInitiated ?? true;
|
|
7596
7598
|
if (!this.updaterAvailable) {
|
|
7597
7599
|
this.updateState("not-available");
|
|
7598
7600
|
return null;
|
|
@@ -7600,27 +7602,29 @@ class AutoUpdaterManager {
|
|
|
7600
7602
|
if (this.isChecking) {
|
|
7601
7603
|
return null;
|
|
7602
7604
|
}
|
|
7603
|
-
if (this.state.status === "downloading") {
|
|
7605
|
+
if (this.state.status === "downloading" || !userInitiated && this.state.status === "ready") {
|
|
7604
7606
|
return null;
|
|
7605
7607
|
}
|
|
7606
7608
|
try {
|
|
7607
7609
|
this.isChecking = true;
|
|
7610
|
+
this.activeCheckUserInitiated = userInitiated;
|
|
7608
7611
|
log$1.info("[AutoUpdater] Checking for updates");
|
|
7609
7612
|
const result = await autoUpdater.checkForUpdates();
|
|
7610
7613
|
return result;
|
|
7611
7614
|
} catch (error) {
|
|
7612
|
-
if (error instanceof Error && this.shouldSuppressUpdateError(error)) {
|
|
7615
|
+
if (error instanceof Error && this.shouldSuppressUpdateError(error, userInitiated)) {
|
|
7613
7616
|
log$1.warn("[AutoUpdater] Update check skipped:", error.message);
|
|
7614
|
-
this.updateState("not-available");
|
|
7617
|
+
this.updateState(userInitiated ? "not-available" : "idle");
|
|
7615
7618
|
return null;
|
|
7616
7619
|
}
|
|
7617
7620
|
log$1.error("[AutoUpdater] Check for updates failed:", error);
|
|
7618
7621
|
this.sendStatus("error", {
|
|
7619
|
-
message: error
|
|
7622
|
+
message: this.getUserFacingUpdateErrorMessage(error)
|
|
7620
7623
|
});
|
|
7621
7624
|
return null;
|
|
7622
7625
|
} finally {
|
|
7623
7626
|
this.isChecking = false;
|
|
7627
|
+
this.activeCheckUserInitiated = false;
|
|
7624
7628
|
}
|
|
7625
7629
|
}
|
|
7626
7630
|
/**
|
|
@@ -7638,7 +7642,7 @@ class AutoUpdaterManager {
|
|
|
7638
7642
|
} catch (error) {
|
|
7639
7643
|
log$1.error("[AutoUpdater] Download failed:", error);
|
|
7640
7644
|
this.sendStatus("error", {
|
|
7641
|
-
message: error
|
|
7645
|
+
message: this.getUserFacingUpdateErrorMessage(error)
|
|
7642
7646
|
});
|
|
7643
7647
|
}
|
|
7644
7648
|
}
|
|
@@ -7700,9 +7704,28 @@ class AutoUpdaterManager {
|
|
|
7700
7704
|
/**
|
|
7701
7705
|
* Suppress known local-install update errors (not actionable for users).
|
|
7702
7706
|
*/
|
|
7703
|
-
shouldSuppressUpdateError(error) {
|
|
7707
|
+
shouldSuppressUpdateError(error, userInitiated) {
|
|
7704
7708
|
const message = error.message.toLowerCase();
|
|
7705
|
-
|
|
7709
|
+
const isLocalBuildMetadataError = message.includes("app-update.yml") || message.includes("latest.yml") || message.includes("enoent");
|
|
7710
|
+
if (isLocalBuildMetadataError) {
|
|
7711
|
+
return true;
|
|
7712
|
+
}
|
|
7713
|
+
if (!userInitiated && this.isTransientNetworkError(message)) {
|
|
7714
|
+
return true;
|
|
7715
|
+
}
|
|
7716
|
+
return false;
|
|
7717
|
+
}
|
|
7718
|
+
isTransientNetworkError(message) {
|
|
7719
|
+
return message.includes("err_internet_disconnected") || message.includes("err_network_changed") || message.includes("err_name_not_resolved") || message.includes("econnrefused") || message.includes("eai_again") || message.includes("enotfound") || message.includes("timed out") || message.includes("timeout") || message.includes("network request failed") || message.includes("failed to fetch");
|
|
7720
|
+
}
|
|
7721
|
+
getUserFacingUpdateErrorMessage(error) {
|
|
7722
|
+
if (!(error instanceof Error)) {
|
|
7723
|
+
return "Unable to check for updates right now. Please try again.";
|
|
7724
|
+
}
|
|
7725
|
+
if (this.isTransientNetworkError(error.message.toLowerCase())) {
|
|
7726
|
+
return "No internet connection detected. Reconnect and try again.";
|
|
7727
|
+
}
|
|
7728
|
+
return error.message;
|
|
7706
7729
|
}
|
|
7707
7730
|
/**
|
|
7708
7731
|
* Get current update state
|
|
@@ -12498,7 +12521,7 @@ function handleMenuAction(action, data) {
|
|
|
12498
12521
|
mainWindow?.webContents.send(IPC_CHANNELS.SHOW_SHORTCUTS);
|
|
12499
12522
|
break;
|
|
12500
12523
|
case "check-updates":
|
|
12501
|
-
autoUpdaterManager.checkForUpdates();
|
|
12524
|
+
void autoUpdaterManager.checkForUpdates({ userInitiated: true });
|
|
12502
12525
|
break;
|
|
12503
12526
|
case "open-session":
|
|
12504
12527
|
showWindow();
|
package/dist/mcp/index.mjs
CHANGED
|
@@ -4524,7 +4524,7 @@ function registerResources(server) {
|
|
|
4524
4524
|
}
|
|
4525
4525
|
|
|
4526
4526
|
// src/mcp/server.ts
|
|
4527
|
-
var VERSION = true ? "2.6.
|
|
4527
|
+
var VERSION = true ? "2.6.8" : "0.0.0-dev";
|
|
4528
4528
|
function createServer() {
|
|
4529
4529
|
const server = new McpServer2({
|
|
4530
4530
|
name: "markupR",
|
|
@@ -4544,7 +4544,7 @@ function createServer() {
|
|
|
4544
4544
|
}
|
|
4545
4545
|
|
|
4546
4546
|
// src/mcp/index.ts
|
|
4547
|
-
var VERSION2 = true ? "2.6.
|
|
4547
|
+
var VERSION2 = true ? "2.6.8" : "0.0.0-dev";
|
|
4548
4548
|
log(`markupR MCP server v${VERSION2} starting...`);
|
|
4549
4549
|
process.on("uncaughtException", (error) => {
|
|
4550
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.
|
|
3
|
+
"version": "2.6.8",
|
|
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",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"prebuild:win": "npm run generate:installer-images",
|
|
100
100
|
"build:cli": "node scripts/build-cli.mjs",
|
|
101
101
|
"build:mcp": "node scripts/build-mcp.mjs",
|
|
102
|
-
"prepublishOnly": "npm run build:cli"
|
|
102
|
+
"prepublishOnly": "npm run build:cli && npm run build:mcp"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@electron/notarize": "^3.1.1",
|