The short version
MARC connects your iPhone to your Mac over SSH. Your Mac collects the readings, runs commands, and handles file actions. MARC sends requests through the encrypted connection and turns the replies into screens and graphs.
- You connect to your Mac’s Remote Login service using a Mac account.
- MARC checks the Mac’s SSH host identity, then authenticates your account.
- A monitoring collector starts automatically. Other readers run when their views need them; changes such as uploading a file or stopping a process require an action from you.
- Readings return to iPhone. Monitoring history is also saved under your Mac account; previews and downloads create temporary iPhone copies.
Your Mac must be awake and reachable from your iPhone. MARC connects directly, with no built-in internet relay. For access away from home, you need a secure route such as a VPN you configure separately. Start with Connect your Mac.
Identity, login, and data flow
Before signing in to a new Mac, MARC reads its public SSH host key and asks you to compare the key type and SHA256 fingerprint with that Mac. It remembers your approval for the host and port. An unexpected key change blocks later connections. Approval alone does not prove you reached the intended Mac.
Password login sends your username and password inside SSH. Key login uses an imported OpenSSH Ed25519 private key to sign on iPhone, without uploading the private key or passphrase. Your Mac must already authorize the matching public key. See SSH keys and Security & privacy.
After login, MARC checks that the server runs macOS. It sends embedded scripts to python3 through SSH. Monitoring and process collectors return one JSON object per line; other helpers return a JSON result. MARC reads these replies to update the display. Terminal output and SFTP transfers use separate formats.
Which features share a connection?
One SSH connection can carry several channels. A new command channel does not necessarily open another network connection.
| Feature | Connection and channel |
|---|---|
| Monitoring | The main authenticated SSH connection runs a long-lived, non-interactive command channel for the collector. |
| Processes and Status top processes | Separate streaming command channels on that main connection, opened by the views that need process readings. These are not interactive Terminal shells. |
| History, termination, Control Center, Services & Docker | Short command channels on the main connection. Controls and Services readers can refresh automatically while their views are active. |
| Files | A separate authenticated SSH connection opens an SFTP subsystem channel. Directory listings, metadata, text editing, and folder archives also use Python command channels on that Files connection. SFTP carries file bytes and performs operations such as canonical-path lookup and upload rename. |
| Terminal | Each session opens a separate authenticated SSH connection and requests an interactive shell with an xterm-256color pseudo-terminal (PTY). The current limit is four sessions. Local AI generation opens a command channel on that Terminal connection. |
| Test SSH and host probe | The host-key probe is a separate unauthenticated key exchange. Test SSH opens and closes its own authenticated connection for fixed read-only checks. |
What runs on your Mac
Monitoring starts automatically. A Python collector reads CPU, memory, storage, network, and available power information. It also tries to save history, even when Statistics is closed.
Process readers run when needed. Status’s top-process preview and the Processes view read a stream based on ps. Each stream uses a temporary stop marker. The reader can appear in its own process list.
Helpers handle individual requests. Browsing files, reading history, and refreshing controls or services run short requests. Uploading, editing, deleting, stopping processes, or changing controls requires your action.
Terminal opens a Mac shell. Your shell’s startup files and the programs you run determine what else starts. Files uses the Mac’s SFTP subsystem.
Python 3 must be installed and available as python3 to the SSH account. MARC supplies its scripts with each request; its collectors need no extra Python packages. Connecting does not install Python, a persistent MARC helper, a LaunchAgent, a LaunchDaemon, a scheduled job, or an executable collector script.
MARC does write history, stop markers, and temporary transfer files. Commands you run, optional tool installation, and service actions can make lasting changes. See Control Center and Services & Docker for prerequisites.
How to read the numbers
CPU: total and per-core activity come from changes in macOS CPU ticks, excluding idle ticks. User CPU includes user and nice ticks; system CPU is separate. These are whole-Mac readings, including other apps and macOS—not a measurement of MARC’s overhead.
Processes: CPU compares accumulated process CPU time with elapsed time. One busy core is about 100%, so a process can exceed 100%. Process memory is resident size (RSS). These figures do not add up directly to the whole-Mac display.
Memory: MARC subtracts free, inactive, and speculative pages from physical RAM using vm_stat’s page size. The estimate includes active, wired, and compressed resident usage; swap is separate. When macOS reports file-backed pages, MARC uses them to estimate cached files. MARC does not report a separate compressed-memory amount or reproduce Activity Monitor’s categories exactly.
Storage: capacity comes from the APFS Data volume. Displayed used space is total minus available, including shared-volume usage; it is not a folder-size total. Disk activity comes from storage-driver byte counters and is not limited to that volume.
Network and disk activity: MARC subtracts successive byte counters and divides by elapsed time. Network readings cover the default-route interface. Missing counters, a reset, or an interface change can leave rates unavailable until a valid pair arrives. These readings are not internet speed tests.
Sampling, slower readings, and gaps
The monitoring loop sleeps one second between collections, plus the time needed to run commands. The 1, 2, or 5 second refresh setting controls display updates; it does not change that sleep or every helper’s interval.
Storage capacity, process count, swap, and local IP refresh about every five seconds. Battery charge and power source use the selected 5, 10, 30, or 60 second interval (10 by default), applied when reconnecting. Battery health is read in a separate thread at most about once a minute when a battery is detected. Processes target one-second cycles; Control Center waits five seconds between refreshes, with wireless reads roughly every 30 seconds. Services & Docker readers use an eight-second refresh cadence. Request duration and failures can lengthen all these intervals.
Missing data does not mean zero usage. Some optional values become unavailable; slower readings can retain an earlier value after failure. A failed capacity read currently returns zero placeholders, not a real zero-capacity disk. Check connection status, update feedback, and permissions.
Invalid collector output can leave the last readings visible with a delay message. Repeated errors or about 90 seconds without a valid sample end the monitoring connection and allow reconnection when eligible. This does not check the freshness of every optional field.
The iPhone’s live chart buffer keeps about five minutes, capped at 300 points. Saved-history views request 15, 30, or 60 minutes and average samples into five- or ten-second buckets. Archived rates are not calculated across gaps over 120 seconds. Disconnections, backgrounding, failed writes, and clock changes can affect the record; charts do not imply continuous recording or reconstruct missing activity.
For the screens and common interpretation questions, see Monitoring and Processes.
Data and command reference
Expand a group for the built-in commands and APIs. Commands run on your Mac unless stated otherwise. Optional tools must already be installed. Angle-bracket values such as <pid> are placeholders.
Monitoring and processes
| Feature | Information accessed | Command or API | When | Purpose |
|---|---|---|---|---|
| Compatibility and connection test | OS identity; test also reads account and hostname | uname -s, sw_vers -productName; Test SSH adds whoami, hostname, uname -sm, sw_vers -productVersion | After authenticated connection; extra checks when Test SSH is selected | Confirm macOS and report login context |
| CPU and per-core activity | User, system, idle, nice counters | Python ctypes loads /usr/lib/libSystem.B.dylib: mach_host_self, host_statistics (flavor 3), host_processor_info (flavor 2), vm_deallocate | Collector startup and each live cycle | Calculate activity from tick differences; release per-core allocations |
| Memory and swap | RAM size, virtual-memory pages, swap used | sysctl -n hw.memsize, vm_stat, sysctl -n vm.swapusage | RAM once; pages each cycle; swap about every five seconds | Estimate RAM usage and report separate swap use |
| System context | Hostname, username, logical cores, model, OS, architecture, boot time, uptime, one-minute load, process count | Python socket.gethostname(), getpass.getuser(), os.getloadavg(); sysctl -n hw.logicalcpu, hw.model, kern.boottime; sw_vers -productVersion, uname -m, ps -axo pid= | Identity mostly once; load/uptime each cycle; count about every five seconds | Label the selected Mac and explain its current state |
| Storage | Data-volume total, used and available blocks | df -k /System/Volumes/Data | About every five seconds | Display capacity and available space |
| Disk activity | Driver-level cumulative bytes read and written | ioreg -r -c IOBlockStorageDriver -d 1 -a; retries without the depth limit if no counters are found | Each live cycle | Calculate disk read/write rates between samples |
| Network | Default-route interface, received/sent bytes, local IPv4 address when available | route -n get default, netstat -ibn, ipconfig getifaddr <interface> | Route/counters each cycle; IP about every five seconds | Show interface activity and connection context |
| Power | Charge, source, charging state; available condition, maximum capacity and cycles | pmset -g batt; system_profiler SPPowerDataType -json | Selected power interval; health at most about once per minute | Report battery and power details |
| Processes | PID, account, resident bytes, start time, accumulated CPU time, executable basename | ps -axo pid=,user=,rss=,lstart=,time=,comm= | While a process stream is requested | Build process lists and CPU deltas; this collector does not request full command-line arguments |
| Terminate process | Selected process’s current identity | ps -p <pid> -o user=,lstart=,comm=, Python os.kill(pid, signal.SIGTERM) | After user confirmation | Recheck identity and request termination, subject to account permissions |
| History | Timestamped monitoring JSON | Python sqlite3; writes marc_samples_v1, reads it and legacy samples | Save about every five seconds; read on history request | Provide recent Statistics beyond the live buffer |
Files and Terminal
| Feature | Information accessed | Command or API | When | Purpose |
|---|---|---|---|---|
| Browse and inspect | Names, sizes, dates, types, paths, ownership and permissions | SFTP path resolution; Python os.scandir, os.lstat, pwd, grp | Opening/reloading folders or item details | Display directory listings and metadata. For eligible text files, the detail helper also reads up to 1 MiB on the Mac to check editor support; that check does not return the content to iPhone. |
| Preview, download, upload | Selected file content and transfer metadata | SFTP file reads/writes, attributes and rename; Python staging and replacement checks | User-selected transfer | Copy content between Mac and iPhone; validate an existing destination before replacing it |
| Edit, rename, move, permissions, delete | Selected path, content for editing, identity and metadata | Python file I/O, os.replace, macOS renamex_np, os.chmod, os.unlink, shutil.rmtree; metadata APIs include fcopyfile and quarantine extended attributes | User-selected action; text bytes staged over SFTP for saving | Change real files; deletion bypasses Trash. Replacement attempts to preserve applicable metadata and quarantine. |
| Folder download | Selected folder’s entries and contents | Python tempfile, tarfile, os.scandir; then SFTP | User-selected folder download/share | Build a bounded .tar.gz archive on the Mac and transfer it |
| Terminal | Your input and the shell/program output | SSH PTY and shell request; commands chosen by you and your shell’s startup files | Opening a session and entering commands | Run a real interactive Mac shell. There is no fixed command inventory for user-run programs. |
Control Center, Services & Docker, and AI
| Feature | Information accessed | Command or API | When | Purpose |
|---|---|---|---|---|
| Audio and display | Volume/mute, output device names/IDs, display IDs/resolutions and supported brightness | osascript -e with fixed volume scripts; CoreAudio AudioObjectGetPropertyData/AudioObjectSetPropertyData; CoreGraphics display APIs and DisplayServices brightness APIs through ctypes | Control refresh; writes on user adjustment | Read settings and adjust supported outputs/displays |
| Wireless | Wi-Fi interface, power and network name when available; Bluetooth power | networksetup -listallhardwareports, -getairportpower, -getairportnetwork; writes -setairportpower; optional blueutil --power [0|1], otherwise system_profiler SPBluetoothDataType -json for reading | Periodic wireless reads in Controls; writes on user action | Report state and offer supported switches; turning off Wi-Fi can cut the SSH connection |
| Media, lock, display sleep/wake, Quick Launch | Open players; available Now Playing title, artist and source; installed app names/bundle IDs; session/display state | pgrep -u <uid> -x Music or Spotify, fixed osascript playback scripts; optional media-control get and advertised playback verbs; app Info.plist reads; open -b <bundle-id>; pmset displaysleepnow, caffeinate -u -t 1; CoreGraphics session dictionary and SACLockScreenImmediate through Python | Media reads during Controls refresh; catalog on request; changes on user action | Show/control existing playback, launch selected apps, and request session/display controls. Media artwork stays on the Mac in MARC’s response path. |
| Listening ports | TCP listening endpoints and process identity/ownership | lsof -nP -a -iTCP -sTCP:LISTEN -Fpcn; ps -axo pid=,uid=,ppid=,lstart=,comm=; eligible stops use SIGTERM | Services refresh; stop after confirmation and identity rechecks | List listeners and stop eligible account-owned processes with protected-process checks |
| Homebrew services | Service names and states | brew services list --json; brew services run <name>; brew services stop --keep --max-wait=5 <name> | Services refresh; start/stop on user action | Manage supported existing services. Start does not add launch-at-login registration; Stop preserves it. |
| Docker | Local socket selection; container ID, name, image, state/status | docker context inspect --format '{{json .Endpoints.docker.Host}}' when needed; docker --host <unix-socket> ps -a --no-trunc --format '{{json .}}'; start <id> or stop --timeout 10 <id> | Docker refresh; start/stop on user action after state and host checks | Manage eligible existing local containers; remote TCP Docker hosts are rejected |
| Local AI | Installed model names; your prompt, selected model and command-generation instructions | Python urllib.request over SSH to http://127.0.0.1:11434/api/tags or /api/chat on the Mac | Model refresh or user-requested generation | Ask configured Ollama for a Terminal command suggestion; no automatic execution |
| API AI | Submitted prompt, model, instructions, selected API key and ordinary request metadata | iPhone HTTPS via URLSession to OpenAI, Google Gemini, Groq, or a custom endpoint; separate model-catalog request | Generation after destination consent; catalog loading after separate approval | Suggest a Terminal command. No built-in Mac command or AI proxy is involved in API requests. |
Controls use supported capabilities where available. Some paths call private macOS frameworks and can change or fail between macOS versions. MARC does not install optional blueutil, media-control, Homebrew, Docker, or Ollama automatically. Review setup commands before running them.
What Activity Monitor may show
Activity Monitor may show SSH sessions, Python collectors, short-lived commands such as ps, vm_stat and ioreg, an SFTP server, and your Terminal programs. Names vary with macOS and your installation: examples include sshd, sshd-session, Python, python3, sftp-server, and zsh.
MARC does not hide its collectors from the process list. A reader or its subprocess can appear there too. Check its account, identity, and timing; a process name alone does not identify it as MARC’s.
Storage, retention, and cleanup
The collector tries to save monitoring JSON about every five seconds in ~/.local/share/marc/history.sqlite. The marc_samples_v1 table stores a timestamp and payload under the SSH account. New history files and directories use private permissions; existing directory permissions are not automatically repaired.
Samples contain CPU and per-core activity, memory, storage, network/disk counters, interface and time. They also include hostname, username, local IP, hardware/OS details, core/process counts, uptime, load, swap, and available power/health readings. They do not include process lists, Terminal transcripts, file contents, control/service results, or AI prompts.
On its first successful save, then about once a minute while active, the collector removes its own rows older than two hours. Deletion is not scheduled after disconnect. Legacy samples rows remain readable. Failed writes retry later without stopping valid live readings.
Removing a profile or uninstalling MARC does not delete history on the Mac. Stop MARC sessions using that account before removing the database separately. Deletion does not securely erase disk pages, backups, or administrator logs.
Temporary files and iPhone storage
- Process streams:
~/.local/share/marc/process-stream-<UUID>is an empty stop marker, not an executable. The loop checks its existence. On cancellation MARC attempts to unlink it using another SSH command; the reader also removes it on normal exit. A broken connection or abrupt termination can leave a marker. Removing an abandoned marker stops a reader only if it is still running and checking that marker. - Uploads and edits: Mac destination folders can contain
.marc-upload-<UUID>.partand.marc-edit-input-<UUID>.partstaging files. Text replacement can also create a.marc-edit-temporary file. Helpers attempt private staging permissions and cleanup. An interrupted operation can leave content behind; check its outcome before deleting leftovers or retrying. - Folder archives: Python creates a
marc-folder-directory under the Mac account’s temporary directory, containing a.tar.gz. MARC attempts cleanup after transfer or preparation failure. If the app cannot receive the archive path or contact the Mac again, removal may not complete. No MARC scheduled sweeper is installed on the Mac. - iPhone copies: previews, downloads and app-imported uploads use MARC-owned temporary storage with iOS Complete Protection and backup exclusion. Cleanup is attempted when the last transfer/preview/share owner releases its copy, and at startup for recognized abandoned folders. Failures or system interruption can leave copies until later cleanup or system removal. Exported Files or other-app copies are independent; MARC does not delete them.
- Other local state: profiles/preferences use app storage; saved passwords, SSH keys/passphrases, API keys and trusted host identities use device-only, unlocked-device Keychain access. Current readings, process/service results, and Terminal display buffers are held in app memory. Shells and services can keep their own logs/history on the Mac. Profile removal is separate from removing reusable keys and fingerprints; uninstalling does not reliably clear Keychain.
See Files, Security & privacy, and the Privacy Policy for storage choices and limits. Only remove files you have identified as abandoned and no longer need.
Permissions and connection lifecycle
Every operation uses the connected Mac account’s permissions. macOS privacy protections, desktop-session requirements, and installed tools’ access still apply. SSH does not bypass these restrictions or create a separate MARC sandbox.
Monitoring and list queries read data, but monitoring also writes history and process readers create stop markers. File changes, termination, controls, app launches, and service actions change the Mac. Stopping a process can lose unsaved work, and a manager may restart it. Disconnecting does not undo completed actions.
When MARC enters the background, it cancels monitoring, pauses Terminal sessions, stops Files work, and attempts to close their connections. Readers tied to a view are canceled when no longer needed. Foreground reconnection starts new work; it does not fill recording gaps. Terminal text can remain in memory after the remote session ends.
Cancellation and remote process exit do not happen at the same instant. The monitoring collector exits when its output raises a broken-pipe error; process readers also use stop markers. Network loss, a blocked command, or delayed SSH teardown can delay cleanup.
Your programs may stop or continue after disconnection, depending on their configuration. MARC does not guarantee persistent Terminal jobs or automatically stop services and containers you started. Check the outcome before retrying an interrupted action.
What can leave your devices?
Monitoring, Files, Terminal and control replies travel directly over SSH between your devices, with no MARC-operated relay. Discovery and hostname lookup also use local or network naming services.
AI starts Off and is available only for Terminal command suggestions. Local mode sends your prompt, model and instructions over SSH to Ollama on the Mac’s loopback address. Its configuration determines further logs and network activity. API mode sends them from iPhone to your chosen OpenAI, Google Gemini, Groq, or custom HTTPS endpoint, including the selected API key and normal request metadata such as your public IP.
The first API prompt to an endpoint in a Terminal session needs approval. Later prompts still require your action; connection or relevant settings changes reset approval. Loading a provider’s model list asks separately and sends no prompt. Terminal output, files and Mac credentials are not attached automatically, but anything pasted into your prompt is sent. Review suggestions at an empty shell prompt before choosing Run.
Commands you run, tool installation, launched apps, and services or containers can make their own network connections. File providers may fetch cloud content; sharing destinations receive the copies you send. See Local & API AI and the Privacy Policy for recipients, consent and retention.
Why use this approach?
SSH uses macOS Remote Login and your existing account over an encrypted connection. macOS tools provide the readings without a separately installed MARC monitoring agent. The source is open for inspection.
Python must be available, your Mac must stay reachable, and permissions and hardware support vary. Collectors, commands, history writes, encryption and transfers use resources. AI models and programs you start add their own load. MARC does not claim zero overhead or a measured performance figure.
For connection or collection problems, see Troubleshooting.