Skip to content

dimamishchenko/widgetctl

v0.2.1MIT

Set up iOS Simulator Home Screen widgets with a local MCP server, CLI and API.

Add, edit and remove a widget with widgetctl

widgetctl

Set up iOS Simulator widgets without clicking through the widget gallery.

  • 🎯 Pick installed apps, widgets and sizes in your terminal
  • ⚙️ Edit widget settings with typed controls and native choices
  • 🤖 Give agents a skill and local MCP tools
  • 🧪 Automate setup with TypeScript and Playwright
  • 💾 Export and restore saved configuration

Tests MIT

Requires macOS, Node 22.14+ and full Xcode. Install your app on a simulator and open it once so its widgets can register.

Install

In your project:

npm install --save-dev widgetctl
npx widgetctl

Or install globally:

npm install --global widgetctl
widgetctl

Agents

The plugin bundles the skill, CLI and local MCP server. Add the Codex marketplace, then install widgetctl from it in the desktop Plugins directory:

codex plugin marketplace add DimaMishchenko/widgetctl

For Claude Code:

/plugin marketplace add DimaMishchenko/widgetctl
/plugin install widgetctl@widgetctl

For a standalone skill, use npx skills add DimaMishchenko/widgetctl --skill widgetctl (Node 22.20+). No separate npm installation is needed for the bundled CLI. Agent setup and Tessl listing →

CLI

Choose a simulator, then Add widget, Edit widget or Remove widget. Pickers show app names, widgets and supported sizes. Review staged settings and choose Save changes. More includes import/export and diagnostics.

For scripts, set SIMULATOR_UDID to your owned, booted simulator and use app/kind values from catalog:

npx widgetctl catalog --device "$SIMULATOR_UDID" --json
npx widgetctl add --device "$SIMULATOR_UDID" \
  --app com.example.weather --kind Forecast --size medium --json

JSON mode never prompts. CLI and configuration guide →

TypeScript

Use your installed widget's identifiers from the catalog:

import { createWidgetController } from "widgetctl";

const widgets = createWidgetController({ udid: process.env.SIMULATOR_UDID! });
try {
  await widgets.prepare();
  const widget = await widgets.ensure({
    extension: "com.example.weather.widgets",
    kind: "Forecast",
    size: "medium",
  });
  console.log(await widget.exportConfiguration());
} finally {
  await widgets.release();
}

ensure reuses a matching widget. Handles can describe, export and apply supported settings, or remove that exact widget. Runnable API examples →

Playwright

Install npm install --save-dev widgetctl @playwright/test, then use the exported fixture:

import { test, expect } from "widgetctl/playwright";

test("forecast widget is configured", async ({ widgets }) => {
  const widget = await widgets.ensure({
    extension: "com.example.weather.widgets",
    kind: "Forecast",
    size: "medium",
  });
  const settings = await widget.exportConfiguration();
  const applied = await widget.applyConfiguration(settings);
  expect(await widget.exportConfiguration()).toEqual(applied);
});

Set workers: 1 and use.widgetctlOptions.udid in your Playwright config. The fixture prepares and releases automatically. Saved configuration checks need a native UI driver alongside them to verify rendering. Playwright examples →

Local MCP

Plugins register the bundled server. For another MCP client, install globally and register widgetctl-mcp as a local stdio command. No account or hosted server is needed. Configuration and tools →

Support

Experimental; uses Apple private APIs. Live coverage includes arm64 iPhone simulators on iOS 26.0, 27.0 and Duo 27.1. Other runtimes, iPad and Intel are unverified. Physical devices, stacks and Lock Screen widgets are unsupported.

Own one simulator per worker. Preparation and release may restart its Home Screen. Release in finally and connect native UI drivers after preparation. For reproducible installs, pin widgetctl@0.2.1 and keep your lockfile.

Usage · Troubleshooting · Releases · Privacy · Contributing