Enterprise · IT administration
Managed Configuration
Parleq supports macOS Managed Configuration so IT departments can deploy it as a governed application: disable optional features, lock settings, prevent users from overriding them, and override the Sparkle update channel to point at a corporate-hosted appcast mirror. Configuration is read from /Library/Managed Preferences/com.parleq.app.plist, which any MDM that speaks macOS can write — Jamf Pro, Kandji, Mosyle, Intune, Workspace ONE, Addigy, JumpCloud, and others. Pins are enforced while the configuration profile is installed: on supervised (DEP-enrolled) Macs the profile is non-removable; on unsupervised machines a local administrator can remove the profile, so use supervised enrollment where you need a hard guarantee.
For a step-by-step deployment guide aimed at IT administrators (rather than developers), see the Admin Guide.
How macOS Managed Configuration works
macOS Managed Configuration is a read-only preference overlay, not an SDK or agent integration. Your MDM pushes a .mobileconfig Configuration Profile targeting com.parleq.app. macOS writes the policy values to /Library/Managed Preferences/com.parleq.app.plist, owned by root.
Parleq reads managed preferences at startup using CFPreferencesAppValueIsForced to detect which keys came from MDM (as opposed to the user’s own preferences). When a key is managed, Parleq uses the MDM value and disables the corresponding Settings toggle so the user can see the policy but cannot override it. The user’s own stored value is not erased — re-deploying without the profile restores the user’s original preference automatically.
Supported keys
Parleq accepts two categories of managed keys: feature toggles (Boolean, default true) and provider/model lockdown (String or [String]).
Feature toggles
| Key | Type | Default | Setting to false does |
|---|---|---|---|
| referenceWindowsEnabled | Boolean | true | Hides the reference-attach button, chip strip, and drag-drop zone on the overlay. Disables Shift+hotkey staging mode. Sub-toggles below become moot. |
| clipboardReferenceEnabled | Boolean | true | Removes the “Add from clipboard” item from the overlay’s + menu. Requires referenceWindowsEnabled: true to have any effect. |
| imageReferenceEnabled | Boolean | true | Removes the T/👁 mode toggle from every reference chip. All references use text-mode (OCR only) — no screenshots are sent to the LLM. |
| fileReferenceEnabled | Boolean | true | Removes the “Add file…” picker item from the + menu and disables drag-drop onto the overlay. Window-capture and clipboard references remain available. |
| customDictionaryEnabled | Boolean | true | Hides the dictionary editor in Settings and drops the vocabulary hint from all LLM cleanup prompts. The user’s stored entries are not deleted — re-enabling restores them. |
| customModelEntryEnabled | Boolean | true | Removes the “Custom…” entry from every provider’s model dropdown. Defense-in-depth: at startup, any non-curated model name already in the config (whether hand-edited or MDM-pushed) is reset to the provider’s curated default and logged. |
| autoUpdateEnabled | Boolean | true | Disables Sparkle’s automatic appcast checks. The “Automatically check for updates” toggle in Settings is disabled and shows a lock icon with “Managed by your organization.” The manual “Check for Updates Now” button remains functional. Additionally, when this key is set to true, Parleq clears any user-domain SUSkippedVersion default on launch — closing the “skip every version to stay on a stale build” bypass against the forced-update policy. |
| livePricingEnabled | Boolean | true | Disables the background fetch of LLM model-pricing data that Parleq pulls once per launch (when stale) from raw.githubusercontent.com (the community LiteLLM price table) to keep the Usage / Stats cost figures current. When false, Parleq makes no such request and serves its bundled price table instead. Set this for locked-down or air-gapped fleets where any outbound HTTPS to a non-approved host must be justified. (The PARLEQ_DISABLE_LIVE_PRICING=1 environment variable does the same for a single user without MDM.) |
| transformPresetsEnabled | Boolean | true | Set to false to disable transform presets fleet-wide — preset chips hidden and per-app defaults not applied. |
Provider and model lockdown
These keys restrict which LLM providers and models users can pick. Parleq has two tiers — Cleanup (used for normal dictation) and Context (used when references are attached) — and each tier has an independent set of keys.
| Key | Type | Behavior |
|---|---|---|
| cleanupProvider | String | Pin. Forces a single provider. The picker is disabled and shows only this value. Valid values: gemini, vertex, bedrock, bedrock-bearer, azure, openai. |
| cleanupAllowedProviders | [String] | Allowlist. Restricts the picker to a subset of providers; the user can still choose among them. A lock icon indicates the restriction. If the user’s stored provider is not in the allowed list, it resets to the first entry. |
| cleanupModel | String | Pin. Forces a single model. The model picker is disabled. |
| cleanupAllowedModels | [String] | Allowlist. Restricts the model picker to a subset. “Custom…” is also hidden when this is set (even if customModelEntryEnabled is true). If the stored model is not in the allowed list, it resets to the first entry. |
| contextProvider | String | Same as cleanupProvider, applied to the Context tier. |
| contextAllowedProviders | [String] | Same as cleanupAllowedProviders, applied to the Context tier. |
| contextModel | String | Same as cleanupModel, applied to the Context tier. |
| contextAllowedModels | [String] | Same as cleanupAllowedModels, applied to the Context tier. |
Pin vs allowlist precedence: When both a pin key (cleanupProvider) and an allowlist key (cleanupAllowedProviders) are set for the same tier, the pin wins — it is strictly more restrictive. The allowlist is effectively ignored because the picker is already disabled by the pin. The same rule applies to the model keys and to the context tier.
Authentication-mode restrictions
These keys let IT admins restrict how users authenticate to LLM providers — forcing federated identity (Entra ID, AWS SSO) and disabling static API-key entry across the board.
| Key | Type | Behavior |
|---|---|---|
| staticApiKeysAllowed | Boolean | Master switch — UI gate and runtime enforcement. When set to false, Parleq takes two complementary actions: (1) hides every “Set … API key”, “Set … Service Account JSON”, and “Set … Bearer key” affordance across all provider credentials cards; and (2) refuses to use static-key auth paths at runtime — even if a key is already stored in the Keychain, Parleq will not use it and will surface a clear error directing the user to switch to a federated-auth provider. Providers with no federated fallback become entirely unavailable: Gemini (Google API direct), OpenAI direct, and Bedrock bearer are blocked outright. Mixed-auth providers retain their federated path: Azure’s azureAd (Entra ID) mode, Bedrock IAM’s sso mode, and Vertex’s adc mode all continue to work. The provider picker annotates fully-blocked providers with “(disabled by your organization)” so the user understands why cleanup fails without opening Settings. Default: true (not restricted). |
| azureAuthMode | String | Pin Azure auth mode. Forces the Azure OpenAI credentials card to a specific authentication flavor. The auth-mode picker becomes a fixed disabled label. Recognized values: apiKey (default — resource API key) and azureAd (Microsoft Entra ID via az login). When pinned to azureAd, the “Set API key” button is hidden; when pinned to apiKey, the Entra ID controls are hidden. Precedence: staticApiKeysAllowed: false is a hard override — even if azureAuthMode: apiKey is set, the API key entry button is still hidden when the master switch is off. Unrecognized values are rejected with a log warning. |
| bedrockAuthMode | String | Pin Bedrock IAM auth mode. Forces the Bedrock (AWS IAM) credentials card to a specific authentication flavor. Applies to the bedrock provider only (not bedrock-bearer). Recognized values: sso (default — AWS CLI / SSO session), static (long-lived IAM access keys), bedrockApiKey (scoped Bedrock-only bearer token). When pinned to sso, credential-entry controls for the other modes are hidden; analogously for the other values. Precedence: staticApiKeysAllowed: false additionally hides static and bedrockApiKey credential entry even if bedrockAuthMode pins to one of them. Unrecognized values are rejected with a log warning. |
| vertexAuthMode | String | Pin Vertex AI auth mode. Symmetric with azureAuthMode and bedrockAuthMode. Recognized values: adc (default — gcloud Application Default Credentials) and serviceAccount (static SA JSON in Keychain). When pinned, the Vertex auth-mode picker is replaced by a fixed disabled label. Precedence: staticApiKeysAllowed: false additionally refuses the serviceAccount path at runtime even if vertexAuthMode pins to it. Unrecognized values are rejected with a log warning. |
Precedence and runtime enforcement: staticApiKeysAllowed: false is the hard outer gate at both UI and runtime layers. An auth-mode pin like azureAuthMode: apiKey permits API-key authentication only if staticApiKeysAllowed does not forbid it — if the master switch is off, the runtime gate refuses the apiKey path regardless of the auth-mode pin. To force Entra ID or AWS SSO across the board, set staticApiKeysAllowed: false and optionally also pin the auth mode for belt-and-suspenders clarity in the Compliance Audit dialog. Note: the runtime gate is enforced at provider-construction time on every launch — removing the MDM profile immediately restores access to previously-stored Keychain credentials on the next restart.
Destination pins
Provider, model, and auth-mode pins above govern which service Parleq calls; destination pins govern where. Even with an allowed provider, an unconstrained user can route data to their personal cloud tenant or their own ASR server. The keys in this section close that gap.
| Key | Type | Behavior |
|---|---|---|
| asrEndpoint | String | Pin the speech-recognition HTTP destination. Closes the “route raw audio elsewhere” bypass: before LLM cleanup runs, audio is POSTed to whatever asr.endpoint resolves to. Unmanaged, this accepts any http:// or https:// URL so a user can run a local Sherpa / faster-whisper server. Managed, only two forms are accepted: the bundled-FluidAudio sentinel string http://127.0.0.1:8767/inference (in-process, no network) or an https:// URL with a non-empty host, no embedded credentials, and no query parameters. Plain http:// is rejected when pushed via MDM — pushing audio off-box must travel over TLS. |
| vertexProject | String | Pin the GCP project ID Vertex calls route through. Closes “use the allowed provider but route to my personal GCP project.” Non-empty values only; empty pins are rejected with a log warning and treated as unmanaged. |
| vertexRegion | String | Pin the Vertex Gemini region (e.g. us-central1). Combined with vertexProject, this lets you fully constrain where Gemini calls land for data-residency compliance. Empty pins rejected. |
| vertexAnthropicRegion | String | Pin the Vertex Anthropic region for Claude-on-Vertex calls (e.g. us-east5). Separate from vertexRegion because us-central1 doesn’t host the Anthropic publisher. Empty pins rejected. |
| awsRegion | String | Pin the AWS Bedrock region. Closes “use the allowed provider but route to a personal AWS region.” Empty pins rejected. |
| awsProfile | String | Pin the local AWS CLI profile name (combined with bedrockAuthMode: sso). An empty value is accepted and means “use AWS_PROFILE env var or the default profile” — useful when the IT department’s SSO configuration is the same on every machine. |
| azureResource | String | Pin the Azure OpenAI resource name (or full hostname). Closes “use the allowed provider but route to a personal Azure resource.” Empty pins rejected. |
| azureDeployment | String | Pin the Azure OpenAI deployment name. Combined with azureResource this fully constrains which Azure deployment Parleq routes through. Empty pins rejected. |
Enterprise OIDC federation
These keys configure Parleq’s optional corporate single sign-on: one OIDC sign-in, federated into AWS Bedrock (AssumeRoleWithWebIdentity) and/or Google Cloud Vertex AI (Workforce Identity Federation), with no per-user API keys. Pinning the issuer, client ID, role ARN, and workforce provider via MDM gives the whole fleet a single managed identity path. The refresh token and signed-in identity stay Keychain-only and are never managed-config surfaced. Set both oidcIssuer and oidcClientID, then enable a leg by pinning awsAuthMode: oidc (with awsRoleArn) and/or vertexAuthMode: oidcFederation (with vertexWorkforceProvider).
| Key | Type | Behavior |
|---|---|---|
| oidcIssuer | String | Pin the OIDC issuer URL (e.g. https://acme.okta.com). Parleq runs OIDC discovery against <issuer>/.well-known/openid-configuration. Empty pins are rejected with a log warning and treated as unmanaged. |
| oidcClientID | String | Pin the public OIDC client ID registered with the IdP for Parleq. The client must be a native / public client (no secret) that permits PKCE and the parleq-auth://oidc/callback redirect URI. Empty pins rejected. |
| oidcScopes | [String] | Pin the requested scopes. Default ["openid", "profile", "email", "offline_access"]. Keep offline_access so the IdP issues a refresh token for silent re-authentication. |
| oidcEphemeralBrowserSession | Boolean | Force an ephemeral sign-in web session. When true, the PKCE sign-in uses an ephemeral browser session (no shared cookies / persistent IdP session), so each sign-in starts clean. Default false. |
| oidcRedirectURI | String | Pin the OAuth redirect URI used in the authorization request and token exchange. The browser callback scheme is derived from this URL’s scheme. Default parleq-auth://oidc/callback; generic OPs (e.g. a Google native client) need their own reversed-client-ID scheme such as com.googleusercontent.apps.<id>:/oauth2redirect. A value that doesn’t parse as a URL with a scheme is rejected (the default is kept). |
| oidcExtraAuthParams | Dictionary | Pin extra authorization-request query params. A [String: String] map appended to the sign-in URL — e.g. {"access_type": "offline", "prompt": "consent"} to force a refresh token on every Google sign-in. Reserved OIDC params (client_id, redirect_uri, state, nonce, code_challenge*, response_type, scope) are ignored. Default empty. |
| awsRoleArn | String | Pin the AWS IAM role ARN assumed via AssumeRoleWithWebIdentity when awsAuthMode: oidc. The role’s trust policy is what gates access (the STS call itself is unauthenticated). Empty pins rejected. |
| awsSessionDurationSeconds | Integer | Pin the requested STS session duration for the federated AWS credentials. Default 3600 (1 h). Clamped to STS’s 900–43200 s (15 min – 12 h) window. Shorter durations tighten offboarding latency — already-issued STS credentials are not revocable, so they live to this maximum. |
| vertexWorkforceProvider | String | Pin the GCP Workforce Identity provider resource name (locations/global/workforcePools/<pool>/providers/<provider>) used when vertexAuthMode: oidcFederation. Empty pins rejected. |
Operational policy
These keys let IT admins govern update channels and logging policy.
Key Type Behavior sparkleUpdateFeedURL String (URL) Overrides the Sparkle appcast URL. When set, Parleq fetches its appcast from this URL instead of the hardcoded SUFeedURL in Info.plist. Use case: a corporate IT department mirrors the Parleq appcast on internal infrastructure after security review and points the fleet at the mirror. Must be a valid https:// URL — http://, file://, and other schemes are rejected with a log warning. Malformed or non-https:// values fall back to Info.plist SUFeedURL. Settings → Updates shows the managed URL with an “(managed by your organization)” caption. loggingMode String Forward-compatibility hook for compliance-sensitive deployments. Parleq does not have a verbose-logging mode today — all logging is length-only by convention. Setting loggingMode: "lengthOnly" records the IT department’s preferred policy in the Compliance Audit dialog and is ready to gate a future verbose mode if one is ever added. Recognized values: lengthOnly (default) and verbose (anticipated future, no current effect). Unrecognized values are rejected with a log warning and the key is not added to managedKeys — the audit shows it as Default rather than Managed.
Security invariant — sparkleUpdateFeedURL:
The managed feed URL changes where the appcast comes from, not who can sign it.
SUPublicEDKey in Info.plist still authoritatively gates which Sparkle signatures are valid.
An attacker who hosts a fake appcast at the managed URL cannot push an arbitrary binary
without the maintainer’s Ed25519 private key — every downloaded update is still
checked against the hardcoded public key before installation.
Residual risk — appcast release-notes content:
Sparkle renders the appcast’s release-notes field as HTML inside the update dialog.
An attacker who controls the managed feed URL can therefore display arbitrary release-notes content to users (text, links, formatted prose) even though they cannot ship code through the EdDSA gate. The risk is social-engineering only — a misleading message in a release-notes pane could prompt the user to visit an external URL and install something outside Sparkle. Mitigation: treat the managed feed URL as a security-critical setting, vet the host before pointing fleets at it, and prefer hosting on infrastructure under your own change-control.
Transcript-history retention
These keys bound the in-memory Recent Dictations history (the cleaned-text list in the Parleq window). Both are integers; either or both can be set, and whichever limit is hit first drops entries. With no policy, history is unbounded until the app quits — there is no on-disk transcript retention surface either way.
Key Type Behavior transcriptHistoryMaxEntries Int Maximum number of cleaned dictations kept in the Recent Dictations list at any time. Older entries drop off as new ones arrive. 0 disables history entirely (see the security invariant below). transcriptHistoryRetentionHours Int Sweep Recent Dictations entries older than this many hours. 0 disables history entirely (see the security invariant below).
Security invariant — 0 is the zero-retention sentinel:
Setting either key to 0 disables dictation history outright — Parleq returns immediately from the history-append path, so both the cleaned-text Recent Dictations list and the text-free per-dictation metrics ring stay empty. This is intentional: in regulated deployments even aggregate per-dictation counts can be a privacy signal, so a zero-retention policy also empties the Stats dashboard and stops writing ~/.parleq/metrics.jsonl. Neither history nor metrics is ever written to disk in cleaned-text form regardless of these values; the only persisted artifact is the text-free metrics.jsonl, which the 0 sentinel suppresses entirely.
Learn from corrections
These keys govern the opt-in “Learn from your corrections” feature (off by default). When enabled, Parleq keeps short correction snippets in memory only (never written to disk) and occasionally asks the user’s already-configured cleanup model to suggest custom-dictionary improvements. No new network destination is introduced — analysis uses the same cleanup model the user already has configured. IT can pin this feature off entirely or bound the in-session correction ring by entry count and retention window. The semantics are the same as for the transcriptHistory* keys: either Int key set to 0 disables the ring entirely.
Key Type Default Behavior learnFromCorrectionsEnabled Boolean false Master switch for the learn-from-corrections feature. Default is false (opt-in). Setting this to false via MDM pins the feature off and disables the toggle in Settings — users cannot enable it. When true is managed, the feature is force-enabled and the toggle is locked on. When the key is absent, users control the setting themselves. learnedCorrectionsMaxEntries Int — Maximum number of correction snippets retained in the in-session correction ring. Older entries are dropped as new ones arrive. 0 disables the ring entirely — no snippets are recorded and the learn-from-corrections analysis never runs, regardless of learnFromCorrectionsEnabled. Same semantics as transcriptHistoryMaxEntries. learnedCorrectionsRetentionHours Int — Age-prune correction snippets older than this many hours from the in-session ring. 0 disables the ring entirely — same zero-retention sentinel as transcriptHistoryRetentionHours. Both Int keys can be set together; whichever limit is hit first drops entries.
Known limitation — customDictionary entry content
The customDictionary feature lets users define term/context pairs that are injected verbatim into the LLM cleanup prompt to bias transcription of jargon, proper nouns, and project-specific vocabulary. MDM can suppress the entire feature by setting customDictionaryEnabled=false, but cannot constrain the content of individual entries when the feature is enabled. A user with a permissive policy can therefore add arbitrary text to the dictionary, which Parleq will pass to the LLM provider as part of its prompt for every cleanup call.
In compliance-sensitive deployments where prompt content must be controlled, set customDictionaryEnabled=false. There is no “allow only these entries” mode — the feature is binary at the MDM level.
Sample .mobileconfig
The snippet below shows the feature-toggle keys set to enforce a privacy-focused posture: reference windows are allowed but image-mode and file references are disabled, auto-update is managed centrally, and the live-pricing fetch is turned off. The full sample profile in the repo also exercises the provider/model lockdown and auth-mode restriction keys.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadIdentifier</key>
<string>com.example.parleq-policy.prefs</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-A-NEW-UUID</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadDisplayName</key>
<string>Parleq managed preferences</string>
<!-- mdmclient reads PayloadContent → bundle ID → Forced
→ [mcx_preference_settings] and materializes the keys
into /Library/Managed Preferences/com.parleq.app.plist so
CFPreferencesAppValueIsForced returns true for each. -->
<key>PayloadContent</key>
<dict>
<key>com.parleq.app</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<!-- Allow reference windows -->
<key>referenceWindowsEnabled</key>
<true/>
<!-- Allow clipboard references -->
<key>clipboardReferenceEnabled</key>
<true/>
<!-- Disable image-mode (no screenshots to LLM) -->
<key>imageReferenceEnabled</key>
<false/>
<!-- Disable file picker + drag-drop -->
<key>fileReferenceEnabled</key>
<false/>
<!-- Allow custom dictionary -->
<key>customDictionaryEnabled</key>
<true/>
<!-- Restrict model picker to curated list -->
<key>customModelEntryEnabled</key>
<false/>
<!-- Manage auto-update centrally -->
<key>autoUpdateEnabled</key>
<false/>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Parleq policy</string>
<key>PayloadIdentifier</key>
<string>com.example.parleq-policy</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-A-NEW-UUID</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
Replace both REPLACE-WITH-A-NEW-UUID placeholders with unique UUIDs (e.g. from uuidgen in Terminal). Also replace the com.example identifiers with your own organization’s reverse-DNS prefix.
How to deploy
- 1.
Author your profile.
Start from the sample snippet above. Set the keys you want to manage; omit keys you want users to control themselves. Any key absent from the profile is not managed — Parleq uses the user’s stored preference or the built-in default.
- 2.
Upload to your MDM.
In Jamf Pro: Computers → Configuration Profiles → New → Custom Schema / Application & Custom Settings payload. In Kandji: Library → Custom Profile → upload the .mobileconfig. In Mosyle: Apple Configurator → General → Custom Configuration Profile. All other MDMs that speak macOS MDM protocol accept the same .mobileconfig format.
- 3.
Scope and push.
Assign the profile to a device group, smart group, or individual machines. The MDM pushes the profile on the next MDM check-in (typically within a few minutes). Settings take effect on Parleq’s next launch after check-in.
Verification
To confirm a key took effect on a managed Mac, open Terminal and run:
defaults read com.parleq.app referenceWindowsEnabled
defaults read com.parleq.app imageReferenceEnabled
defaults read com.parleq.app autoUpdateEnabled
Each command prints 0 (false) or 1 (true), or The domain/default pair of (com.parleq.app, <key>) does not exist when the key is not managed.
To confirm the key came from managed preferences (not just a user-domain write), check the managed-preferences plist directly:
sudo defaults read /Library/Managed\ Preferences/com.parleq.app
For verification without Terminal access, open Parleq Settings → Privacy & Features and click View managed configuration…. The Compliance Audit dialog lists every managed-eligible key with its current effective value and source (Managed / User / Default), and includes a Copy snapshot button that places the full state as JSON on the clipboard so an IT admin can verify policy uptake without screen-sharing.
Local testing without an MDM
To verify a managed config payload locally without enrolling the Mac in an MDM, you can write to /Library/Managed Preferences/ directly with root. Two gotchas worth knowing:
- The directory may not exist on an unmanaged Mac.
/Library/Managed Preferences/ is created the first time an MDM enrolls the device. On a personal Mac you’ll need to create it yourself. cfprefsd caches managed-preferences lookups. A Parleq restart alone won’t pick up the new plist; you also need to invalidate the daemon cache.
Working recipe:
# 1. Create the directory if it doesn't exist
sudo mkdir -p "/Library/Managed Preferences"
# 2. Write the managed plist directly. Do NOT use `defaults write` here —
# its path-domain parser falls back to ~/Library/Preferences/ when the
# path contains multiple dots (com.parleq.app is three dots), so the
# file silently lands in the user domain instead of Managed.
sudo tee "/Library/Managed Preferences/com.parleq.app.plist" > /dev/null <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>referenceWindowsEnabled</key>
<false/>
</dict>
</plist>
EOF
# 3. Verify the file landed correctly
sudo plutil -p "/Library/Managed Preferences/com.parleq.app.plist"
# 4. Force the prefs daemon to invalidate its cache
sudo killall cfprefsd
# 5. Restart Parleq — the toggle should now be locked OFF and disabled
Parleq logs a one-line summary at startup listing the MDM-managed keys it detected; tail ~/.parleq/app.log to confirm your override was picked up.
To remove the override:
sudo rm "/Library/Managed Preferences/com.parleq.app.plist"
sudo killall cfprefsd
# restart Parleq — toggle is user-controllable again