diff --git a/.gitignore b/.gitignore index 3af027a..e0aab3d 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ build/ts/ .claude/ CLAUDE.md TYPESCRIPT_MIGRATION.md +Makefile # ai-dev-kit MCP writes these into its working directory (Mason repo root when # spawned from Mason). They're per-machine resource ledgers — never commit. .databricks-resources.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5a6f5d0..945d474 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.3.12" + ".": "1.3.13" } diff --git a/README.md b/README.md index b3b8ce3..71a1d42 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,32 @@ curl -fsSL https://raw.githubusercontent.com/databricks-solutions/mason/main/scr Download `Mason-*-arm64.dmg` from [the latest release](https://github.com/databricks-solutions/mason/releases/latest), open it, and drag Mason to Applications. -### Other platforms +### Windows (x64 or ARM64) — one-line install -Windows and Linux builds are not yet published. The `electron-builder` config supports them — building from source on those platforms (`npm ci && npm run build:win` / `npm run build:linux`) will produce installers. +Open PowerShell and run: + +```powershell +irm https://raw.githubusercontent.com/databricks-solutions/mason/main/scripts/install.ps1 | iex +``` + +This auto-detects your architecture (x64 vs ARM64), downloads the matching installer from GitHub Releases, and runs it silently as a per-user install (no admin elevation). Mason launches automatically when it finishes. Pin to a specific version: + +```powershell +$env:MASON_VERSION = "v1.3.13"; irm https://raw.githubusercontent.com/databricks-solutions/mason/main/scripts/install.ps1 | iex +``` + +### Windows — manual install + +Download the matching `.exe` from [the latest release](https://github.com/databricks-solutions/mason/releases/latest) and run it: + +- `Mason Setup X.Y.Z.exe` — x64 (Intel / AMD) +- `Mason Setup X.Y.Z-arm64.exe` — ARM64 (Surface Pro X, Copilot+ PCs) + +Not sure which architecture you have? Settings → System → About → "System type". + +### Linux + +Linux builds aren't published yet. Build from source: `npm ci && npm run build:linux` produces an `.AppImage`. ## How to get help diff --git a/package-lock.json b/package-lock.json index eabfaa6..ea4cdac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mason", - "version": "1.3.12", + "version": "1.3.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mason", - "version": "1.3.12", + "version": "1.3.13", "hasInstallScript": true, "dependencies": { "electron-window-state": "5.0.3", diff --git a/package.json b/package.json index ad431bd..e77f3f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mason", - "version": "1.3.12", + "version": "1.3.13", "description": "Desktop chat app for Databricks AI Gateway with MCP tool calling", "author": "Databricks", "main": "build/ts/main.js", @@ -72,7 +72,13 @@ }, "win": { "target": [ - "nsis" + { + "target": "nsis", + "arch": [ + "x64", + "arm64" + ] + } ], "icon": "build/icon_square.png" }, diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..fb2b84a --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,90 @@ +# Mason installer for Windows. Usage: +# +# irm https://raw.githubusercontent.com/databricks-solutions/mason/main/scripts/install.ps1 | iex +# +# Pin to a specific version: +# +# $env:MASON_VERSION = "v1.3.13"; irm https://raw.githubusercontent.com/databricks-solutions/mason/main/scripts/install.ps1 | iex +# +# Downloads the latest Mason release for the current architecture (x64 or +# ARM64) and runs the NSIS installer in silent mode. Per-user install — +# no admin elevation required. + +$ErrorActionPreference = "Stop" +$repo = "databricks-solutions/mason" + +# --- Detect architecture --- +$arch = $env:PROCESSOR_ARCHITECTURE +if ($arch -eq "ARM64") { + $suffix = "-arm64" + $archLabel = "ARM64" +} elseif ($arch -eq "AMD64" -or $arch -eq "x86_64") { + $suffix = "" + $archLabel = "x64" +} else { + Write-Host "[mason] Unsupported architecture: $arch (Mason supports x64 and ARM64)" -ForegroundColor Red + exit 1 +} + +# --- Look up release --- +$tag = $env:MASON_VERSION +if ($tag) { + Write-Host "[mason] Looking up release $tag for $archLabel..." + $releaseUrl = "https://api.github.com/repos/$repo/releases/tags/$tag" +} else { + Write-Host "[mason] Looking up latest release for $archLabel..." + $releaseUrl = "https://api.github.com/repos/$repo/releases/latest" +} + +try { + $release = Invoke-RestMethod -Uri $releaseUrl -Headers @{ + "User-Agent" = "mason-installer" + "Accept" = "application/vnd.github+json" + } +} catch { + Write-Host "[mason] Failed to query GitHub release: $_" -ForegroundColor Red + exit 1 +} + +$version = $release.tag_name -replace "^v","" +$assetName = "Mason Setup $version$suffix.exe" +$asset = $release.assets | Where-Object { $_.name -eq $assetName } +if (-not $asset) { + Write-Host "[mason] No release asset matching '$assetName' (architecture $archLabel)" -ForegroundColor Red + Write-Host "[mason] Available assets:" + $release.assets | ForEach-Object { Write-Host " - $($_.name)" } + exit 1 +} + +# --- Download --- +$tmp = Join-Path $env:TEMP $assetName +Write-Host "[mason] Downloading $assetName ($([math]::Round($asset.size / 1MB, 1)) MB)..." +try { + Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $tmp -UseBasicParsing +} catch { + Write-Host "[mason] Download failed: $_" -ForegroundColor Red + exit 1 +} + +# --- Install --- +Write-Host "[mason] Running installer (per-user, no admin required)..." +# /S = silent install. electron-builder NSIS default install path is +# %LOCALAPPDATA%\Programs\mason\Mason.exe and the installer adds a +# Start-Menu shortcut + Desktop shortcut automatically. +$proc = Start-Process -FilePath $tmp -ArgumentList "/S" -PassThru -Wait +if ($proc.ExitCode -ne 0) { + Write-Host "[mason] Installer exited with code $($proc.ExitCode)" -ForegroundColor Red + Remove-Item $tmp -ErrorAction SilentlyContinue + exit 1 +} + +$installPath = Join-Path $env:LOCALAPPDATA "Programs\mason\Mason.exe" +Remove-Item $tmp -ErrorAction SilentlyContinue + +if (Test-Path $installPath) { + Write-Host "[mason] Installed at $installPath" -ForegroundColor Green + Write-Host "[mason] Launching..." + Start-Process $installPath +} else { + Write-Host "[mason] Install completed. Launch Mason from the Start Menu." -ForegroundColor Green +} diff --git a/src/main.ts b/src/main.ts index 6f4a105..60dc7f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1308,8 +1308,23 @@ const IMAGE_MIME: Record = { gif: "image/gif", webp: "image/webp", }; -// @ts-ignore — pdf-parse has no @types -const { PDFParse } = require("pdf-parse"); +// pdf-parse is loaded lazily inside the PDF branch below. Eager require fails +// on Windows because the bundled pdf.js calls DOMMatrix during module init, +// which doesn't exist in Electron's main-process Node context on Windows. +// Lazy-loading keeps startup clean; PDF uploads on platforms where the load +// fails return a friendly error instead of crashing the whole app. +let _PDFParse: any = undefined; +function loadPDFParse(): any { + if (_PDFParse !== undefined) return _PDFParse; + try { + // @ts-ignore — pdf-parse has no @types + _PDFParse = require("pdf-parse").PDFParse; + } catch (err) { + console.error("[UPLOAD] pdf-parse failed to load:", (err as Error).message); + _PDFParse = null; + } + return _PDFParse; +} ipcMain.handle("read-file-for-upload", async (_event: IpcMainInvokeEvent, { filePath }: { filePath: string }) => { if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`); @@ -1335,6 +1350,12 @@ ipcMain.handle("read-file-for-upload", async (_event: IpcMainInvokeEvent, { file throw new Error( `PDF too large (${(stats.size / 1024 / 1024).toFixed(1)} MB > ${MAX_PDF_BYTES / 1024 / 1024} MB)` ); + const PDFParse = loadPDFParse(); + if (!PDFParse) { + throw new Error( + "PDF support isn't available on this build. Convert the PDF to text or paste its content directly into the chat." + ); + } const buf = fs.readFileSync(filePath); const parser = new PDFParse({ data: buf }); let result: any;