style(macos): tidy settings and CLI

This commit is contained in:
Peter Steinberger
2025-12-13 19:23:41 +00:00
parent 02fe19effa
commit 0b990443de
13 changed files with 116 additions and 89 deletions

View File

@@ -38,4 +38,3 @@ enum ClawdisConfigFile {
self.saveDict(root)
}
}

View File

@@ -203,17 +203,17 @@ struct ConfigSettings: View {
.toggleStyle(.checkbox)
.disabled(!self.browserEnabled)
.onChange(of: self.browserAttachOnly) { _, _ in self.autosaveConfig() }
.help("When enabled, the browser server will only connect if the clawd browser is already running.")
.help(
"When enabled, the browser server will only connect if the clawd browser is already running.")
}
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text(
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it wont interfere with your daily browser."
)
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it wont interfere with your daily browser.")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}

View File

@@ -525,7 +525,8 @@ private struct CronJobEditor: View {
VStack(alignment: .leading, spacing: 6) {
Text(self.job == nil ? "New cron job" : "Edit cron job")
.font(.title3.weight(.semibold))
Text("Create a schedule that wakes clawd via the Gateway. Use an isolated session for agent turns so your main chat stays clean.")
Text(
"Create a schedule that wakes clawd via the Gateway. Use an isolated session for agent turns so your main chat stays clean.")
.font(.callout)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
@@ -570,7 +571,8 @@ private struct CronJobEditor: View {
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text("Main jobs post a system event into the current main session. Isolated jobs run clawd in a dedicated session and can deliver results (WhatsApp/Telegram/etc).")
Text(
"Main jobs post a system event into the current main session. Isolated jobs run clawd in a dedicated session and can deliver results (WhatsApp/Telegram/etc).")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -594,7 +596,8 @@ private struct CronJobEditor: View {
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text("“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression.")
Text(
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression.")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -604,7 +607,10 @@ private struct CronJobEditor: View {
case .at:
GridRow {
self.gridLabel("At")
DatePicker("", selection: self.$atDate, displayedComponents: [.date, .hourAndMinute])
DatePicker(
"",
selection: self.$atDate,
displayedComponents: [.date, .hourAndMinute])
.labelsHidden()
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -635,7 +641,8 @@ private struct CronJobEditor: View {
GroupBox("Payload") {
VStack(alignment: .leading, spacing: 10) {
if self.sessionTarget == .isolated {
Text("Isolated jobs always run an agent turn. The result can be delivered to a surface, and a short summary is posted back to your main chat.")
Text(
"Isolated jobs always run an agent turn. The result can be delivered to a surface, and a short summary is posted back to your main chat.")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
@@ -655,7 +662,8 @@ private struct CronJobEditor: View {
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text("System events are injected into the current main session. Agent turns require an isolated session target.")
Text(
"System events are injected into the current main session. Agent turns require an isolated session target.")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -687,7 +695,8 @@ private struct CronJobEditor: View {
GridRow {
Color.clear
.frame(width: self.labelColumnWidth, height: 1)
Text("Controls the label used when posting the completion summary back to the main session.")
Text(
"Controls the label used when posting the completion summary back to the main session.")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -45,13 +45,13 @@ struct MenuContent: View {
WebChatManager.shared.show(sessionKey: WebChatManager.shared.preferredSessionKey())
}
}
Toggle(isOn: Binding(
get: { self.browserControlEnabled },
set: { enabled in
self.browserControlEnabled = enabled
ClawdisConfigFile.setBrowserControlEnabled(enabled)
})
) {
Toggle(
isOn: Binding(
get: { self.browserControlEnabled },
set: { enabled in
self.browserControlEnabled = enabled
ClawdisConfigFile.setBrowserControlEnabled(enabled)
})) {
Text("Browser Control")
}
Toggle(isOn: Binding(get: { self.state.canvasEnabled }, set: { self.state.canvasEnabled = $0 })) {
@@ -110,7 +110,9 @@ struct MenuContent: View {
await self.reloadSessionMenu()
}
} label: {
Label(level.capitalized, systemImage: row.thinkingLevel == normalized ? "checkmark" : "")
Label(
level.capitalized,
systemImage: row.thinkingLevel == normalized ? "checkmark" : "")
}
}
}
@@ -126,7 +128,9 @@ struct MenuContent: View {
await self.reloadSessionMenu()
}
} label: {
Label(level.capitalized, systemImage: row.verboseLevel == normalized ? "checkmark" : "")
Label(
level.capitalized,
systemImage: row.verboseLevel == normalized ? "checkmark" : "")
}
}
}

View File

@@ -53,7 +53,8 @@ final class PeekabooBridgeHostCoordinator {
self.host = host
await host.start()
self.logger.info("PeekabooBridge host started at \(PeekabooBridgeConstants.clawdisSocketPath, privacy: .public)")
self.logger
.info("PeekabooBridge host started at \(PeekabooBridgeConstants.clawdisSocketPath, privacy: .public)")
}
}

View File

@@ -28,4 +28,3 @@ extension View {
self.modifier(PointingHandCursorModifier())
}
}

View File

@@ -15,4 +15,3 @@ enum ScreenshotSize {
return Size(width: width, height: height)
}
}

View File

@@ -224,26 +224,26 @@ struct ToolsSettings: View {
EmptyView()
} else {
VStack(alignment: .leading, spacing: 10) {
Text(title)
.font(.callout.weight(.semibold))
.padding(.top, 6)
Text(title)
.font(.callout.weight(.semibold))
.padding(.top, 6)
VStack(spacing: 8) {
ForEach(filtered) { tool in
ToolRow(
tool: tool,
state: self.binding(for: tool),
packageManager: self.packageManager,
refreshState: { await self.refresh(tool: tool) })
.padding(10)
.background(Color(nsColor: .controlBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.secondary.opacity(0.15), lineWidth: 1))
VStack(spacing: 8) {
ForEach(filtered) { tool in
ToolRow(
tool: tool,
state: self.binding(for: tool),
packageManager: self.packageManager,
refreshState: { await self.refresh(tool: tool) })
.padding(10)
.background(Color(nsColor: .controlBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.secondary.opacity(0.15), lineWidth: 1))
}
}
}
}
}
}

View File

@@ -639,9 +639,9 @@ enum CommandResolver {
return URL(fileURLWithPath: expanded)
}
#if SWIFT_PACKAGE
#if SWIFT_PACKAGE
static func _testNodeManagerBinPaths(home: URL) -> [String] {
self.nodeManagerBinPaths(home: home)
}
#endif
#endif
}

View File

@@ -36,8 +36,8 @@ struct GatewayChatMessage: Codable, Identifiable {
id: UUID = .init(),
role: String,
content: [GatewayChatMessageContent],
timestamp: Double?
) {
timestamp: Double?)
{
self.id = id
self.role = role
self.content = content
@@ -124,6 +124,7 @@ final class WebChatViewModel: ObservableObject {
private var pendingRuns = Set<String>() {
didSet { self.pendingRunCount = self.pendingRuns.count }
}
private var lastHealthPollAt: Date?
init(sessionKey: String) {
@@ -162,7 +163,8 @@ final class WebChatViewModel: ObservableObject {
do {
let data = try await Task.detached { try Data(contentsOf: url) }.value
guard data.count <= 5_000_000 else {
await MainActor.run { self.errorText = "Attachment \(url.lastPathComponent) exceeds 5 MB limit" }
await MainActor
.run { self.errorText = "Attachment \(url.lastPathComponent) exceeds 5 MB limit" }
continue
}
let uti = UTType(filenameExtension: url.pathExtension) ?? .data
@@ -445,7 +447,8 @@ struct WebChatView: View {
.foregroundStyle(Color.accentColor.opacity(0.9))
Text("Say hi to Clawd")
.font(.headline)
Text(self.viewModel.healthOK ? "This is the native SwiftUI debug chat." : "Connecting to the gateway…")
Text(self.viewModel
.healthOK ? "This is the native SwiftUI debug chat." : "Connecting to the gateway…")
.font(.subheadline)
.foregroundStyle(.secondary)
}
@@ -458,7 +461,9 @@ struct WebChatView: View {
} else {
ForEach(self.viewModel.messages) { msg in
MessageBubble(message: msg)
.frame(maxWidth: .infinity, alignment: msg.role.lowercased() == "user" ? .trailing : .leading)
.frame(
maxWidth: .infinity,
alignment: msg.role.lowercased() == "user" ? .trailing : .leading)
}
}
@@ -673,7 +678,7 @@ private struct ChatMessageBody: View {
switch block.kind {
case .text:
MarkdownTextView(text: block.text)
case .code(let language):
case let .code(language):
CodeBlockView(code: block.text, language: language)
}
}
@@ -747,7 +752,7 @@ private struct AttachmentRow: View {
var body: some View {
HStack(spacing: 8) {
Image(systemName: "paperclip")
Text(att.fileName ?? "Attachment")
Text(self.att.fileName ?? "Attachment")
.font(.footnote)
.lineLimit(1)
Spacer()

View File

@@ -83,25 +83,34 @@ enum BrowserCLI {
do {
switch sub {
case "status":
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(method: "GET", url: baseURL.appendingPathComponent("/")))
res: self.httpJSON(method: "GET", url: baseURL.appendingPathComponent("/")))
return 0
case "start":
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(method: "POST", url: baseURL.appendingPathComponent("/start"), timeoutInterval: 15.0))
res: self.httpJSON(
method: "POST",
url: baseURL.appendingPathComponent("/start"),
timeoutInterval: 15.0))
return 0
case "stop":
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(method: "POST", url: baseURL.appendingPathComponent("/stop"), timeoutInterval: 15.0))
res: self.httpJSON(
method: "POST",
url: baseURL.appendingPathComponent("/stop"),
timeoutInterval: 15.0))
return 0
case "tabs":
let res = try await self.httpJSON(method: "GET", url: baseURL.appendingPathComponent("/tabs"), timeoutInterval: 3.0)
let res = try await self.httpJSON(
method: "GET",
url: baseURL.appendingPathComponent("/tabs"),
timeoutInterval: 3.0)
if jsonOutput {
self.printJSON(ok: true, result: res)
} else {
@@ -114,9 +123,9 @@ enum BrowserCLI {
self.printHelp()
return 2
}
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(
res: self.httpJSON(
method: "POST",
url: baseURL.appendingPathComponent("/tabs/open"),
body: ["url": url],
@@ -128,9 +137,9 @@ enum BrowserCLI {
self.printHelp()
return 2
}
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(
res: self.httpJSON(
method: "POST",
url: baseURL.appendingPathComponent("/tabs/focus"),
body: ["targetId": id],
@@ -142,9 +151,9 @@ enum BrowserCLI {
self.printHelp()
return 2
}
self.printResult(
try await self.printResult(
jsonOutput: jsonOutput,
res: try await self.httpJSON(
res: self.httpJSON(
method: "DELETE",
url: baseURL.appendingPathComponent("/tabs/\(id)"),
timeoutInterval: 5.0))
@@ -345,8 +354,8 @@ enum BrowserCLI {
method: String,
url: URL,
body: [String: Any]? = nil,
timeoutInterval: TimeInterval = 2.0
) async throws -> [String: Any] {
timeoutInterval: TimeInterval = 2.0) async throws -> [String: Any]
{
var req = URLRequest(url: url, timeoutInterval: timeoutInterval)
req.httpMethod = method
if let body {
@@ -369,7 +378,7 @@ enum BrowserCLI {
])
}
if status >= 200 && status < 300 {
if status >= 200, status < 300 {
return obj
}
@@ -517,11 +526,11 @@ enum BrowserCLI {
}
}
#if SWIFT_PACKAGE
#if SWIFT_PACKAGE
static func _testFormatTabs(res: [String: Any]) -> [String] {
self.formatTabs(res: res)
}
#endif
#endif
private static func printJSON(ok: Bool, result: Any) {
let obj: [String: Any] = ["ok": ok, "result": result]

View File

@@ -1,5 +1,5 @@
import Foundation
import Darwin
import Foundation
import PeekabooAutomationKit
import PeekabooBridge
import PeekabooFoundation
@@ -94,7 +94,7 @@ enum UICLI {
private static func runPermissions(args: [String], jsonOutput: Bool, context: Context) async throws -> Int32 {
let sub = args.first ?? "status"
if sub != "status" && sub != "--help" && sub != "-h" && sub != "help" {
if sub != "status", sub != "--help", sub != "-h", sub != "help" {
self.printHelp()
return 1
}
@@ -103,7 +103,7 @@ enum UICLI {
try self.writeJSON([
"ok": true,
"host": context.hostDescription,
"result": try self.toJSONObject(status),
"result": self.toJSONObject(status),
])
} else {
FileHandle.standardOutput.write(Data((self.formatPermissions(status) + "\n").utf8))
@@ -123,7 +123,7 @@ enum UICLI {
try self.writeJSON([
"ok": true,
"host": context.hostDescription,
"app": try self.toJSONObject(app),
"app": self.toJSONObject(app),
"window": windowObject,
])
} else {
@@ -131,7 +131,7 @@ enum UICLI {
let line = "\(bundle) (pid \(app.processIdentifier))"
FileHandle.standardOutput.write(Data((line + "\n").utf8))
if let window {
FileHandle.standardOutput.write(Data(("window \(window.windowID): \(window.title)\n").utf8))
FileHandle.standardOutput.write(Data("window \(window.windowID): \(window.title)\n".utf8))
}
}
return 0
@@ -143,12 +143,12 @@ enum UICLI {
try self.writeJSON([
"ok": true,
"host": context.hostDescription,
"result": try self.toJSONObject(apps),
"result": self.toJSONObject(apps),
])
} else {
for app in apps {
let bundle = app.bundleIdentifier ?? "<unknown>"
FileHandle.standardOutput.write(Data(("\(bundle)\t\(app.name)\n").utf8))
FileHandle.standardOutput.write(Data("\(bundle)\t\(app.name)\n".utf8))
}
}
return 0
@@ -176,11 +176,11 @@ enum UICLI {
try self.writeJSON([
"ok": true,
"host": context.hostDescription,
"result": try self.toJSONObject(windows),
"result": self.toJSONObject(windows),
])
} else {
for window in windows {
FileHandle.standardOutput.write(Data(("\(window.windowID)\t\(window.title)\n").utf8))
FileHandle.standardOutput.write(Data("\(window.windowID)\t\(window.title)\n".utf8))
}
}
return 0
@@ -217,20 +217,19 @@ enum UICLI {
}
}
let capture: CaptureResult
if let bundleId, !bundleId.isEmpty {
capture = try await context.client.captureWindow(
let capture: CaptureResult = if let bundleId, !bundleId.isEmpty {
try await context.client.captureWindow(
appIdentifier: bundleId,
windowIndex: windowIndex,
visualizerMode: mode,
scale: scale)
} else if displayIndex != nil {
capture = try await context.client.captureScreen(
try await context.client.captureScreen(
displayIndex: displayIndex,
visualizerMode: mode,
scale: scale)
} else {
capture = try await context.client.captureFrontmost(visualizerMode: mode, scale: scale)
try await context.client.captureFrontmost(visualizerMode: mode, scale: scale)
}
let path = try self.writeTempPNG(capture.imageData)
@@ -240,7 +239,7 @@ enum UICLI {
"ok": true,
"host": context.hostDescription,
"path": path,
"metadata": try self.toJSONObject(capture.metadata),
"metadata": self.toJSONObject(capture.metadata),
"warning": capture.warning ?? "",
])
} else {
@@ -287,7 +286,8 @@ enum UICLI {
let resolvedSnapshotId: String = if let snapshotId, !snapshotId.isEmpty {
snapshotId
} else if let bundleId, !bundleId.isEmpty, let existing = try? await context.client
.getMostRecentSnapshot(applicationBundleId: bundleId) {
.getMostRecentSnapshot(applicationBundleId: bundleId)
{
existing
} else {
try await context.client.createSnapshot()
@@ -321,7 +321,7 @@ enum UICLI {
"host": context.hostDescription,
"snapshotId": resolvedSnapshotId,
"screenshotPath": screenshotPath,
"result": try self.toJSONObject(detection),
"result": self.toJSONObject(detection),
])
} else {
FileHandle.standardOutput.write(Data((screenshotPath + "\n").utf8))
@@ -494,7 +494,7 @@ enum UICLI {
try self.writeJSON([
"ok": true,
"host": context.hostDescription,
"result": try self.toJSONObject(result),
"result": self.toJSONObject(result),
])
} else {
FileHandle.standardOutput.write(Data((result.found ? "found\n" : "not found\n").utf8))
@@ -549,7 +549,7 @@ enum UICLI {
return "\(sr) \(ax) \(ascr)"
}
private static func toJSONObject<T: Encodable>(_ value: T) throws -> Any {
private static func toJSONObject(_ value: some Encodable) throws -> Any {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try encoder.encode(value)

View File

@@ -15,8 +15,10 @@
"clawdis": "tsx src/index.ts",
"clawdis:rpc": "tsx src/index.ts agent --mode rpc --json",
"lint": "biome check src",
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
"lint:fix": "biome check --write --unsafe src && biome format --write src",
"format": "biome format src",
"format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdisKit/Sources",
"format:fix": "biome format src --write",
"test": "vitest",
"test:force": "tsx scripts/test-force.ts",