Help us improve
Share bugs, ideas, or general feedback.
Auto-discovered marketplace from koher/swift-cui
npx claudepluginhub koher/swift-cuiSwiftCUI framework guide — build terminal-based interactive applications with a SwiftUI-like declarative architecture over TCP
Share bugs, ideas, or general feedback.
SwiftCUI is a framework for building character user interfaces in Swift with a declarative style inspired by SwiftUI.
Instead of driving the UI through an interactive terminal session, a SwiftCUI app runs as a TCP server and is controlled through request/response commands. That makes it a good fit for both human operators and AI agents that need deterministic, scriptable interactions.
This repository now ships both a plugin manifest and a marketplace manifest under .claude-plugin.
swiftcuikoherTo add the marketplace from GitHub and install the plugin:
/plugin marketplace add koher/swift-cui
/plugin install swiftcui@koher
scui script in the SwiftCUI skill for inspecting and operating a running appViewView renders a Componentbody returns a Component, not another ViewAction valuesscui scriptperform(_:)navigator.present(), navigator.dismiss(), and navigator.dismissAll()TabActionHandler and navigator.activateStack(_:)Applicationimport SwiftCUI
struct SearchView: View {
@Environment(\.navigator) var navigator
let viewModel = SearchViewModel()
var body: some Component {
"# Search"
""
if viewModel.isLoading {
"Searching..."
} else if let error = viewModel.errorMessage {
"Error: \(error)"
} else if viewModel.items.isEmpty {
"No results. Use the search action."
} else {
for (index, item) in viewModel.items.enumerated() {
"\(index). \(item.name)"
}
}
}
enum Action: ActionProtocol {
case search(query: String)
case detail(index: Int)
var hint: String {
switch self {
case .search: "Search items"
case .detail: "Open detail"
}
}
}
var actions: [Action] {
var result: [Action] = [.search(query: "...")]
if !viewModel.items.isEmpty {
result.append(.detail(index: 0))
}
return result
}
func onAppear() async {
await viewModel.loadInitialData()
}
func perform(_ action: Action) async {
switch action {
case .search(let query):
viewModel.query = query
await viewModel.search()
case .detail(let index):
guard viewModel.items.indices.contains(index) else { return }
navigator.present(DetailView(id: viewModel.items[index].id))
}
}
}
Use the View lifecycle hooks according to their role:
onAppear(): work required before the first rendered responsetask(): long-running background work after the response is returnedonDisappear(): cleanup when the view leaves the stackIn practice, initial data loading usually belongs in onAppear(). Use task() for observation, polling, or subscriptions that should continue in the background.
Actions typically use a Codable enum that conforms to ActionProtocol.
enum Action: ActionProtocol {
case refresh
case search(query: String)
case detail(index: Int)
var hint: String {
switch self {
case .refresh: "Refresh data"
case .search: "Search items"
case .detail: "Open detail"
}
}
}
Only return actions that are actually available in the current UI state. SwiftCUI renders each action as JSON plus its hint, so the user or agent can invoke it directly.
Get the navigator from the environment:
@Environment(\.navigator) var navigator
Then navigate with:
navigator.present(DetailView(id: item.id))
navigator.dismiss()
navigator.dismissAll()
For multiple top-level screens, define a TabActionHandler.
import SwiftCUI
struct Tabs: TabActionHandler {
@Environment(\.navigator) var navigator
enum Action: String, ActionProtocol, Hashable {
case search, trending, user
var hint: String {
switch self {
case .search: "Go to Search"
case .trending: "Go to Trending"
case .user: "Go to User"
}
}
}