markupr 2.6.6 → 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 CHANGED
@@ -18,7 +18,8 @@
18
18
 
19
19
  <p align="center">
20
20
  <a href="#quick-start">Quick Start</a> &middot;
21
- <a href="#why-markupR">Why markupR</a> &middot;
21
+ <a href="#context-aware-capture-what-your-agent-actually-gets">Context-Aware Capture</a> &middot;
22
+ <a href="#why-markupr">Why markupR</a> &middot;
22
23
  <a href="#mcp-server">MCP Server</a> &middot;
23
24
  <a href="#cli">CLI</a> &middot;
24
25
  <a href="#integrations">Integrations</a> &middot;
@@ -27,7 +28,11 @@
27
28
 
28
29
  ---
29
30
 
30
- <!-- hero-screenshot: Replace with an actual screenshot or GIF of markupR in action -->
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 records your screen while you narrate what's wrong. When you stop, it runs an intelligent pipeline that correlates your transcript timestamps with the video to extract the right frames at the right moments -- then stitches everything into structured Markdown your AI agent can act on immediately.
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
- - **Output** -- structured Markdown with screenshots placed exactly where they belong
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
- ### CLI (zero install)
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 analyze ./recording.mov
70
+ npx -p markupr markupr-mcp
54
71
  ```
55
72
 
56
- ### MCP Server (for AI coding agents)
73
+ ### CLI (for existing recordings / CI / automation)
57
74
 
58
75
  ```bash
59
- npx markupr-mcp
76
+ npx markupr analyze ./recording.mov
60
77
  ```
61
78
 
62
- ### Desktop App
79
+ Use this when you already have a video file. The desktop app remains the primary capture workflow.
63
80
 
64
- Download from [markupr.com](https://markupr.com) or [GitHub Releases](https://github.com/eddiesanjuan/markupr/releases). Available for macOS and Windows.
81
+ ## Context-Aware Capture: What Your Agent Actually Gets
65
82
 
66
- No API keys required. Local Whisper transcription works out of the box.
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 inline screenshots. Not a raw transcript with random images. Every screenshot shows exactly what you were describing when you said it.
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 use. CLI for scripts and CI/CD. MCP server for agent integration. GitHub Action for PR feedback. Same pipeline, four interfaces.
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
  ![Screenshot at 1:12](screenshots/fb-002.png)
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. Your agent sees what you see. |
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 describing the bug in text. The agent looks at your screen and acts.
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 a screen recording into structured Markdown.
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 -- screenshots capture automatically during pauses
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
@@ -3623,7 +3623,7 @@ async function runInit(options) {
3623
3623
  }
3624
3624
 
3625
3625
  // src/cli/index.ts
3626
- var VERSION = true ? "2.6.6" : "0.0.0-dev";
3626
+ var VERSION = true ? "2.6.8" : "0.0.0-dev";
3627
3627
  var SYMBOLS = {
3628
3628
  check: "\u2714",
3629
3629
  // checkmark
@@ -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.message
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 instanceof Error ? error.message : "Failed to check for updates"
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 instanceof Error ? error.message : "Failed to download update"
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
- return message.includes("app-update.yml") || message.includes("latest.yml") || message.includes("enoent");
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();
@@ -4524,7 +4524,7 @@ function registerResources(server) {
4524
4524
  }
4525
4525
 
4526
4526
  // src/mcp/server.ts
4527
- var VERSION = true ? "2.6.6" : "0.0.0-dev";
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.6" : "0.0.0-dev";
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.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",