From 3a56bf01c49ad246a7a46c3a198eb5a90e0d63df Mon Sep 17 00:00:00 2001 From: mao13811364454 <293340454+mao13811364454@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:30:50 +0800 Subject: [PATCH 1/3] fix(macos): allow terminal tools to request audio input Add the audio-input entitlement and microphone usage description for recording tools launched from the integrated terminal. Document user consent, virtual-input routing, and short-recording verification. (cherry picked from commit 42c210faaf4deda42a7c02472ef23ba08a8749d0) --- src-tauri/Info.plist | 2 ++ src-tauri/sidecar-entitlements.plist | 3 +++ website/guide/terminal.md | 15 +++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist index a2352ce8a..323b8f45e 100644 --- a/src-tauri/Info.plist +++ b/src-tauri/Info.plist @@ -18,6 +18,8 @@ entirely and falls back to the .icns. --> + NSMicrophoneUsageDescription + Allow command-line tools you run in VMark's integrated terminal to record audio from your microphone or other audio input devices. CFBundleVersion CFBundleIconName diff --git a/src-tauri/sidecar-entitlements.plist b/src-tauri/sidecar-entitlements.plist index d7445c344..769590be4 100644 --- a/src-tauri/sidecar-entitlements.plist +++ b/src-tauri/sidecar-entitlements.plist @@ -2,6 +2,9 @@ + + com.apple.security.device.audio-input + com.apple.security.cs.allow-jit diff --git a/website/guide/terminal.md b/website/guide/terminal.md index 74e2ef28a..cd5de251b 100644 --- a/website/guide/terminal.md +++ b/website/guide/terminal.md @@ -186,6 +186,21 @@ Standard shell shortcuts like `Ctrl+R` (reverse history search in zsh/bash) work When the workspace root changes after the terminal is already running, idle sessions automatically `cd` to the new root. A session busy with a command (say, `vim` or `less`) is not interrupted: it changes directory once the command finishes, which needs [shell integration](#shell-integration) to detect. With the [workspace rail](/guide/workspace-rail) on, sessions that belong to a workspace keep their own directory. +## Audio input on macOS + +Recording tools running in the integrated terminal may request microphone +permission under VMark's name. Allow access when macOS asks. If VMark is listed in +**System Settings → Privacy & Security → Microphone**, you can manage its access +there. If no prompt or entry appears, collect the recording tool's output and +macOS permission logs when reporting the issue. Permission is requested when a +tool accesses audio input, not simply when you open the terminal. + +For virtual audio inputs such as BlackHole, permission alone does not establish +audio routing. Select the intended input in your recording tool, route audio to it, and verify +a short recording before a longer session. A growing audio file alone does not +confirm that sound was captured. VMark does not include a recorder or transcriber; +those commands are provided by tools you install separately. + ## Not yet implemented These are tracked but do **not** ship today. They are listed here because From cdb4b1d3b690c8dfc45e219b1f42106406d4640e Mon Sep 17 00:00:00 2001 From: xiaolai Date: Wed, 30 Sep 2026 12:51:39 +0800 Subject: [PATCH 2/3] fix(macos): let terminal programs ask for microphone, camera and Apple Events macOS attributes programs run in VMark's integrated terminal to VMark, so under the Hardened Runtime they can only ask for a protected resource the app declares. VMark declared none: FFmpeg recorded all-zero audio with no prompt (#1483), and the camera and osascript failed the same way. - src-tauri/app-entitlements.plist: the app bundle's own entitlements, adding audio-input, camera and automation.apple-events to the existing runtime exceptions. Info.plist gains the three usage descriptions, translated for the ten app locales in macos-l10n/.lproj/InfoPlist.strings. - The Tauri bundler re-signs every externalBin with the app's entitlements but copies bundle.macOS.files unsigned. The macOS release therefore places the MCP sidecar through tauri.macos-release.conf.json, keeping the signature release.yml gives it with sidecar-entitlements.plist (JIT exceptions only). The layout is read from the checked-out tree, so a manual release of an older tag still builds as before. - scripts/verify-macos-bundle-entitlements.sh checks the signed .app: in release.yml before publishing, and in release-smoke.yml on the published DMG from v0.9.89. - scripts/check-macos-tcc-entitlements.test.mjs pins the plists, the translations and the release wiring. Builds on the entitlement change from #1485. --- .claude/settings.json | 5 +- .github/workflows/release-smoke.yml | 25 ++- .github/workflows/release.yml | 58 +++++- scripts/check-macos-tcc-entitlements.test.mjs | 191 ++++++++++++++++++ scripts/verify-macos-bundle-entitlements.sh | 63 ++++++ src-tauri/Info.plist | 12 ++ src-tauri/app-entitlements.plist | 38 ++++ .../macos-l10n/de.lproj/InfoPlist.strings | 5 + .../macos-l10n/en.lproj/InfoPlist.strings | 5 + .../macos-l10n/es.lproj/InfoPlist.strings | 5 + .../macos-l10n/fr.lproj/InfoPlist.strings | 5 + .../macos-l10n/it.lproj/InfoPlist.strings | 5 + .../macos-l10n/ja.lproj/InfoPlist.strings | 5 + .../macos-l10n/ko.lproj/InfoPlist.strings | 5 + .../macos-l10n/pt-BR.lproj/InfoPlist.strings | 5 + .../zh-Hans.lproj/InfoPlist.strings | 5 + .../zh-Hant.lproj/InfoPlist.strings | 5 + src-tauri/sidecar-entitlements.plist | 3 - src-tauri/tauri.conf.json | 14 +- src-tauri/tauri.macos-release.conf.json | 10 + 20 files changed, 456 insertions(+), 13 deletions(-) create mode 100644 scripts/check-macos-tcc-entitlements.test.mjs create mode 100755 scripts/verify-macos-bundle-entitlements.sh create mode 100644 src-tauri/app-entitlements.plist create mode 100644 src-tauri/macos-l10n/de.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/en.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/es.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/fr.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/it.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/ja.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/ko.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/pt-BR.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/zh-Hans.lproj/InfoPlist.strings create mode 100644 src-tauri/macos-l10n/zh-Hant.lproj/InfoPlist.strings create mode 100644 src-tauri/tauri.macos-release.conf.json diff --git a/.claude/settings.json b/.claude/settings.json index a1cf62da6..be483fb9d 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -20,13 +20,12 @@ ] }, "enabledPlugins": { - "frontend-design@claude-code-plugins": true, "rust-analyzer-lsp@claude-plugins-official": true, - "cc-suite@xiaolai": true, "tdd-guardian@xiaolai": true, "claude-english-buddy@xiaolai": true, "docs-guardian@xiaolai": true, "typescript-lsp@claude-plugins-official": true, - "vmark-lsp@vmark-local": true + "vmark-lsp@vmark-local": true, + "frontend-design@claude-plugins-official": true } } diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml index 66da61465..7475bd304 100644 --- a/.github/workflows/release-smoke.yml +++ b/.github/workflows/release-smoke.yml @@ -179,8 +179,9 @@ jobs: - name: The bundled MCP sidecar reports its version shell: bash - # The sidecar is a Tauri externalBin (Contents/MacOS/vmark-mcp-server*), - # a CLI — so unlike the GUI app it can prove itself in headless CI. Its + # The sidecar (Contents/MacOS/vmark-mcp-server; an externalBin, except in + # the macOS release, which places it via bundle.macOS.files) is a CLI — + # so unlike the GUI app it can prove itself in headless CI. Its # --version must match the tag, and --health-check must pass: each has # failed silently in a shipped binary before elsewhere, and each looks # identical to success until someone runs it. @@ -200,6 +201,26 @@ jobs: } "$SIDECAR" --health-check + - name: The published bundle carries the intended entitlements + shell: bash + # Same check release.yml runs before publishing, now on the notarized, + # downloaded artifact: the app declares microphone, camera and Apple + # Events for its terminal (#1483); the sidecar carries none of them. + # Enforced from v0.9.89, the first release built this way; a manual run + # against an older tag would otherwise fail on what that tag never had. + timeout-minutes: 3 + env: + APP: ${{ steps.mount.outputs.app }} + TAG: ${{ steps.tag.outputs.tag }} + run: | + set -euo pipefail + FIRST=0.9.89 + if [ "$(printf '%s\n%s\n' "$FIRST" "${TAG#v}" | sort -V | head -n 1)" != "$FIRST" ]; then + echo "skip: $TAG predates the entitlement layout (first: v$FIRST)" + exit 0 + fi + scripts/verify-macos-bundle-entitlements.sh "$APP" + - name: Unmount if: always() && steps.mount.outputs.mount != '' shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d736045f8..2ea9cbbb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -183,6 +183,31 @@ jobs: # same commit the trigger already carried. ref: refs/tags/${{ needs.create-release.outputs.version }} + # Decided from the CHECKED-OUT tree, not from this workflow file: a manual + # dispatch runs today's workflow against an older tag. Trees that carry + # the overlay ship the pre-signed sidecar through bundle.macOS.files + # instead of externalBin, which the bundler would re-sign with the app's + # privacy entitlements (#1483), and are verified after the build. Trees + # from before it build the way they always did. Half a layout fails. + - name: Resolve macOS bundle layout + id: layout + if: matrix.platform == 'macos-latest' + run: | + set -euo pipefail + OVERLAY=src-tauri/tauri.macos-release.conf.json + VERIFIER=scripts/verify-macos-bundle-entitlements.sh + if [ -f "$OVERLAY" ] && [ -f "$VERIFIER" ]; then + echo "config=--config $OVERLAY" >> "$GITHUB_OUTPUT" + echo "verify=true" >> "$GITHUB_OUTPUT" + elif [ ! -e "$OVERLAY" ] && [ ! -e "$VERIFIER" ]; then + echo "::notice::This tag predates the sidecar entitlement layout (#1483): building with externalBin, no bundle verification." + echo "config=" >> "$GITHUB_OUTPUT" + echo "verify=false" >> "$GITHUB_OUTPUT" + else + echo "::error::Only one of $OVERLAY and $VERIFIER exists; the layout is half-applied." + exit 1 + fi + - name: Setup Node uses: actions/setup-node@v7 with: @@ -274,8 +299,13 @@ jobs: echo "Skipping full health check (cross-architecture build)" fi - # Pre-sign sidecar with JIT entitlements (required for pkg/Node.js binaries) - # This must happen BEFORE Tauri signs the app + # Sign the sidecar with ITS OWN entitlements (JIT exceptions for the + # pkg/Node.js binary, nothing else) and stage it where + # src-tauri/tauri.macos-release.conf.json places it in the bundle. The + # bundler copies files listed there without re-signing them, so this is + # the signature that ships; the app's microphone/camera/Apple Events + # entitlements stay on the app. Verified after the build by + # scripts/verify-macos-bundle-entitlements.sh. - name: Sign sidecar with JIT entitlements (macOS) if: matrix.platform == 'macos-latest' env: @@ -306,6 +336,12 @@ jobs: echo "Checking entitlements..." codesign -d --entitlements - "$SIDECAR_PATH" + # cp keeps the embedded signature; the overlay maps this fixed path + # to Contents/MacOS/vmark-mcp-server for either architecture. + mkdir -p src-tauri/binaries/macos-release + cp "$SIDECAR_PATH" src-tauri/binaries/macos-release/vmark-mcp-server + codesign --verify --strict src-tauri/binaries/macos-release/vmark-mcp-server + - name: Build MCP server sidecar (Windows) if: matrix.platform == 'windows-latest' working-directory: server/mcp @@ -370,12 +406,28 @@ jobs: CI: true with: releaseId: ${{ needs.create-release.outputs.release_id }} - args: ${{ matrix.args }} + # The layout config goes BEFORE the matrix's `--target`: the DMG step + # reads the target as ${MATRIX_ARGS##*--target }. The separating space + # lives here, not in the output value. Config is empty off macOS. + args: ${{ steps.layout.outputs.config }} ${{ matrix.args }} # Disable per-job latest.json upload to avoid race condition # We generate and upload it once in publish-release job # (renamed from includeUpdaterJson in tauri-action v1.0.0) uploadUpdaterJson: false + # The gate on what was signed, not on what the plists meant: the app must + # declare microphone, camera and Apple Events for its terminal (#1483) and + # the sidecar must carry none of them. Fails the job, so publish-release + # (which needs build-tauri) never promotes the draft. + - name: Verify bundle entitlements (macOS) + if: matrix.platform == 'macos-latest' && steps.layout.outputs.verify == 'true' + env: + MATRIX_ARGS: ${{ matrix.args }} + run: | + set -euo pipefail + TARGET="${MATRIX_ARGS##*--target }" + scripts/verify-macos-bundle-entitlements.sh "src-tauri/target/$TARGET/release/bundle/macos/VMark.app" + # tauri-action signs, notarizes and staples the .app, and signs the .dmg — # but it never notarizes the disk image itself. A signed-but-unnotarized # dmg is refused by `spctl -t open` with "Unnotarized Developer ID", so diff --git a/scripts/check-macos-tcc-entitlements.test.mjs b/scripts/check-macos-tcc-entitlements.test.mjs new file mode 100644 index 000000000..655f1ded5 --- /dev/null +++ b/scripts/check-macos-tcc-entitlements.test.mjs @@ -0,0 +1,191 @@ +/** + * The macOS app bundle must declare the privacy-protected resources that + * programs run in its integrated terminal ask for, and the MCP sidecar must not. + * + * VMark ships with the Hardened Runtime, and macOS treats the app as the + * "responsible process" for everything its terminal spawns. A resource the app + * never declared is denied WITHOUT a prompt: FFmpeg recording from VMark's + * terminal wrote all-zero samples and TCC logged the denial against `app.vmark` + * (#1483). The same held for the camera and for Apple Events (`osascript`), so + * the app declares all three — the subset of what iTerm2 and Ghostty declare + * that terminal programs realistically use. + * + * Each entitlement needs its usage description in Info.plist as well: with the + * entitlement but no description, macOS terminates the process on first access + * instead of asking the user. + * + * The sidecar (`vmark-mcp-server`) opens no device and scripts no app. The + * Tauri bundler re-signs every `externalBin` with the app's entitlements + * (crates/tauri-bundler `macos/app.rs`, `sign.rs`), but copies + * `bundle.macOS.files` without signing and signs the app without `--deep`. So + * the macOS release passes src-tauri/tauri.macos-release.conf.json, which + * places the sidecar release.yml pre-signed with sidecar-entitlements.plist. + * This file pins that wiring; scripts/verify-macos-bundle-entitlements.sh checks + * the signed artifact itself, in release.yml before publishing and in + * release-smoke.yml after. + */ +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { parse as parseYaml } from "yaml"; + +const ROOT = join(import.meta.dirname, ".."); +const read = (rel) => readFileSync(join(ROOT, rel), "utf8"); + +/** Keys whose value is `` in a flat plist dict. */ +function trueKeys(xml) { + const plain = xml.replace(//g, ""); + return [...plain.matchAll(/([^<]+)<\/key>\s*/g)].map((m) => m[1]); +} + +/** Keys whose value is a non-empty `` in a flat plist dict. */ +function stringKeys(xml) { + const plain = xml.replace(//g, ""); + return [...plain.matchAll(/([^<]+)<\/key>\s*([^<]*)<\/string>/g)] + .filter((m) => m[2].trim() !== "") + .map((m) => m[1]); +} + +/** Resource entitlement → the Info.plist usage description macOS requires with it. */ +const TCC_RESOURCES = { + "com.apple.security.device.audio-input": "NSMicrophoneUsageDescription", + "com.apple.security.device.camera": "NSCameraUsageDescription", + "com.apple.security.automation.apple-events": "NSAppleEventsUsageDescription", +}; + +/** Code-signing exceptions V8 needs, on both the app and the sidecar. */ +const RUNTIME_EXCEPTIONS = [ + "com.apple.security.cs.allow-jit", + "com.apple.security.cs.allow-unsigned-executable-memory", + "com.apple.security.cs.disable-library-validation", +]; + +const PRIVACY_PREFIXES = [ + "com.apple.security.device.", + "com.apple.security.automation.", + "com.apple.security.personal-information.", +]; +const isPrivacy = (k) => PRIVACY_PREFIXES.some((p) => k.startsWith(p)); + +const OVERLAY = "src-tauri/tauri.macos-release.conf.json"; +const STAGED = "binaries/macos-release/vmark-mcp-server"; + +const tauriConf = JSON.parse(read("src-tauri/tauri.conf.json")); +const appEntitlementsPath = tauriConf.bundle?.macOS?.entitlements; + +/** The build-tauri job's steps, parsed — so a commented-out step does not count. */ +const buildSteps = parseYaml(read(".github/workflows/release.yml")).jobs["build-tauri"].steps; +const stepIndex = (name) => { + const i = buildSteps.findIndex((s) => s.name === name); + expect(i, `release.yml build-tauri has no step named "${name}"`).toBeGreaterThanOrEqual(0); + return i; +}; +const step = (name) => buildSteps[stepIndex(name)]; + +describe("macOS TCC declarations for the integrated terminal", () => { + it("signs the app bundle with its own entitlements file, not the sidecar's", () => { + expect(appEntitlementsPath).toBe("app-entitlements.plist"); + }); + + it("declares every terminal-facing resource on the app, with its usage description", () => { + const app = trueKeys(read(join("src-tauri", appEntitlementsPath))); + const described = stringKeys(read("src-tauri/Info.plist")); + for (const [entitlement, description] of Object.entries(TCC_RESOURCES)) { + expect(app, `app entitlements lack ${entitlement}`).toContain(entitlement); + expect(described, `${entitlement} needs a non-empty ${description} in Info.plist`).toContain(description); + } + }); + + it("gives the app exactly these privacy entitlements, plus the runtime exceptions", () => { + const app = trueKeys(read(join("src-tauri", appEntitlementsPath))); + expect(app.filter(isPrivacy).sort()).toEqual(Object.keys(TCC_RESOURCES).sort()); + for (const key of RUNTIME_EXCEPTIONS) expect(app).toContain(key); + }); + + it("keeps the sidecar's own entitlements to the runtime exceptions", () => { + const sidecar = trueKeys(read("src-tauri/sidecar-entitlements.plist")); + expect(sidecar.filter(isPrivacy)).toEqual([]); + for (const key of RUNTIME_EXCEPTIONS) expect(sidecar).toContain(key); + }); +}); + +describe("the macOS release ships the sidecar with its own signature", () => { + it("keeps externalBin for dev, Windows and Linux", () => { + expect(tauriConf.bundle.externalBin).toContain("binaries/vmark-mcp-server"); + }); + + it("the release overlay drops externalBin and places the pre-signed sidecar via files", () => { + const overlay = JSON.parse(read(OVERLAY)); + expect(overlay.bundle.externalBin).toEqual([]); + expect(overlay.bundle.macOS.files).toEqual({ "MacOS/vmark-mcp-server": STAGED }); + }); + + it("resolves the layout from the checked-out tree, on macOS, right after checkout", () => { + const layout = step("Resolve macOS bundle layout"); + expect(layout.id).toBe("layout"); + expect(layout.if).toBe("matrix.platform == 'macos-latest'"); + expect(stepIndex("Resolve macOS bundle layout")).toBe(stepIndex("Checkout") + 1); + expect(layout.run).toContain(`OVERLAY=${OVERLAY}`); + expect(layout.run).toContain("VERIFIER=scripts/verify-macos-bundle-entitlements.sh"); + expect(layout.run).toContain('echo "config=--config $OVERLAY" >> "$GITHUB_OUTPUT"'); + }); + + it("passes the layout config to the build before the matrix's --target", () => { + expect(step("Build Tauri app").with.args).toBe("${{ steps.layout.outputs.config }} ${{ matrix.args }}"); + }); + + it("signs the sidecar with sidecar-entitlements.plist and stages it where the overlay reads it", () => { + const sign = step("Sign sidecar with JIT entitlements (macOS)"); + expect(sign.run).toContain("--entitlements src-tauri/sidecar-entitlements.plist"); + expect(sign.run).toContain(`cp "$SIDECAR_PATH" src-tauri/${STAGED}`); + expect(stepIndex("Sign sidecar with JIT entitlements (macOS)")).toBeLessThan(stepIndex("Build Tauri app")); + }); + + it("verifies the signed bundle after the build and before the DMG is notarized", () => { + const verify = step("Verify bundle entitlements (macOS)"); + expect(verify.if).toBe("matrix.platform == 'macos-latest' && steps.layout.outputs.verify == 'true'"); + const commands = verify.run.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")); + expect(commands).toContain('scripts/verify-macos-bundle-entitlements.sh "src-tauri/target/$TARGET/release/bundle/macos/VMark.app"'); + const at = stepIndex("Verify bundle entitlements (macOS)"); + expect(at).toBeGreaterThan(stepIndex("Build Tauri app")); + expect(at).toBeLessThan(stepIndex("Notarize and staple DMG (macOS)")); + }); +}); + +/** App locale (src/locales) → the macOS .lproj that localizes Info.plist for it. */ +const LPROJ = { en: "en", de: "de", es: "es", fr: "fr", it: "it", ja: "ja", ko: "ko", "pt-BR": "pt-BR", "zh-CN": "zh-Hans", "zh-TW": "zh-Hant" }; + +/** `"key" = "value";` pairs of an .strings file. */ +function stringsPairs(text) { + return Object.fromEntries([...text.matchAll(/^"([^"]+)"\s*=\s*"((?:[^"\\]|\\.)*)";\s*$/gm)].map((m) => [m[1], m[2]])); +} + +describe("the permission prompts are localized like the app", () => { + const appLocales = readdirSync(join(ROOT, "src/locales"), { withFileTypes: true }) + .filter((d) => d.isDirectory() && !d.name.startsWith("__")) + .map((d) => d.name) + .sort(); + const files = tauriConf.bundle.macOS.files; + + it("maps every app locale to an lproj", () => { + expect(appLocales).toEqual(Object.keys(LPROJ).sort()); + }); + + it.each(Object.entries(LPROJ))("%s: bundles InfoPlist.strings with all three descriptions", (_locale, lproj) => { + const source = files[`Resources/${lproj}.lproj/InfoPlist.strings`]; + expect(source, `bundle.macOS.files lacks Resources/${lproj}.lproj/InfoPlist.strings`).toBeTruthy(); + const pairs = stringsPairs(read(join("src-tauri", source))); + for (const description of Object.values(TCC_RESOURCES)) { + expect(pairs[description]?.trim(), `${lproj}: ${description}`).toBeTruthy(); + } + }); + + it("the English strings match Info.plist word for word", () => { + const plist = read("src-tauri/Info.plist").replace(//g, ""); + const en = stringsPairs(read(join("src-tauri", files["Resources/en.lproj/InfoPlist.strings"]))); + for (const description of Object.values(TCC_RESOURCES)) { + const m = plist.match(new RegExp(`${description}\\s*([^<]*)`)); + expect(en[description]).toBe(m[1].replace(/'/g, "'")); + } + }); +}); diff --git a/scripts/verify-macos-bundle-entitlements.sh b/scripts/verify-macos-bundle-entitlements.sh new file mode 100755 index 000000000..3e996d982 --- /dev/null +++ b/scripts/verify-macos-bundle-entitlements.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Verify the entitlements a built VMark.app actually carries — the signed +# artifact, not the plists that were meant to produce it. +# +# Usage: scripts/verify-macos-bundle-entitlements.sh +# +# The app must declare the privacy resources that programs run in its +# integrated terminal ask for (microphone, camera, Apple Events; #1483), and the +# MCP sidecar at Contents/MacOS/vmark-mcp-server must carry none of them. The +# Tauri bundler re-signs every `externalBin` with the app's entitlements, so the +# macOS release ships the sidecar through `bundle.macOS.files` instead +# (src-tauri/tauri.macos-release.conf.json), keeping the signature release.yml +# gave it with src-tauri/sidecar-entitlements.plist. A change that brought back +# the re-sign would look identical to success; this is what notices. +# +# Fails closed: a missing binary, an unreadable signature or any mismatch exits 1. +set -euo pipefail + +APP="${1:?usage: $0 }" +SIDECAR="$APP/Contents/MacOS/vmark-mcp-server" + +fail() { echo "FAIL: $*" >&2; exit 1; } + +[ -d "$APP" ] || fail "no app bundle at $APP" +[ -x "$SIDECAR" ] || fail "no executable sidecar at $SIDECAR" + +# Keys set to true in a binary's signed entitlements, one per line. +entitlements() { + local xml + xml="$(codesign -d --entitlements - --xml "$1" 2>/dev/null)" || fail "cannot read the signature of $1" + [ -n "$xml" ] || fail "$1 is signed without entitlements" + printf '%s' "$xml" | plutil -convert json -o - - | /usr/bin/python3 -c \ + 'import json,sys; print("\n".join(k for k,v in json.load(sys.stdin).items() if v is True))' +} + +APP_KEYS="$(entitlements "$APP")" +SIDECAR_KEYS="$(entitlements "$SIDECAR")" + +for key in \ + com.apple.security.device.audio-input \ + com.apple.security.device.camera \ + com.apple.security.automation.apple-events; do + grep -qxF "$key" <<<"$APP_KEYS" || fail "app lacks $key" +done + +for key in \ + com.apple.security.cs.allow-jit \ + com.apple.security.cs.allow-unsigned-executable-memory \ + com.apple.security.cs.disable-library-validation; do + grep -qxF "$key" <<<"$APP_KEYS" || fail "app lacks $key" + grep -qxF "$key" <<<"$SIDECAR_KEYS" || fail "sidecar lacks $key (V8 needs it)" +done + +PRIVACY="$(grep -E '^com\.apple\.security\.(device|automation|personal-information)\.' <<<"$SIDECAR_KEYS" || true)" +[ -z "$PRIVACY" ] || fail "sidecar carries privacy entitlements it never uses: $(echo "$PRIVACY" | tr '\n' ' ')" + +# Captured first: `codesign | grep -q` under pipefail fails whenever grep exits +# on its first match and codesign dies of SIGPIPE. +SIDECAR_SIG="$(codesign -dv "$SIDECAR" 2>&1)" || fail "cannot read the signature of $SIDECAR" +grep -q 'flags=.*runtime' <<<"$SIDECAR_SIG" || fail "sidecar is not signed with the hardened runtime" +codesign --verify --deep --strict "$APP" || fail "codesign --verify --deep --strict rejected $APP" + +echo "OK: $APP — app declares microphone, camera and Apple Events; sidecar keeps only its runtime exceptions" diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist index 323b8f45e..2e153adcc 100644 --- a/src-tauri/Info.plist +++ b/src-tauri/Info.plist @@ -16,10 +16,22 @@ Tauri's own CFBundleIconFile (icons/icon.icns) stays and is not redundant: macOS 26 prefers the catalogue, and everything older ignores CFBundleIconName entirely and falls back to the .icns. + + The NS*UsageDescription strings pair with the privacy entitlements in + app-entitlements.plist: macOS attributes programs run in the integrated + terminal to VMark and shows these strings when one of them asks for the + microphone, the camera or Apple Events (#1483). Without a string, macOS + terminates the program on first access instead of asking. Translations live + in macos-l10n/.lproj/InfoPlist.strings, one per app locale, copied into + Contents/Resources through bundle.macOS.files. --> NSMicrophoneUsageDescription Allow command-line tools you run in VMark's integrated terminal to record audio from your microphone or other audio input devices. + NSCameraUsageDescription + Allow command-line tools you run in VMark's integrated terminal to capture video from your camera. + NSAppleEventsUsageDescription + Allow command-line tools you run in VMark's integrated terminal, such as osascript, to control other apps. CFBundleVersion CFBundleIconName diff --git a/src-tauri/app-entitlements.plist b/src-tauri/app-entitlements.plist new file mode 100644 index 000000000..d687e50c5 --- /dev/null +++ b/src-tauri/app-entitlements.plist @@ -0,0 +1,38 @@ + + + + + + + com.apple.security.device.audio-input + + + com.apple.security.device.camera + + + com.apple.security.automation.apple-events + + + com.apple.security.cs.allow-jit + + + com.apple.security.cs.allow-unsigned-executable-memory + + + com.apple.security.cs.disable-library-validation + + + diff --git a/src-tauri/macos-l10n/de.lproj/InfoPlist.strings b/src-tauri/macos-l10n/de.lproj/InfoPlist.strings new file mode 100644 index 000000000..d9671c748 --- /dev/null +++ b/src-tauri/macos-l10n/de.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Erlaubt Befehlszeilenprogrammen, die Sie im integrierten Terminal von VMark ausführen, Audio über Ihr Mikrofon oder andere Audioeingänge aufzunehmen."; +"NSCameraUsageDescription" = "Erlaubt Befehlszeilenprogrammen, die Sie im integrierten Terminal von VMark ausführen, Video über Ihre Kamera aufzunehmen."; +"NSAppleEventsUsageDescription" = "Erlaubt Befehlszeilenprogrammen, die Sie im integrierten Terminal von VMark ausführen, etwa osascript, andere Apps zu steuern."; diff --git a/src-tauri/macos-l10n/en.lproj/InfoPlist.strings b/src-tauri/macos-l10n/en.lproj/InfoPlist.strings new file mode 100644 index 000000000..f4118ba5e --- /dev/null +++ b/src-tauri/macos-l10n/en.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Allow command-line tools you run in VMark's integrated terminal to record audio from your microphone or other audio input devices."; +"NSCameraUsageDescription" = "Allow command-line tools you run in VMark's integrated terminal to capture video from your camera."; +"NSAppleEventsUsageDescription" = "Allow command-line tools you run in VMark's integrated terminal, such as osascript, to control other apps."; diff --git a/src-tauri/macos-l10n/es.lproj/InfoPlist.strings b/src-tauri/macos-l10n/es.lproj/InfoPlist.strings new file mode 100644 index 000000000..a8f922da6 --- /dev/null +++ b/src-tauri/macos-l10n/es.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Permite que las herramientas de línea de comandos que ejecutas en el terminal integrado de VMark graben audio del micrófono u otros dispositivos de entrada de audio."; +"NSCameraUsageDescription" = "Permite que las herramientas de línea de comandos que ejecutas en el terminal integrado de VMark capturen vídeo de la cámara."; +"NSAppleEventsUsageDescription" = "Permite que las herramientas de línea de comandos que ejecutas en el terminal integrado de VMark, como osascript, controlen otras apps."; diff --git a/src-tauri/macos-l10n/fr.lproj/InfoPlist.strings b/src-tauri/macos-l10n/fr.lproj/InfoPlist.strings new file mode 100644 index 000000000..462497001 --- /dev/null +++ b/src-tauri/macos-l10n/fr.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Autorise les outils en ligne de commande que vous lancez dans le terminal intégré de VMark à enregistrer l'audio de votre micro ou d'autres entrées audio."; +"NSCameraUsageDescription" = "Autorise les outils en ligne de commande que vous lancez dans le terminal intégré de VMark à capturer la vidéo de votre caméra."; +"NSAppleEventsUsageDescription" = "Autorise les outils en ligne de commande que vous lancez dans le terminal intégré de VMark, comme osascript, à contrôler d'autres apps."; diff --git a/src-tauri/macos-l10n/it.lproj/InfoPlist.strings b/src-tauri/macos-l10n/it.lproj/InfoPlist.strings new file mode 100644 index 000000000..5ceb40ec2 --- /dev/null +++ b/src-tauri/macos-l10n/it.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Consente agli strumenti da riga di comando che esegui nel terminale integrato di VMark di registrare l'audio dal microfono o da altri dispositivi di ingresso audio."; +"NSCameraUsageDescription" = "Consente agli strumenti da riga di comando che esegui nel terminale integrato di VMark di acquisire video dalla fotocamera."; +"NSAppleEventsUsageDescription" = "Consente agli strumenti da riga di comando che esegui nel terminale integrato di VMark, come osascript, di controllare altre app."; diff --git a/src-tauri/macos-l10n/ja.lproj/InfoPlist.strings b/src-tauri/macos-l10n/ja.lproj/InfoPlist.strings new file mode 100644 index 000000000..6d67f4515 --- /dev/null +++ b/src-tauri/macos-l10n/ja.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "VMark の統合ターミナルで実行するコマンドラインツールが、マイクやその他のオーディオ入力装置から録音することを許可します。"; +"NSCameraUsageDescription" = "VMark の統合ターミナルで実行するコマンドラインツールが、カメラから映像を取り込むことを許可します。"; +"NSAppleEventsUsageDescription" = "VMark の統合ターミナルで実行する osascript などのコマンドラインツールが、他のアプリを操作することを許可します。"; diff --git a/src-tauri/macos-l10n/ko.lproj/InfoPlist.strings b/src-tauri/macos-l10n/ko.lproj/InfoPlist.strings new file mode 100644 index 000000000..28ca1c997 --- /dev/null +++ b/src-tauri/macos-l10n/ko.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "VMark 통합 터미널에서 실행하는 명령줄 도구가 마이크 또는 기타 오디오 입력 장치에서 오디오를 녹음하도록 허용합니다."; +"NSCameraUsageDescription" = "VMark 통합 터미널에서 실행하는 명령줄 도구가 카메라에서 비디오를 캡처하도록 허용합니다."; +"NSAppleEventsUsageDescription" = "VMark 통합 터미널에서 실행하는 osascript 같은 명령줄 도구가 다른 앱을 제어하도록 허용합니다."; diff --git a/src-tauri/macos-l10n/pt-BR.lproj/InfoPlist.strings b/src-tauri/macos-l10n/pt-BR.lproj/InfoPlist.strings new file mode 100644 index 000000000..64ff3f0bb --- /dev/null +++ b/src-tauri/macos-l10n/pt-BR.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "Permite que as ferramentas de linha de comando que você executa no terminal integrado do VMark gravem áudio do microfone ou de outros dispositivos de entrada de áudio."; +"NSCameraUsageDescription" = "Permite que as ferramentas de linha de comando que você executa no terminal integrado do VMark capturem vídeo da câmera."; +"NSAppleEventsUsageDescription" = "Permite que as ferramentas de linha de comando que você executa no terminal integrado do VMark, como o osascript, controlem outros apps."; diff --git a/src-tauri/macos-l10n/zh-Hans.lproj/InfoPlist.strings b/src-tauri/macos-l10n/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 000000000..4177b630a --- /dev/null +++ b/src-tauri/macos-l10n/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "允许你在 VMark 集成终端中运行的命令行工具通过麦克风或其他音频输入设备录音。"; +"NSCameraUsageDescription" = "允许你在 VMark 集成终端中运行的命令行工具通过摄像头拍摄视频。"; +"NSAppleEventsUsageDescription" = "允许你在 VMark 集成终端中运行的命令行工具(例如 osascript)控制其他应用。"; diff --git a/src-tauri/macos-l10n/zh-Hant.lproj/InfoPlist.strings b/src-tauri/macos-l10n/zh-Hant.lproj/InfoPlist.strings new file mode 100644 index 000000000..260433fd7 --- /dev/null +++ b/src-tauri/macos-l10n/zh-Hant.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Privacy prompts for programs run in VMark's integrated terminal (#1483). + Keep in step with src-tauri/Info.plist; scripts/check-macos-tcc-entitlements.test.mjs checks both. */ +"NSMicrophoneUsageDescription" = "允許你在 VMark 整合式終端機中執行的命令列工具透過麥克風或其他音訊輸入裝置錄音。"; +"NSCameraUsageDescription" = "允許你在 VMark 整合式終端機中執行的命令列工具透過相機拍攝影片。"; +"NSAppleEventsUsageDescription" = "允許你在 VMark 整合式終端機中執行的命令列工具(例如 osascript)控制其他 App。"; diff --git a/src-tauri/sidecar-entitlements.plist b/src-tauri/sidecar-entitlements.plist index 769590be4..d7445c344 100644 --- a/src-tauri/sidecar-entitlements.plist +++ b/src-tauri/sidecar-entitlements.plist @@ -2,9 +2,6 @@ - - com.apple.security.device.audio-input - com.apple.security.cs.allow-jit diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5dcc3ac0d..1b2bddd55 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -105,10 +105,20 @@ "resources/workflows/**/*" ], "macOS": { - "entitlements": "sidecar-entitlements.plist", + "entitlements": "app-entitlements.plist", "minimumSystemVersion": "13.4", "files": { - "Resources/Assets.car": "icons/Assets.car" + "Resources/Assets.car": "icons/Assets.car", + "Resources/en.lproj/InfoPlist.strings": "macos-l10n/en.lproj/InfoPlist.strings", + "Resources/de.lproj/InfoPlist.strings": "macos-l10n/de.lproj/InfoPlist.strings", + "Resources/es.lproj/InfoPlist.strings": "macos-l10n/es.lproj/InfoPlist.strings", + "Resources/fr.lproj/InfoPlist.strings": "macos-l10n/fr.lproj/InfoPlist.strings", + "Resources/it.lproj/InfoPlist.strings": "macos-l10n/it.lproj/InfoPlist.strings", + "Resources/ja.lproj/InfoPlist.strings": "macos-l10n/ja.lproj/InfoPlist.strings", + "Resources/ko.lproj/InfoPlist.strings": "macos-l10n/ko.lproj/InfoPlist.strings", + "Resources/pt-BR.lproj/InfoPlist.strings": "macos-l10n/pt-BR.lproj/InfoPlist.strings", + "Resources/zh-Hans.lproj/InfoPlist.strings": "macos-l10n/zh-Hans.lproj/InfoPlist.strings", + "Resources/zh-Hant.lproj/InfoPlist.strings": "macos-l10n/zh-Hant.lproj/InfoPlist.strings" } }, "windows": { diff --git a/src-tauri/tauri.macos-release.conf.json b/src-tauri/tauri.macos-release.conf.json new file mode 100644 index 000000000..7496b1d64 --- /dev/null +++ b/src-tauri/tauri.macos-release.conf.json @@ -0,0 +1,10 @@ +{ + "bundle": { + "externalBin": [], + "macOS": { + "files": { + "MacOS/vmark-mcp-server": "binaries/macos-release/vmark-mcp-server" + } + } + } +} From bf61e52e61427f9e8ac28c219e81bb01ac271332 Mon Sep 17 00:00:00 2001 From: xiaolai Date: Wed, 30 Sep 2026 12:51:39 +0800 Subject: [PATCH 3/3] docs(terminal): explain microphone, camera and Apple Events prompts on macOS Replaces the audio-only section from #1485 with one covering all three resources, where to change the permission, and what a silent denial looks like, in English and the nine translated guides. --- website/de/guide/terminal.md | 8 ++++++++ website/es/guide/terminal.md | 8 ++++++++ website/fr/guide/terminal.md | 8 ++++++++ website/guide/terminal.md | 21 +++++++-------------- website/it/guide/terminal.md | 8 ++++++++ website/ja/guide/terminal.md | 8 ++++++++ website/ko/guide/terminal.md | 8 ++++++++ website/pt-BR/guide/terminal.md | 8 ++++++++ website/zh-CN/guide/terminal.md | 8 ++++++++ website/zh-TW/guide/terminal.md | 8 ++++++++ 10 files changed, 79 insertions(+), 14 deletions(-) diff --git a/website/de/guide/terminal.md b/website/de/guide/terminal.md index db320cee7..d0a916e16 100644 --- a/website/de/guide/terminal.md +++ b/website/de/guide/terminal.md @@ -193,6 +193,14 @@ Standard-Shell-Kürzel wie `Strg+R` (Rückwärtshistorie-Suche in zsh/bash) funk Wenn sich das Arbeitsbereichsstammverzeichnis ändert, nachdem das Terminal bereits läuft, wechseln untätige Sitzungen automatisch per `cd` zum neuen Stammverzeichnis. Eine Sitzung, die mit einem Befehl beschäftigt ist (etwa `vim` oder `less`), wird nicht unterbrochen: Sie wechselt das Verzeichnis, sobald der Befehl beendet ist, was [Shell-Integration](#shell-integration) zur Erkennung voraussetzt. Mit eingeschalteter [Workspace-Leiste](/de/guide/workspace-rail) behalten Sitzungen, die zu einem Arbeitsbereich gehören, ihr eigenes Verzeichnis. +## Mikrofon, Kamera und Apple Events unter macOS + +Programme, die Sie im integrierten Terminal ausführen, können das Mikrofon, die Kamera oder die Erlaubnis anfordern, andere Apps zu steuern (Apple Events, die `osascript` verwendet). macOS fragt im Namen von VMark, weil es VMark als verantwortliche App für alles behandelt, was das Terminal startet. Erlauben Sie den Zugriff, wenn macOS fragt; ändern können Sie ihn später unter **Systemeinstellungen → Datenschutz & Sicherheit** bei **Mikrofon**, **Kamera** oder **Automation**. Die Anfrage erfolgt, wenn ein Programm die Ressource zum ersten Mal nutzt, nicht beim Öffnen des Terminals. + +Wenn ein Programm nur Stille aufnimmt, ein schwarzes Bild liefert oder einen „nicht autorisiert“-Fehler meldet, ohne dass eine Anfrage erscheint, prüfen Sie auf der jeweiligen Einstellungsseite, ob VMark aufgeführt und erlaubt ist. Fügen Sie einer Fehlermeldung die Ausgabe des Programms bei. + +Bei virtuellen Audioeingängen wie BlackHole leitet die Berechtigung allein noch kein Audio weiter. Wählen Sie im Aufnahmeprogramm den gewünschten Eingang, leiten Sie das Audio dorthin und prüfen Sie vor einer längeren Sitzung eine kurze Aufnahme: Eine wachsende Audiodatei allein beweist nicht, dass Ton aufgezeichnet wurde. VMark enthält weder Rekorder noch Transkription; diese Befehle stammen aus Werkzeugen, die Sie separat installieren. + ## Noch nicht implementiert Diese Punkte sind vorgemerkt, werden aber heute **nicht** ausgeliefert. Sie sind diff --git a/website/es/guide/terminal.md b/website/es/guide/terminal.md index b8c5957a2..26cbcf752 100644 --- a/website/es/guide/terminal.md +++ b/website/es/guide/terminal.md @@ -189,6 +189,14 @@ Los atajos de shell estándar como `Ctrl+R` (búsqueda inversa del historial en Cuando la raíz del espacio de trabajo cambia después de que el terminal ya está en ejecución, las sesiones inactivas cambian automáticamente su directorio a la nueva raíz mediante `cd`. Una sesión ocupada con un comando (por ejemplo, `vim` o `less`) no se interrumpe: cambia de directorio cuando el comando termina, lo que requiere la [integración con el shell](#integracion-con-el-shell) para detectarlo. Con la [barra de espacios de trabajo](/es/guide/workspace-rail) activada, las sesiones que pertenecen a un espacio de trabajo conservan su propio directorio. +## Micrófono, cámara y Apple Events en macOS + +Los programas que ejecutas en el terminal integrado pueden solicitar el micrófono, la cámara o permiso para controlar otras apps (Apple Events, que usa `osascript`). macOS pregunta en nombre de VMark, porque considera a VMark la app responsable de todo lo que inicia su terminal. Permite el acceso cuando macOS lo pregunte; puedes cambiarlo más adelante en **Ajustes del Sistema → Privacidad y seguridad**, en **Micrófono**, **Cámara** o **Automatización**. La solicitud se produce cuando un programa usa el recurso por primera vez, no al abrir el terminal. + +Si un programa graba silencio, captura un fotograma negro o muestra un error de «no autorizado» sin que aparezca ninguna solicitud, comprueba en la página de ajustes correspondiente que VMark aparece en la lista y está permitido. Al informar del problema, incluye la salida del programa. + +Con entradas de audio virtuales como BlackHole, el permiso por sí solo no enruta el audio. Selecciona la entrada deseada en tu herramienta de grabación, enruta el audio hacia ella y verifica una grabación corta antes de una sesión larga: que un archivo de audio crezca no demuestra por sí solo que se haya capturado sonido. VMark no incluye grabadora ni transcriptor; esos comandos los proporcionan herramientas que instalas por separado. + ## Aún no implementado Estas funciones están registradas pero **no** se incluyen hoy. Se enumeran aquí diff --git a/website/fr/guide/terminal.md b/website/fr/guide/terminal.md index 0cb7f3a4b..25a9c6906 100644 --- a/website/fr/guide/terminal.md +++ b/website/fr/guide/terminal.md @@ -192,6 +192,14 @@ Les raccourcis shell standard comme `Ctrl+R` (recherche d'historique inversée d Lorsque la racine de l'espace de travail change alors que le terminal est déjà en cours d'exécution, les sessions inactives effectuent automatiquement `cd` vers la nouvelle racine. Une session occupée par une commande (par exemple `vim` ou `less`) n'est pas interrompue : elle change de répertoire une fois la commande terminée, ce qui nécessite l'[intégration du shell](#integration-du-shell) pour être détecté. Avec la [barre des espaces de travail](/fr/guide/workspace-rail) activée, les sessions qui appartiennent à un espace de travail conservent leur propre répertoire. +## Micro, caméra et Apple Events sur macOS + +Les programmes que vous lancez dans le terminal intégré peuvent demander le micro, la caméra ou l'autorisation de contrôler d'autres apps (Apple Events, utilisés par `osascript`). macOS pose la question au nom de VMark, car il considère VMark comme l'app responsable de tout ce que le terminal lance. Autorisez l'accès lorsque macOS le demande ; vous pourrez le modifier plus tard dans **Réglages Système → Confidentialité et sécurité**, sous **Microphone**, **Caméra** ou **Automatisation**. La demande a lieu lorsqu'un programme utilise la ressource pour la première fois, pas à l'ouverture du terminal. + +Si un programme enregistre du silence, capture une image noire ou signale une erreur « non autorisé » sans qu'aucune demande n'apparaisse, vérifiez que VMark figure dans la page de réglages correspondante et qu'il y est autorisé. Joignez la sortie du programme lorsque vous signalez le problème. + +Pour les entrées audio virtuelles comme BlackHole, l'autorisation seule n'achemine pas le son. Choisissez l'entrée voulue dans votre outil d'enregistrement, acheminez-y le son et vérifiez un court enregistrement avant une longue session : un fichier audio qui grossit ne prouve pas à lui seul que du son a été capté. VMark n'inclut ni enregistreur ni outil de transcription ; ces commandes proviennent d'outils que vous installez séparément. + ## Pas encore implémenté Ces éléments sont suivis mais ne sont **pas** disponibles aujourd'hui. Ils sont diff --git a/website/guide/terminal.md b/website/guide/terminal.md index cd5de251b..3fcd8f369 100644 --- a/website/guide/terminal.md +++ b/website/guide/terminal.md @@ -186,20 +186,13 @@ Standard shell shortcuts like `Ctrl+R` (reverse history search in zsh/bash) work When the workspace root changes after the terminal is already running, idle sessions automatically `cd` to the new root. A session busy with a command (say, `vim` or `less`) is not interrupted: it changes directory once the command finishes, which needs [shell integration](#shell-integration) to detect. With the [workspace rail](/guide/workspace-rail) on, sessions that belong to a workspace keep their own directory. -## Audio input on macOS - -Recording tools running in the integrated terminal may request microphone -permission under VMark's name. Allow access when macOS asks. If VMark is listed in -**System Settings → Privacy & Security → Microphone**, you can manage its access -there. If no prompt or entry appears, collect the recording tool's output and -macOS permission logs when reporting the issue. Permission is requested when a -tool accesses audio input, not simply when you open the terminal. - -For virtual audio inputs such as BlackHole, permission alone does not establish -audio routing. Select the intended input in your recording tool, route audio to it, and verify -a short recording before a longer session. A growing audio file alone does not -confirm that sound was captured. VMark does not include a recorder or transcriber; -those commands are provided by tools you install separately. +## Microphone, camera and Apple Events on macOS + +Programs you run in the integrated terminal can ask for the microphone, the camera, or permission to control other apps (Apple Events, used by `osascript`). macOS asks under VMark's name, because it treats VMark as the app responsible for everything its terminal starts. Allow access when macOS asks; you can change it later in **System Settings → Privacy & Security** under **Microphone**, **Camera** or **Automation**. The request happens when a program first uses the resource, not when you open the terminal. + +If a program gets silent audio, a black frame or a "not authorized" error and no prompt appears, check whether VMark is listed and allowed in that settings page. When reporting the problem, include the program's output. + +For virtual audio inputs such as BlackHole, permission alone does not route audio. Select the intended input in your recording tool, route audio to it, and verify a short recording before a longer session: a growing audio file does not by itself prove that sound was captured. VMark does not include a recorder or transcriber; those come from tools you install separately. ## Not yet implemented diff --git a/website/it/guide/terminal.md b/website/it/guide/terminal.md index 4d0ae0449..34749cf01 100644 --- a/website/it/guide/terminal.md +++ b/website/it/guide/terminal.md @@ -191,6 +191,14 @@ Le scorciatoie shell standard come `Ctrl+R` (ricerca cronologia inversa in zsh/b Quando la radice del workspace cambia dopo che il terminale è già in esecuzione, le sessioni inattive eseguono automaticamente `cd` alla nuova radice. Una sessione impegnata in un comando (ad esempio `vim` o `less`) non viene interrotta: cambia directory quando il comando termina, cosa che richiede l'[integrazione della shell](#integrazione-della-shell) per essere rilevata. Con la [barra degli spazi di lavoro](/it/guide/workspace-rail) attiva, le sessioni che appartengono a un workspace mantengono la propria directory. +## Microfono, fotocamera e Apple Events su macOS + +I programmi che esegui nel terminale integrato possono richiedere il microfono, la fotocamera o il permesso di controllare altre app (Apple Events, usati da `osascript`). macOS chiede a nome di VMark, perché considera VMark l'app responsabile di tutto ciò che il terminale avvia. Consenti l'accesso quando macOS lo chiede; potrai modificarlo in seguito in **Impostazioni di Sistema → Privacy e sicurezza**, alla voce **Microfono**, **Fotocamera** o **Automazione**. La richiesta avviene quando un programma usa la risorsa per la prima volta, non all'apertura del terminale. + +Se un programma registra silenzio, cattura un fotogramma nero o segnala un errore di tipo "non autorizzato" senza che compaia alcuna richiesta, verifica nella pagina delle impostazioni corrispondente che VMark sia elencato e consentito. Quando segnali il problema, allega l'output del programma. + +Per gli ingressi audio virtuali come BlackHole, il permesso da solo non instrada l'audio. Seleziona l'ingresso desiderato nello strumento di registrazione, instrada l'audio verso di esso e verifica una breve registrazione prima di una sessione lunga: un file audio che cresce non dimostra da solo che il suono sia stato catturato. VMark non include registratori né strumenti di trascrizione; questi comandi provengono da strumenti che installi separatamente. + ## Non ancora implementato Queste funzioni sono pianificate ma **non** sono disponibili oggi. Sono elencate qui diff --git a/website/ja/guide/terminal.md b/website/ja/guide/terminal.md index c5c77308a..3dee51536 100644 --- a/website/ja/guide/terminal.md +++ b/website/ja/guide/terminal.md @@ -153,6 +153,14 @@ VMark は意図的に `EDITOR` を**設定しません**。`git commit`、`cront ターミナルがすでに実行中の状態でワークスペースのルートが変わると、アイドル状態のセッションが自動的に新しいルートに`cd`します。コマンド(`vim`や`less`など)を実行中のセッションは中断されず、コマンドが終了した時点でディレクトリを変更します。これを検出するには[シェル統合](#シェル統合)が必要です。[ワークスペースレール](/ja/guide/workspace-rail)がオンの場合、ワークスペースに属するセッションは独自のディレクトリを保持します。 +## macOS でのマイク、カメラ、Apple イベント + +統合ターミナルで実行したプログラムは、マイク、カメラ、または他のアプリを操作する権限(Apple イベント。`osascript` が使用します)を要求できます。macOS は VMark の名前で確認します。ターミナルが起動するすべてのプログラムについて、VMark を責任のあるアプリとして扱うためです。macOS に確認されたら許可してください。あとから**システム設定 → プライバシーとセキュリティ**の**マイク**、**カメラ**、**オートメーション**で変更できます。要求はプログラムがそのリソースを初めて使うときに行われ、ターミナルを開いただけでは行われません。 + +プログラムの録音が無音になる、映像が真っ黒になる、または「許可されていません」というエラーが出るのに確認が表示されない場合は、該当する設定ページで VMark が一覧にあり、許可されているかを確認してください。問題を報告するときは、そのプログラムの出力を添えてください。 + +BlackHole などの仮想オーディオ入力では、権限だけでは音声はルーティングされません。録音ツールで目的の入力を選び、そこへ音声をルーティングし、長時間の録音の前に短い録音で確認してください。音声ファイルが大きくなっていても、それだけでは音が録れている証拠になりません。VMark には録音や文字起こしの機能は含まれていません。それらのコマンドは別途インストールしたツールが提供します。 + ## 未実装の機能 以下は検討中の項目ですが、現時点では**提供されていません**。このページの以前の版で、その一部がすでに提供されているかのように説明されていたため、ここに記載しています: diff --git a/website/ko/guide/terminal.md b/website/ko/guide/terminal.md index 18f8bf89c..21265e29d 100644 --- a/website/ko/guide/terminal.md +++ b/website/ko/guide/terminal.md @@ -183,6 +183,14 @@ VMark는 의도적으로 `EDITOR`를 **설정하지 않습니다**. `git commit` 터미널이 이미 실행 중일 때 워크스페이스 루트가 바뀌면 유휴 상태의 세션이 자동으로 새 루트로 `cd`합니다. 명령 (예: `vim`이나 `less`)을 실행 중인 세션은 중단되지 않으며, 명령이 끝난 뒤 디렉터리를 변경합니다. 이를 감지하려면 [셸 통합](#셸-통합)이 필요합니다. [워크스페이스 레일](/ko/guide/workspace-rail)이 켜져 있으면 워크스페이스에 속한 세션은 자체 디렉터리를 유지합니다. +## macOS의 마이크, 카메라, Apple 이벤트 + +통합 터미널에서 실행한 프로그램은 마이크, 카메라 또는 다른 앱을 제어하는 권한(Apple 이벤트, `osascript`가 사용)을 요청할 수 있습니다. macOS는 VMark의 이름으로 묻습니다. 터미널이 시작하는 모든 프로그램에 대해 VMark를 책임 있는 앱으로 보기 때문입니다. macOS가 물으면 허용하세요. 나중에 **시스템 설정 → 개인정보 보호 및 보안**의 **마이크**, **카메라** 또는 **자동화**에서 변경할 수 있습니다. 요청은 터미널을 열 때가 아니라 프로그램이 해당 리소스를 처음 사용할 때 발생합니다. + +프로그램이 무음으로 녹음하거나, 검은 화면만 캡처하거나, "권한 없음" 오류를 내는데 요청 창이 나타나지 않는다면 해당 설정 페이지에서 VMark가 목록에 있고 허용되어 있는지 확인하세요. 문제를 보고할 때는 해당 프로그램의 출력을 함께 첨부하세요. + +BlackHole 같은 가상 오디오 입력의 경우 권한만으로는 오디오가 라우팅되지 않습니다. 녹음 도구에서 원하는 입력을 선택하고 그 입력으로 오디오를 라우팅한 뒤, 긴 녹음 전에 짧은 녹음으로 확인하세요. 오디오 파일이 커지고 있다는 사실만으로는 소리가 녹음되었다고 볼 수 없습니다. VMark에는 녹음기나 전사 도구가 포함되어 있지 않으며, 그런 명령은 별도로 설치한 도구가 제공합니다. + ## 아직 구현되지 않은 기능 다음 기능은 추적 중이지만 현재 제공되지 **않습니다**. 이 페이지의 이전 버전이 그중 diff --git a/website/pt-BR/guide/terminal.md b/website/pt-BR/guide/terminal.md index 1cbdc9886..bf8183896 100644 --- a/website/pt-BR/guide/terminal.md +++ b/website/pt-BR/guide/terminal.md @@ -189,6 +189,14 @@ Os atalhos padrão do shell como `Ctrl+R` (pesquisa de histórico reverso no zsh Quando a raiz da área de trabalho muda enquanto o terminal já está em execução, as sessões ociosas fazem automaticamente `cd` para a nova raiz. Uma sessão ocupada com um comando (por exemplo, `vim` ou `less`) não é interrompida: ela muda de diretório quando o comando termina, o que requer a [integração com o shell](#integracao-com-o-shell) para ser detectado. Com a [barra de espaços de trabalho](/pt-BR/guide/workspace-rail) ativada, as sessões que pertencem a uma área de trabalho mantêm seu próprio diretório. +## Microfone, câmera e Apple Events no macOS + +Os programas que você executa no terminal integrado podem solicitar o microfone, a câmera ou permissão para controlar outros apps (Apple Events, usados pelo `osascript`). O macOS pergunta em nome do VMark, porque considera o VMark o app responsável por tudo o que o terminal inicia. Permita o acesso quando o macOS perguntar; você pode alterá-lo depois em **Ajustes do Sistema → Privacidade e Segurança**, em **Microfone**, **Câmera** ou **Automação**. A solicitação acontece quando um programa usa o recurso pela primeira vez, não ao abrir o terminal. + +Se um programa gravar silêncio, capturar uma imagem preta ou relatar um erro de "não autorizado" sem que apareça nenhuma solicitação, verifique na página de ajustes correspondente se o VMark está listado e permitido. Ao relatar o problema, inclua a saída do programa. + +Para entradas de áudio virtuais como o BlackHole, a permissão por si só não roteia o áudio. Selecione a entrada desejada na sua ferramenta de gravação, roteie o áudio para ela e verifique uma gravação curta antes de uma sessão longa: um arquivo de áudio crescendo não prova, por si só, que o som foi capturado. O VMark não inclui gravador nem transcritor; esses comandos vêm de ferramentas que você instala separadamente. + ## Ainda não implementado Estes itens estão planejados, mas **não** estão disponíveis hoje. Eles aparecem diff --git a/website/zh-CN/guide/terminal.md b/website/zh-CN/guide/terminal.md index 5fab062db..790e4624b 100644 --- a/website/zh-CN/guide/terminal.md +++ b/website/zh-CN/guide/terminal.md @@ -153,6 +153,14 @@ VMark 有意**不**设置 `EDITOR`。`git commit`、`crontab -e` 等命令会启 在终端已运行的情况下工作区根目录发生变化时,空闲的会话会自动 `cd` 到新的根目录。正在执行命令(比如 `vim` 或 `less`)的会话不会被打断:它会在命令结束后再切换目录,而检测这一点需要 [Shell 集成](#shell-集成)。开启[工作区导轨](/zh-CN/guide/workspace-rail)后,属于某个工作区的会话会保留各自的目录。 +## macOS 上的麦克风、摄像头与 Apple 事件 + +在集成终端中运行的程序可以请求使用麦克风、摄像头,或控制其他应用的权限(Apple 事件,`osascript` 会用到)。macOS 会以 VMark 的名义询问,因为它把 VMark 视为终端所启动的一切程序的责任应用。macOS 询问时请选择允许;之后可以在**系统设置 → 隐私与安全性**的**麦克风**、**摄像头**或**自动化**中更改。请求发生在程序首次使用该资源时,而不是打开终端时。 + +如果程序录到的是静音、拍到的是黑屏,或报告“未授权”错误,而且没有出现提示,请在对应的设置页面中检查 VMark 是否在列表中并已允许。报告问题时,请附上该程序的输出。 + +对于 BlackHole 这类虚拟音频输入,仅有权限并不能完成音频路由。请在录音工具中选择目标输入、把音频路由到该输入,并先验证一段短录音再开始长时间录制:音频文件在变大,本身并不能证明录到了声音。VMark 不包含录音或转写工具;这些命令来自你另行安装的工具。 + ## 尚未实现 以下功能已在跟踪中,但目前**尚未**提供。之所以在此列出,是因为本页的早期版本曾把其中一些描述得好像已经可用: diff --git a/website/zh-TW/guide/terminal.md b/website/zh-TW/guide/terminal.md index b92220786..563a764c7 100644 --- a/website/zh-TW/guide/terminal.md +++ b/website/zh-TW/guide/terminal.md @@ -153,6 +153,14 @@ VMark 刻意**不**設定 `EDITOR`。`git commit`、`crontab -e` 等會啟動的 若終端機已在執行時工作區根目錄發生變更,閒置的工作階段會自動 `cd` 至新的根目錄。正在執行指令(例如 `vim` 或 `less`)的工作階段不會被中斷:它會在指令結束後才切換目錄,這需要 [shell 整合](#shell-整合) 才能偵測。開啟[工作區導軌](/zh-TW/guide/workspace-rail)後,屬於某個工作區的工作階段會保留自己的目錄。 +## macOS 上的麥克風、相機與 Apple 事件 + +在整合式終端機中執行的程式可以要求使用麥克風、相機,或控制其他 App 的權限(Apple 事件,`osascript` 會用到)。macOS 會以 VMark 的名義詢問,因為它把 VMark 視為終端機所啟動的一切程式的負責 App。macOS 詢問時請選擇允許;之後可以在**系統設定 → 隱私權與安全性**的**麥克風**、**相機**或**自動化**中變更。要求發生在程式首次使用該資源時,而不是開啟終端機時。 + +如果程式錄到的是靜音、拍到的是黑畫面,或回報「未授權」錯誤,而且沒有出現提示,請在對應的設定頁面中檢查 VMark 是否在列表中並已允許。回報問題時,請附上該程式的輸出。 + +對於 BlackHole 這類虛擬音訊輸入,僅有權限並不能完成音訊路由。請在錄音工具中選擇目標輸入、把音訊路由到該輸入,並先驗證一段短錄音再開始長時間錄製:音訊檔案在變大,本身並不能證明錄到了聲音。VMark 不包含錄音或轉寫工具;這些指令來自你另行安裝的工具。 + ## 尚未實作 以下項目已列入追蹤,但目前**尚未**提供。之所以列在這裡,是因為本頁的早期版本曾把其中幾項描述得好像已經可用: