Skip to content

Building Machine-Aware Plugins

Machine-aware plugins can interact with the user's local computer through PIE Connect. This guide shows how to declare machine capabilities in your manifest and use the context.machine API.

Declaring Machine Capabilities

Add a machineCapabilities array to your plugin manifest:

yaml
name: my-machine-plugin
displayName: My Machine Plugin
description: A plugin that reads machine info and clipboard
tier: tool
version: 1.0.0
manifest:
  trigger: auto
  machineCapabilities:
    - machine.info
    - clipboard.read
  tool:
    name: my_machine_tool
    description: Reads machine info and clipboard
    parameters:
      type: object
      properties:
        action:
          type: string
          enum: [info, clipboard]
      required: [action]

When a user installs this plugin, they'll be asked to approve the requested capabilities.

Available Capabilities

CapabilityRiskDescriptionPlatform
machine.infoLowRead hostname, OS, uptime, memoryAll
clipboard.readMediumRead clipboard contentsAll
clipboard.writeMediumWrite text to the clipboardAll
notifications.sendLowSend desktop notificationsAll
messages.readHighRead iMessagesmacOS
screenshot.captureHighCapture screenshotsmacOS
desktop.controlCriticalControl mouse and keyboardmacOS
shell.runCriticalExecute shell commandsAll
filesystemHighRead, write, list, search, move, copy, delete filesAll
apps.automateHighAutomate macOS apps via scoped AppleScriptmacOS
browser.dataHighRead browser tabs, history, bookmarksmacOS
contacts.readHighRead contacts from macOS ContactsmacOS
calendar.readHighRead events from macOS CalendarmacOS
system.controlMediumVolume, dark mode, wifi, battery, processes, etc.macOS
file.transferHighUpload files from Mac to cloud or download files to Mac (up to 100MB)All

Using the context.machine API

Check if a machine is online

javascript
const online = await context.machine.isOnline();
if (!online) {
  return { error: true, message: 'No machine connected. Install PIE Connect on your Mac.' };
}

Execute a capability

javascript
// Basic machine info
const info = await context.machine.execute('machine.info', {});

// Read clipboard
const clip = await context.machine.execute('clipboard.read', {});

// Write to clipboard
await context.machine.execute('clipboard.write', { text: 'Hello from PIE!' });

// Send notification
await context.machine.execute('notifications.send', {
  title: 'My Plugin',
  message: 'Task completed!'
});

// Read iMessages
const messages = await context.machine.execute('messages.read', { limit: 5 });

// Execute shell command
const result = await context.machine.execute('shell.run', {
  command: 'ls -la ~/Documents',
  timeout: 10000
});

// Read a file
const file = await context.machine.execute('filesystem', {
  action: 'read',
  path: '~/Documents/notes.txt'
});

// List directory
const dir = await context.machine.execute('filesystem', {
  action: 'list',
  path: '~/Desktop',
  recursive: false
});

// Automate an app (Spotify)
const track = await context.machine.execute('apps.automate', {
  app: 'Spotify',
  script: 'get name of current track'
});

// Get browser tabs
const tabs = await context.machine.execute('browser.data', {
  action: 'tabs',
  browser: 'safari'
});

// Read contacts
const contacts = await context.machine.execute('contacts.read', {
  search: 'John',
  limit: 10,
  fields: ['name', 'email', 'phone']
});

// Read calendar events
const events = await context.machine.execute('calendar.read', {
  from: '2025-03-14',
  to: '2025-03-21',
  limit: 20
});

// System info
const battery = await context.machine.execute('system.control', { action: 'battery' });
const volume = await context.machine.execute('system.control', { action: 'volume' });
const apps = await context.machine.execute('system.control', { action: 'running_apps' });

// Set volume
await context.machine.execute('system.control', { action: 'set_volume', level: 50 });

// Toggle dark mode
await context.machine.execute('system.control', { action: 'set_dark_mode', enabled: true });

// Upload a file from Mac to cloud (convenience method)
const uploaded = await context.machine.upload('~/Documents/report.pdf');
console.log(uploaded.fileId, uploaded.url);

// Download a file from cloud to Mac (convenience method)
await context.machine.download('https://example.com/file.pdf', '~/Downloads/file.pdf');

// Upload via execute (equivalent to context.machine.upload)
const result = await context.machine.execute('file.transfer', {
  action: 'upload',
  path: '~/Desktop/screenshot.png'
});

// Download via execute (equivalent to context.machine.download)
await context.machine.execute('file.transfer', {
  action: 'download',
  url: 'https://example.com/data.csv',
  savePath: '~/Documents/data.csv'
});

List registered machines

javascript
const machines = await context.machine.list();
// Returns: [{ machineId, capabilities, connectedAt }]

Handling Offline Machines Gracefully

Always check if the machine is online before executing commands. If the machine is offline, return a helpful error message so the AI can guide the user:

javascript
async function handler(input, context) {
  const online = await context.machine.isOnline();
  if (!online) {
    return {
      error: true,
      message: 'Your machine is not connected. Please make sure PIE Connect is running on your Mac.',
    };
  }
  // ... proceed with capability execution
}

Risk Levels

Capabilities are categorized by risk level, which the user sees during plugin installation:

  • Low — Read-only system info, notifications
  • Medium — Clipboard access, system settings
  • High — File access, app automation, personal data (contacts, calendar, messages, browser)
  • Critical — Shell commands, direct mouse/keyboard control

Security Model

PIE uses a three-layer permission system:

  1. Plugin manifest — Plugins must declare every capability they use in machineCapabilities
  2. User approval — Users see the requested capabilities and risk levels at install time and must approve
  3. Desktop enforcement — The desktop app only executes capabilities from its fixed registry

The apps.automate capability rejects scripts containing: do shell script, run script, system attribute, path to startup disk, do script, POSIX file, call method, store script, load script, scripting additions. Use shell.run for terminal commands and filesystem for file operations.

Every apps.automate response includes a dictionary field containing the app's scripting dictionary (sdef), fetched in parallel and cached. This tells you exactly what commands and classes the app supports — especially useful for fixing scripts after errors.

Error Handling

Machine commands can fail for several reasons:

  • MACHINE_OFFLINE — No machine is connected
  • MACHINE_TIMEOUT — Machine didn't respond within 60 seconds
  • Permission denied — The capability requires additional OS permissions (e.g., Full Disk Access for messages.read)
  • Capability not found — The capability is disabled or not supported
javascript
try {
  const result = await context.machine.execute('messages.read', { limit: 5 });
  return result;
} catch (err) {
  return { error: true, message: err.message };
}

Security Considerations

  • Only request capabilities your plugin actually needs
  • The security scanner will flag plugins that request unnecessary machine capabilities
  • shell.run and desktop.control are critical-risk — users must explicitly enable them
  • All machine commands are logged for auditing
  • apps.automate scripts are sandboxed to tell application blocks

Built with VitePress