ASC supports a plugin system based on compiled .plugin bundles (dylibs) that extend the CLI with server routes, UI components, CLI commands, and domain affordances. Plugins are discovered from ~/.asc/plugins/ at startup.
For browsing and installing plugins from a marketplace, see Plugin Market.
~/.asc/plugins/ASCPro.plugin/
├── manifest.json # metadata: name, version, server dylib, UI scripts
├── ASCPro.dylib # compiled dynamic library
└── ui/
└── sim-stream.js # web UI scripts (loaded by command-center)
{
"name": "ASC Pro",
"version": "1.0",
"server": "ASCPro.dylib",
"ui": ["ui/sim-stream.js"]
}List installed dylib plugins.
asc plugins list --prettyInstall a plugin from the marketplace.
asc plugins install --name asc-proRemove an installed plugin bundle.
asc plugins uninstall --name ASCProBrowse all available plugins. See Plugin Market.
Search marketplace by keyword. See Plugin Market.
List installed plugins that have a newer version available in the marketplace. Inspired by Sparkle's appcast — each entry pairs the installed version with the latest marketplace version.
asc plugins updates --pretty{
"data": [
{
"affordances": {
"list": "asc plugins updates",
"update": "asc plugins update --name Hello"
},
"installedVersion": "1.0.0",
"latestVersion": "1.2.0",
"name": "Hello"
}
]
}Apply a marketplace update by uninstalling the named plugin and reinstalling the latest version. Returns the freshly installed Plugin record.
asc plugins update --name HelloThe same operations are reachable over HTTP via asc web-server:
| CLI | REST | Body / Query |
|---|---|---|
asc plugins list |
GET /api/v1/plugins |
— |
asc plugins install --name X |
POST /api/v1/plugins |
{ "name": "X" } |
asc plugins uninstall --name X |
DELETE /api/v1/plugins/:name |
— (returns 204) |
asc plugins market list |
GET /api/v1/plugins/market |
— |
asc plugins market search --query Q |
GET /api/v1/plugins/market?q=Q |
— |
asc plugins updates |
GET /api/v1/plugins/updates |
— |
asc plugins update --name X |
POST /api/v1/plugins/:name/update |
— |
Example:
# Install
curl -X POST http://localhost:5173/api/v1/plugins \
-H 'content-type: application/json' \
-d '{"name":"Hello.plugin"}'
# Search
curl "http://localhost:5173/api/v1/plugins/market?q=hello"
# Uninstall
curl -X DELETE http://localhost:5173/api/v1/plugins/Hello.plugin
# Check for updates (Sparkle-style appcast)
curl http://localhost:5173/api/v1/plugins/updates
# Apply an update
curl -X POST http://localhost:5173/api/v1/plugins/Hello/updatePlugins export a C entry point and conform to ASCPluginBase:
@_cdecl("ascPlugin")
public func ascPlugin() -> UnsafeMutableRawPointer {
Unmanaged.passRetained(MyPlugin()).toOpaque()
}
public final class MyPlugin: NSObject, ASCPluginBase {
public let name = "My Plugin"
public var commands: [Any] { [] }
public func configureRoutes(_ router: Any) {
// Register HTTP/WebSocket routes
}
}Plugins extend domain model affordances at runtime using structured Affordance values that render to both CLI commands and REST _links:
AffordanceRegistry.register(Simulator.self) { id, props in
guard props["isBooted"] == "true" else { return [] }
return [Affordance(key: "stream", command: "simulators", action: "stream", params: ["udid": id])]
}This produces:
- CLI:
"stream": "asc simulators stream --udid <id>" - REST:
"stream": {"href": "/api/v1/simulators/<id>/stream", "method": "POST"}
PluginLoader.discover()
→ scans ~/.asc/plugins/ for .plugin, .framework, .dylib
→ loads via dlopen/dlsym("ascPlugin")
→ returns [LoadedPlugin] with name, slug, uiScripts
ASCWebServer.buildRouter()
→ calls plugin.configureRoutes(routerPtr)
→ serves plugin UI scripts at /api/plugins/{slug}/ui/*
→ GET /api/plugins returns manifest list for web app