Skip to content

RFC: add a signed local CLI and capability API over canonical actions #309

Description

@xcv58

Summary

Introduce an official mactools command-line tool backed by a versioned, authenticated local IPC contract

The CLI should discover and invoke the same canonical actions used by Unified Search, global shortcuts, Automation, Action Grid, gestures, Run Links, and App Intents.

Canonical actions remain the domain model. Run Links remain the lightweight deep-link interface. The CLI becomes an additional programmatic surface with structured input, output, completion, and error behavior.

Decision Requested

This RFC asks the project to decide:

  • Whether MacTools should ship an official CLI
  • Which local IPC architecture should connect the CLI to the running host
  • How CLI action eligibility relates to existing external-invocation policy
  • How the CLI locates or starts the MacTools host
  • How the binary is distributed and placed on the user's shell path
  • Which commands and guarantees belong in the first version

This RFC does not authorize a network server or remote-control API.

Motivation

Run Links are appropriate for:

  • Clickable integrations
  • Apple Shortcuts
  • Launchers
  • Documentation
  • Fire-and-forget shell commands
  • Stored parameter presets

They are not sufficient for:

  • Listing installed actions
  • Describing action parameters
  • Querying availability
  • Passing typed parameters directly
  • Receiving structured results
  • Returning reliable shell exit codes
  • Waiting for completion
  • Cancelling cancellable work
  • Machine-readable scripting
  • Future local agent integrations

A CLI would make MacTools useful from:

  • Terminal scripts
  • Shell aliases
  • Developer tooling
  • Alfred, Raycast, Hammerspoon, and similar tools
  • Test automation
  • Local agents
  • A future MCP adapter
  • Support and diagnostic workflows

Architectural Invariants

  • Plugins continue to publish canonical actions through the existing action registry.
  • The CLI does not load plugin bundles.
  • The CLI does not implement plugin behavior.
  • The CLI does not bypass availability, permission, confirmation, concurrency, or timeout policies.
  • The host process remains authoritative for installed plugins and runtime state.
  • The CLI communicates only through a local authenticated channel.
  • No TCP port or local HTTP server is introduced.
  • Run Links remain supported.
  • Existing action references remain stable.
  • Sensitive action values are never placed in URLs or ordinary command-line arguments.

Proposed Command Surface

The initial command groups should be:

General

  • mactools version
  • mactools doctor
  • mactools help

Actions

  • mactools actions list
  • mactools actions describe <provider/action>
  • mactools actions availability <provider/action>
  • mactools actions run <provider/action>
  • mactools actions run <provider/action> --parameter name=value
  • mactools actions run <provider/action> --input-json <path>
  • mactools actions run <provider/action> --json

Workflows

Workflows remain canonical actions, but convenience commands improve usability:

  • mactools workflows list
  • mactools workflows describe <name-or-id>
  • mactools workflows run <name-or-id>
  • mactools workflows history <name-or-id>

Plugins

Read-only plugin status may be useful:

  • mactools plugins list
  • mactools plugins describe <plugin-id>
  • mactools plugins doctor <plugin-id>

Installing, updating, and uninstalling plugins should remain outside the first CLI version.

Output Behavior

Human-Readable Mode

Default output should be concise and suitable for interactive terminal use.

Examples of result categories:

  • Completed
  • Started
  • Cancelled
  • Unavailable
  • Confirmation denied
  • Timed out
  • Invalid parameters
  • Host unavailable
  • Provider changed

JSON Mode

--json should produce a versioned object containing:

  • Protocol version
  • Request ID
  • Action reference
  • Invocation source
  • Start and finish timestamps
  • Outcome category
  • User-facing message
  • Structured rejection category

The output must not contain sensitive parameter values.

Exit Codes

Define stable exit-code categories for:

  • Success
  • Invalid command or input
  • Unknown action
  • Action unavailable
  • Confirmation denied or unavailable
  • Action failure
  • Timeout
  • Cancellation
  • Host or transport failure
  • Protocol incompatibility

