assets: zn asset CLI, MCP get_asset, desktop save dialog - #736
Open
gtheys wants to merge 2 commits into
Open
Conversation
Server: - GET /api/assets/raw accepts ?download=1, serving the asset with an attachment Content-Disposition (RFC 5987 filename*) so plain browsers and <a href> links save the file under its own name. - The SPA fallback now returns a real 404 for unknown file-looking paths (anything with a non-.html extension, e.g. /files/assets/img.png) instead of index.html with HTTP 200, which made naive download attempts 'succeed' while yielding an HTML file. Unknown app routes still fall through to the SPA shell. Web + Desktop UI (shared app-core Assets view): - New 'Download' context-menu item and a keyboard 'd' on focused asset rows. The shared downloadAsset() helper resolves the asset URL exactly like embedded images do (same-origin HTTP on web, zen-asset:// scheme in the desktop app for local and remote vaults) and saves it via a blob + <a download> click, with no bridge-contract change. Tests: Go coverage for the download flag and the fallback 404; vitest coverage for the download helper (resolve failure, read failure, and anchor naming).
CLI:
- zn asset list [--json] and zn asset get <path> [--output <file>]
[--quiet], backed by a new readAsset() on the VaultBackend seam:
local vaults read under the root (vault-ops readAsset, path-escape
guarded), remote vaults stream GET /api/assets/raw with Bearer auth.
Without --output the bytes go to stdout, so piping works.
MCP:
- New get_asset tool next to list_assets: path in, { path, size,
mimeType, base64 } out, with a 10 MB cap that points agents at
zn asset get for bigger files.
Desktop:
- Bridge contract gains optional downloadAsset(relPath): IPC handler
shows the native save dialog (suggested name = basename), then copies
the file for local vaults or streams the server's raw-asset response
for remote/self-hosted vaults. Preload passthrough included; the
shared Assets-view Download action prefers it and keeps the web blob
path as fallback.
Tests: vitest for the CLI commands, the MCP tool, and the shared
helper's desktop-vs-web branching. Verified live against a running
server: local list/get, remote list/get/output/401.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #735 — closes the remaining surfaces of #716 (CLI + MCP + desktop save dialog). Stacked on #735: review that first; this diff shrinks to the CLI/MCP/desktop work once it merges.
1. CLI:
zn asset list/zn asset getBacked by a new
readAsset()on theVaultBackendseam, so it works identically against a local folder or a server:vault-ops.readAsset(), vault-root escape-guardedGET /api/assets/rawwith Bearer auth, raw bytes (no JSON envelope), properRemoteRequestErrorhandling (401 gives the existing token hint)assetis registered as a subcommand group (likefolder/task/base) and documented inzn --helpunder a new ASSETS heading.2. MCP:
get_assettoolSibling to
list_assets:pathin →{ path, size, mimeType, base64 }out. MIME is guessed from the extension (same table as the desktop's protocol handler). A 10 MB cap rejects huge binaries with a message pointing agents atzn asset get— a base64 payload over that size isn't useful inside one JSON-RPC message anyway.3. Desktop: native save dialog
bridge.downloadAsset(relPath)(optional method, so web stays clean):fs.copyFilefetchAssetResponse(rel)'s web body to the file viastream/promises pipeline— bytes never land in the rendererVerification
npm run typecheck(all TS workspaces) +go vet/go test— green; web + desktop builds passcli.jsbundle and ran it — local vault list/get (file + stdout piping), and against a running server: remote list/get/--output, plus a wrong-token run producing the clean 401 hint