Exact numeric values should be selected during implementation and documented as part of the CLI contract.

Execution Semantics

Add a distinct .cli action-execution source.

The first version should:

  • Wait for validation and confirmation
  • Wait for ordinary action completion by default
  • Return after durable background work is accepted when the action owns its own progress UI
  • Allow --no-wait only when semantics are unambiguous
  • Send cancellation when the CLI receives an interrupt and the action is cancellable
  • Preserve the action's configured timeout
  • Display host-owned confirmation UI for confirmation-required actions
  • Fail rather than silently approve when confirmation UI cannot be presented

Fine-grained progress streaming is not required for the first version. The initial protocol may report accepted, started, and final states. A future protocol version can add progress events.

Initial Action Eligibility

Use a conservative policy for the first version.

An action may be run from the CLI only when:

  • It is published in the action catalog
  • It is currently registered
  • Its parameters validate
  • It is currently available
  • Its execution mode is supported
  • It is eligible for external invocation under the current policy
  • It contains no unsupported sensitive parameter transport
  • The provider has not excluded the CLI surface

Add ActionExposureSurface.cli so providers can explicitly exclude a CLI invocation.

The RFC should decide whether a future dedicated CLI policy is needed for actions that should be available to a signed local CLI but not to URL-based Run Links.

Sensitive Parameters

Do not encourage secrets on the command line because shell histories and process listings may expose them.

Sensitive values should use one of:

  • Standard input
  • A permission-restricted input file
  • A future host-owned secure prompt
  • A Keychain-backed saved preset

The host must continue validating parameter privacy metadata.

Sensitive parameter values must never appear in:

  • Process arguments
  • Logs
  • Diagnostics
  • JSON output
  • Action history
  • Error messages

Local Transport Spike

Before implementing the full CLI, compare these architectures.

Option A: User-Scoped Bundled LaunchAgent Using XPC

Potential benefits:

  • Discoverable local service
  • Launch-on-demand behavior
  • Explicit bidirectional interface
  • Strong code-signing validation
  • Clear service lifecycle

Costs:

  • Additional process
  • Service Management registration
  • Upgrade and removal behavior
  • Coordination with the GUI host and plugin runtime

Option B: GUI-Owned Authenticated IPC Endpoint

Potential benefits:

  • Fewer long-running processes
  • The action registry stays directly inside the existing host
  • Simpler ownership after the app is running

Costs:

  • Endpoint discovery
  • Secure app-launch handoff
  • Stale endpoint cleanup
  • Peer validation
  • More complicated behavior when the app is not running

Option C: User-Owned Unix-Domain Socket

Potential benefits:

  • Simple request and response protocol
  • Natural streaming and cancellation
  • Easy use from multiple languages

Costs:

  • Custom authentication
  • Socket lifecycle and permissions
  • Peer-identity verification
  • More custom security code than XPC

The spike should select an option based on:

  • Same-user and same-team validation
  • Launch behavior
  • Upgrade behavior
  • Cancellation
  • Streaming
  • Testability
  • Failure recovery
  • App Sandbox and signing constraints
  • Removal behavior

Do not select a transport only because it is quickest to prototype.

Security Requirements

  • Accept connections only from the current user.
  • Validate the CLI's code-signing identity where supported.
  • Fail closed when identity cannot be established.
  • Use a versioned, explicit IPC interface.
  • Bound request and response sizes.
  • Reject unknown fields or unsupported protocol versions safely.
  • Validate every action reference through the registry.
  • Revalidate provider generation and availability before execution.
  • Preserve confirmation policy.
  • Never expose arbitrary Swift invocation, selectors, shell execution, or plugin internals.
  • Do not add a general "run arbitrary command" endpoint.
  • Keep an optional redacted audit entry for CLI-originated actions.
  • Prevent recursive CLI-to-action-to-CLI invocation loops where detectable.

Host Lifecycle

When MacTools is not running, the CLI should:

  1. Locate the installed MacTools application.
  2. Launch or activate the host without opening unnecessary windows.
  3. Wait for action-registry readiness with a bounded timeout.
  4. Negotiate the local protocol version.
  5. Execute or return a clear startup error.

The CLI must not independently inspect or load installed plugin bundles.

If no graphical user session is available, actions requiring UI or confirmation should fail clearly.

Distribution

Ship a signed CLI binary with the MacTools application.

Evaluate these installation paths:

  • Homebrew exposes the bundled binary automatically.
  • The app provides an "Install Command Line Tool" or "Show Setup Command" action.
  • Users can add a symlink under ~/.local/bin.
  • Advanced users can invoke the binary directly from the app bundle.

Avoid introducing a privileged helper solely to create a symlink.

The CLI and application should report compatible protocol and build versions through mactools version and mactools doctor.

Implementation Phases

Phase 0: Transport and Security Spike

  • Compare the candidate IPC architectures.
  • Implement version handshake.
  • Validate same-user and same-team behavior.
  • Test app launch and registry readiness.
  • Test stale or incompatible clients.
  • Document the selected architecture.

Phase 1: Read-Only Discovery

  • version
  • doctor
  • actions list
  • actions describe
  • actions availability
  • workflows list
  • Human-readable and JSON output

Phase 2: Conservative Execution

  • Run parameterless externally eligible actions.
  • Wait for completion.
  • Return structured result and exit code.
  • Preserve confirmation behavior.
  • Add cancellation for cancellable actions.

Phase 3: Typed Parameters

  • Validate typed parameter input.
  • Support JSON input.
  • Support secure sensitive-value input.
  • Add saved-preset references where appropriate.

Phase 4: Richer Integrations

  • Optional progress events
  • Action and workflow history
  • MCP adapter
  • Better launcher integrations
  • Additional authenticated local clients

Test Coverage

Include tests for:

  • Protocol-version mismatch
  • Unsigned or wrong-team client
  • Wrong user
  • Host not installed
  • Host not running
  • Host launch timeout
  • Registry startup delay
  • Unknown action
  • Invalid parameters
  • Unavailable action
  • Permission failure
  • Confirmation approval and denial
  • Confirmation unavailable
  • External invocation excluded
  • Provider changing during execution
  • Concurrent invocation policy
  • Timeout
  • Cancellation
  • Sensitive-value redaction
  • JSON schema stability
  • Exit codes
  • App and CLI upgrade ordering

Acceptance Criteria for the RFC

  • The product role of the CLI is approved.
  • Canonical actions remain the sole execution domain model.
  • Run Links remain supported.
  • One local transport architecture is selected.
  • Client-identity and user-identity requirements are documented.
  • Host-startup behavior is decided.
  • Initial action-eligibility policy is decided.
  • Sensitive-parameter transport is decided.
  • Initial command and JSON contracts are approved.
  • Distribution and shell-path behavior are decided.
  • Phase 0 can proceed without committing to a public remote API.

Non-Goals

  • Remote access over the network
  • A REST server
  • A public cloud API
  • Replacing the GUI
  • Replacing Run Links
  • Loading plugins inside the CLI
  • Allowing arbitrary code execution
  • Root or system-wide control
  • Plugin installation or update in the first version
  • Guaranteeing headless execution for actions that require a graphical session
  • Declaring the first IPC protocol permanently stable for third parties

Open Questions

  • Should CLI eligibility initially reuse Run Link external policy, or should a separate opt-in be required?
  • Should the host, a LaunchAgent, or a broker own the IPC listener?
  • Should the CLI launch MacTools automatically by default?
  • Which actions should be visible but not runnable?
  • Should workflow conveniences be aliases over actions or separate IPC operations?
  • How should fine-grained progress evolve without changing the current action ABI prematurely?
  • What is the preferred Homebrew and non-Homebrew installation path?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions