chore(swift): run swiftformat and clear swiftlint

This commit is contained in:
Peter Steinberger
2025-12-13 19:53:17 +00:00
parent 39c232548c
commit 6143338116
18 changed files with 713 additions and 723 deletions

View File

@@ -51,8 +51,7 @@ actor BridgeClient {
nodeId: hello.nodeId, nodeId: hello.nodeId,
displayName: hello.displayName, displayName: hello.displayName,
platform: hello.platform, platform: hello.platform,
version: hello.version version: hello.version),
),
over: connection) over: connection)
onStatus?("Waiting for approval…") onStatus?("Waiting for approval…")

View File

@@ -1,5 +1,13 @@
import ClawdisKit
import SwiftUI import SwiftUI
@MainActor
private final class ConnectStatusStore: ObservableObject {
@Published var text: String?
}
extension ConnectStatusStore: @unchecked Sendable {}
struct SettingsTab: View { struct SettingsTab: View {
@EnvironmentObject private var appModel: NodeAppModel @EnvironmentObject private var appModel: NodeAppModel
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@@ -8,7 +16,7 @@ struct SettingsTab: View {
@AppStorage("voiceWake.enabled") private var voiceWakeEnabled: Bool = false @AppStorage("voiceWake.enabled") private var voiceWakeEnabled: Bool = false
@AppStorage("bridge.preferredStableID") private var preferredBridgeStableID: String = "" @AppStorage("bridge.preferredStableID") private var preferredBridgeStableID: String = ""
@StateObject private var discovery = BridgeDiscoveryModel() @StateObject private var discovery = BridgeDiscoveryModel()
@State private var connectStatus: String? @StateObject private var connectStatus = ConnectStatusStore()
@State private var connectingBridgeID: String? @State private var connectingBridgeID: String?
@State private var didAutoConnect = false @State private var didAutoConnect = false
@@ -47,8 +55,8 @@ struct SettingsTab: View {
self.bridgeList(showing: .all) self.bridgeList(showing: .all)
} }
if let connectStatus { if let text = self.connectStatus.text {
Text(connectStatus) Text(text)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@@ -86,13 +94,11 @@ struct SettingsTab: View {
displayName: self.displayName, displayName: self.displayName,
token: existing, token: existing,
platform: self.platformString(), platform: self.platformString(),
version: self.appVersion() version: self.appVersion()))
) self.connectStatus.text = nil
)
self.connectStatus = nil
} }
.onChange(of: self.appModel.bridgeServerName) { _, _ in .onChange(of: self.appModel.bridgeServerName) { _, _ in
self.connectStatus = nil self.connectStatus.text = nil
} }
} }
} }
@@ -178,17 +184,16 @@ struct SettingsTab: View {
displayName: self.displayName, displayName: self.displayName,
token: existingToken, token: existingToken,
platform: self.platformString(), platform: self.platformString(),
version: self.appVersion() version: self.appVersion())
)
let token = try await BridgeClient().pairAndHello( let token = try await BridgeClient().pairAndHello(
endpoint: bridge.endpoint, endpoint: bridge.endpoint,
hello: hello, hello: hello,
onStatus: { status in onStatus: { status in
let store = self.connectStatus
Task { @MainActor in Task { @MainActor in
self.connectStatus = status store.text = status
} }
} })
)
if !token.isEmpty, token != existingToken { if !token.isEmpty, token != existingToken {
_ = KeychainStore.saveString( _ = KeychainStore.saveString(
@@ -204,12 +209,10 @@ struct SettingsTab: View {
displayName: self.displayName, displayName: self.displayName,
token: token, token: token,
platform: self.platformString(), platform: self.platformString(),
version: self.appVersion() version: self.appVersion()))
)
)
} catch { } catch {
self.connectStatus = "Failed: \(error.localizedDescription)" self.connectStatus.text = "Failed: \(error.localizedDescription)"
} }
} }

View File

@@ -4,6 +4,11 @@ import SwiftUI
struct ConfigSettings: View { struct ConfigSettings: View {
private let isPreview = ProcessInfo.processInfo.isPreview private let isPreview = ProcessInfo.processInfo.isPreview
private let labelColumnWidth: CGFloat = 120 private let labelColumnWidth: CGFloat = 120
private static let browserAttachOnlyHelp =
"When enabled, the browser server will only connect if the clawd browser is already running."
private static let browserProfileNote =
"Clawd uses a separate Chrome profile and ports (default 18791/18792) "
+ "so it wont interfere with your daily browser."
@State private var configModel: String = "" @State private var configModel: String = ""
@State private var customModel: String = "" @State private var customModel: String = ""
@State private var configSaving = false @State private var configSaving = false
@@ -203,16 +208,12 @@ struct ConfigSettings: View {
.toggleStyle(.checkbox) .toggleStyle(.checkbox)
.disabled(!self.browserEnabled) .disabled(!self.browserEnabled)
.onChange(of: self.browserAttachOnly) { _, _ in self.autosaveConfig() } .onChange(of: self.browserAttachOnly) { _, _ in self.autosaveConfig() }
.help( .help(Self.browserAttachOnlyHelp)
"When enabled, the browser server will only connect if the clawd browser is already running."
)
} }
GridRow { GridRow {
Color.clear Color.clear
.frame(width: self.labelColumnWidth, height: 1) .frame(width: self.labelColumnWidth, height: 1)
Text( Text(Self.browserProfileNote)
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it wont interfere with your daily browser."
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -71,8 +71,7 @@ struct CronSettings: View {
} }
Text( Text(
"Jobs are saved, but they will not run automatically until `cron.enabled` is set to `true` " + "Jobs are saved, but they will not run automatically until `cron.enabled` is set to `true` " +
"and the Gateway restarts." "and the Gateway restarts.")
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -497,6 +496,21 @@ private struct CronJobEditor: View {
let onSave: ([String: Any]) -> Void let onSave: ([String: Any]) -> Void
private let labelColumnWidth: CGFloat = 160 private let labelColumnWidth: CGFloat = 160
private static let introText =
"Create a schedule that wakes clawd via the Gateway. "
+ "Use an isolated session for agent turns so your main chat stays clean."
private static let sessionTargetNote =
"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)."
private static let scheduleKindNote =
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression."
private static let isolatedPayloadNote =
"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."
private static let mainPayloadNote =
"System events are injected into the current main session. Agent turns require an isolated session target."
private static let mainSummaryNote =
"Controls the label used when posting the completion summary back to the main session."
@State private var name: String = "" @State private var name: String = ""
@State private var enabled: Bool = true @State private var enabled: Bool = true
@@ -527,9 +541,7 @@ private struct CronJobEditor: View {
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(self.job == nil ? "New cron job" : "Edit cron job") Text(self.job == nil ? "New cron job" : "Edit cron job")
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))
Text( Text(Self.introText)
"Create a schedule that wakes clawd via the Gateway. Use an isolated session for agent turns so your main chat stays clean."
)
.font(.callout) .font(.callout)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -575,8 +587,7 @@ private struct CronJobEditor: View {
Color.clear Color.clear
.frame(width: self.labelColumnWidth, height: 1) .frame(width: self.labelColumnWidth, height: 1)
Text( 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)." Self.sessionTargetNote)
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -601,8 +612,7 @@ private struct CronJobEditor: View {
Color.clear Color.clear
.frame(width: self.labelColumnWidth, height: 1) .frame(width: self.labelColumnWidth, height: 1)
Text( Text(
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression." Self.scheduleKindNote)
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -646,9 +656,7 @@ private struct CronJobEditor: View {
GroupBox("Payload") { GroupBox("Payload") {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
if self.sessionTarget == .isolated { if self.sessionTarget == .isolated {
Text( Text(Self.isolatedPayloadNote)
"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) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -669,8 +677,7 @@ private struct CronJobEditor: View {
Color.clear Color.clear
.frame(width: self.labelColumnWidth, height: 1) .frame(width: self.labelColumnWidth, height: 1)
Text( Text(
"System events are injected into the current main session. Agent turns require an isolated session target." Self.mainPayloadNote)
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -703,8 +710,7 @@ private struct CronJobEditor: View {
Color.clear Color.clear
.frame(width: self.labelColumnWidth, height: 1) .frame(width: self.labelColumnWidth, height: 1)
Text( Text(
"Controls the label used when posting the completion summary back to the main session." Self.mainSummaryNote)
)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -149,8 +149,7 @@ struct DebugSettings: View {
.help( .help(
"When enabled in local mode, the mac app will only connect " + "When enabled in local mode, the mac app will only connect " +
"to an already-running gateway " + "to an already-running gateway " +
"and will not start one itself." "and will not start one itself.")
)
} }
GridRow { GridRow {
self.gridLabel("Deep links") self.gridLabel("Deep links")
@@ -237,8 +236,7 @@ struct DebugSettings: View {
.toggleStyle(.checkbox) .toggleStyle(.checkbox)
.help( .help(
"Writes a rotating, local-only diagnostics log under ~/Library/Logs/Clawdis/. " + "Writes a rotating, local-only diagnostics log under ~/Library/Logs/Clawdis/. " +
"Enable only while actively debugging." "Enable only while actively debugging.")
)
HStack(spacing: 8) { HStack(spacing: 8) {
Button("Open folder") { Button("Open folder") {
NSWorkspace.shared.open(DiagnosticsFileLog.logDirectoryURL()) NSWorkspace.shared.open(DiagnosticsFileLog.logDirectoryURL())
@@ -490,8 +488,7 @@ struct DebugSettings: View {
.toggleStyle(.checkbox) .toggleStyle(.checkbox)
.help( .help(
"When off, agent Canvas requests return “Canvas disabled by user”. " + "When off, agent Canvas requests return “Canvas disabled by user”. " +
"Manual debug actions still work." "Manual debug actions still work.")
)
HStack(spacing: 8) { HStack(spacing: 8) {
TextField("Session", text: self.$canvasSessionKey) TextField("Session", text: self.$canvasSessionKey)
@@ -593,8 +590,7 @@ struct DebugSettings: View {
.toggleStyle(.checkbox) .toggleStyle(.checkbox)
.help( .help(
"When enabled, the menu bar chat window/panel uses the native SwiftUI UI instead of the " + "When enabled, the menu bar chat window/panel uses the native SwiftUI UI instead of the " +
"bundled WKWebView." "bundled WKWebView.")
)
} }
} }
} }
@@ -757,7 +753,7 @@ struct DebugSettings: View {
.appendingPathComponent(".clawdis") .appendingPathComponent(".clawdis")
.appendingPathComponent("clawdis.json") .appendingPathComponent("clawdis.json")
} }
} }
extension DebugSettings { extension DebugSettings {
// MARK: - Canvas debug actions // MARK: - Canvas debug actions
@@ -854,8 +850,7 @@ extension DebugSettings {
let session = self.canvasSessionKey.trimmingCharacters(in: .whitespacesAndNewlines) let session = self.canvasSessionKey.trimmingCharacters(in: .whitespacesAndNewlines)
let result = try await CanvasManager.shared.eval( let result = try await CanvasManager.shared.eval(
sessionKey: session.isEmpty ? "main" : session, sessionKey: session.isEmpty ? "main" : session,
javaScript: self.canvasEvalJS javaScript: self.canvasEvalJS)
)
self.canvasEvalResult = result self.canvasEvalResult = result
} catch { } catch {
self.canvasError = error.localizedDescription self.canvasError = error.localizedDescription
@@ -870,8 +865,7 @@ extension DebugSettings {
let session = self.canvasSessionKey.trimmingCharacters(in: .whitespacesAndNewlines) let session = self.canvasSessionKey.trimmingCharacters(in: .whitespacesAndNewlines)
let path = try await CanvasManager.shared.snapshot( let path = try await CanvasManager.shared.snapshot(
sessionKey: session.isEmpty ? "main" : session, sessionKey: session.isEmpty ? "main" : session,
outPath: nil outPath: nil)
)
self.canvasSnapshotPath = path self.canvasSnapshotPath = path
} catch { } catch {
self.canvasError = error.localizedDescription self.canvasError = error.localizedDescription
@@ -879,7 +873,7 @@ extension DebugSettings {
} }
} }
private struct PlainSettingsGroupBoxStyle: GroupBoxStyle { private struct PlainSettingsGroupBoxStyle: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
configuration.label configuration.label
@@ -889,10 +883,10 @@ extension DebugSettings {
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
} }
#if DEBUG #if DEBUG
struct DebugSettings_Previews: PreviewProvider { struct DebugSettings_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
DebugSettings() DebugSettings()
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight) .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight)

View File

@@ -1,7 +1,7 @@
import AppKit import AppKit
import SwiftUI import SwiftUI
struct GeneralSettings: View { struct GeneralSettings: View {
@ObservedObject var state: AppState @ObservedObject var state: AppState
@ObservedObject private var healthStore = HealthStore.shared @ObservedObject private var healthStore = HealthStore.shared
@ObservedObject private var gatewayManager = GatewayProcessManager.shared @ObservedObject private var gatewayManager = GatewayProcessManager.shared

View File

@@ -121,8 +121,7 @@ struct OnboardingView: View {
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
"Your macOS menu bar companion for notifications, screenshots, and agent automation — " + "Your macOS menu bar companion for notifications, screenshots, and agent automation — " +
"setup takes just a few minutes." "setup takes just a few minutes.")
)
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -167,8 +166,7 @@ struct OnboardingView: View {
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
"Clawdis has one primary Gateway (“master”) that runs continuously. " + "Clawdis has one primary Gateway (“master”) that runs continuously. " +
"Connect locally or over SSH/Tailscale so the agent can work on any Mac." "Connect locally or over SSH/Tailscale so the agent can work on any Mac.")
)
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -303,8 +301,7 @@ struct OnboardingView: View {
} else { } else {
Text( Text(
"Uses \"npm install -g clawdis@<version>\" on your PATH. " + "Uses \"npm install -g clawdis@<version>\" on your PATH. " +
"We keep the gateway on port 18789." "We keep the gateway on port 18789.")
)
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(2) .lineLimit(2)

View File

@@ -20,17 +20,17 @@ enum PermissionManager {
private static func ensureCapability(_ cap: Capability, interactive: Bool) async -> Bool { private static func ensureCapability(_ cap: Capability, interactive: Bool) async -> Bool {
switch cap { switch cap {
case .notifications: case .notifications:
return await self.ensureNotifications(interactive: interactive) await self.ensureNotifications(interactive: interactive)
case .appleScript: case .appleScript:
return await self.ensureAppleScript(interactive: interactive) await self.ensureAppleScript(interactive: interactive)
case .accessibility: case .accessibility:
return await self.ensureAccessibility(interactive: interactive) await self.ensureAccessibility(interactive: interactive)
case .screenRecording: case .screenRecording:
return await self.ensureScreenRecording(interactive: interactive) await self.ensureScreenRecording(interactive: interactive)
case .microphone: case .microphone:
return await self.ensureMicrophone(interactive: interactive) await self.ensureMicrophone(interactive: interactive)
case .speechRecognition: case .speechRecognition:
return await self.ensureSpeechRecognition(interactive: interactive) await self.ensureSpeechRecognition(interactive: interactive)
} }
} }
@@ -45,7 +45,8 @@ enum PermissionManager {
guard interactive else { return false } guard interactive else { return false }
let granted = await (try? center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false let granted = await (try? center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false
let updated = await center.notificationSettings() let updated = await center.notificationSettings()
return granted && (updated.authorizationStatus == .authorized || updated.authorizationStatus == .provisional) return granted &&
(updated.authorizationStatus == .authorized || updated.authorizationStatus == .provisional)
case .denied: case .denied:
if interactive { if interactive {
NotificationPermissionHelper.openSettings() NotificationPermissionHelper.openSettings()

View File

@@ -451,8 +451,7 @@ struct WebChatView: View {
Text( Text(
self.viewModel.healthOK self.viewModel.healthOK
? "This is the native SwiftUI debug chat." ? "This is the native SwiftUI debug chat."
: "Connecting to the gateway…" : "Connecting to the gateway…")
)
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }

View File

@@ -105,8 +105,8 @@ enum BrowserCLI {
sub: String, sub: String,
options: RunOptions, options: RunOptions,
baseURL: URL, baseURL: URL,
jsonOutput: Bool jsonOutput: Bool) async throws -> Int32
) async throws -> Int32 { {
switch sub { switch sub {
case "status": case "status":
return try await self.handleStatus(baseURL: baseURL, jsonOutput: jsonOutput) return try await self.handleStatus(baseURL: baseURL, jsonOutput: jsonOutput)
@@ -172,8 +172,7 @@ enum BrowserCLI {
method: "POST", method: "POST",
url: url, url: url,
body: ["url": urlString], body: ["url": urlString],
timeoutInterval: 15.0 timeoutInterval: 15.0)
)
self.printResult(jsonOutput: jsonOutput, res: res) self.printResult(jsonOutput: jsonOutput, res: res)
return 0 return 0
} }
@@ -188,8 +187,7 @@ enum BrowserCLI {
method: "POST", method: "POST",
url: url, url: url,
body: ["targetId": id], body: ["targetId": id],
timeoutInterval: 5.0 timeoutInterval: 5.0)
)
self.printResult(jsonOutput: jsonOutput, res: res) self.printResult(jsonOutput: jsonOutput, res: res)
return 0 return 0
} }
@@ -250,8 +248,7 @@ enum BrowserCLI {
"targetId": options.targetId ?? "", "targetId": options.targetId ?? "",
"await": options.awaitPromise, "await": options.awaitPromise,
], ],
timeoutInterval: 15.0 timeoutInterval: 15.0)
)
if jsonOutput { if jsonOutput {
self.printJSON(ok: true, result: res) self.printJSON(ok: true, result: res)

View File

@@ -123,8 +123,7 @@ struct ClawdisCLI {
guard let t = title, let b = body else { throw CLIError.help } guard let t = title, let b = body else { throw CLIError.help }
return ParsedCLIRequest( return ParsedCLIRequest(
request: .notify(title: t, body: b, sound: sound, priority: priority, delivery: delivery), request: .notify(title: t, body: b, sound: sound, priority: priority, delivery: delivery),
kind: .generic kind: .generic)
)
} }
private static func parseEnsurePermissions(args: inout [String]) -> ParsedCLIRequest { private static func parseEnsurePermissions(args: inout [String]) -> ParsedCLIRequest {
@@ -174,10 +173,8 @@ struct ClawdisCLI {
cwd: cwd, cwd: cwd,
env: env.isEmpty ? nil : env, env: env.isEmpty ? nil : env,
timeoutSec: timeout, timeoutSec: timeout,
needsScreenRecording: needsSR needsScreenRecording: needsSR),
), kind: .generic)
kind: .generic
)
} }
private static func parseEnvPair(_ pair: String, into env: inout [String: String]) { private static func parseEnvPair(_ pair: String, into env: inout [String: String]) {
@@ -212,8 +209,7 @@ struct ClawdisCLI {
guard let message else { throw CLIError.help } guard let message else { throw CLIError.help }
return ParsedCLIRequest( return ParsedCLIRequest(
request: .agent(message: message, thinking: thinking, session: session, deliver: deliver, to: to), request: .agent(message: message, thinking: thinking, session: session, deliver: deliver, to: to),
kind: .generic kind: .generic)
)
} }
private static func parseNode(args: inout [String]) throws -> ParsedCLIRequest { private static func parseNode(args: inout [String]) throws -> ParsedCLIRequest {
@@ -237,8 +233,7 @@ struct ClawdisCLI {
guard let nodeId, let command else { throw CLIError.help } guard let nodeId, let command else { throw CLIError.help }
return ParsedCLIRequest( return ParsedCLIRequest(
request: .nodeInvoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON), request: .nodeInvoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON),
kind: .generic kind: .generic)
)
default: default:
throw CLIError.help throw CLIError.help
} }
@@ -253,8 +248,7 @@ struct ClawdisCLI {
let placement = self.parseCanvasPlacement(args: &args, session: &session, path: &path) let placement = self.parseCanvasPlacement(args: &args, session: &session, path: &path)
return ParsedCLIRequest( return ParsedCLIRequest(
request: .canvasShow(session: session, path: path, placement: placement), request: .canvasShow(session: session, path: path, placement: placement),
kind: .generic kind: .generic)
)
case "hide": case "hide":
var session = "main" var session = "main"
while !args.isEmpty { while !args.isEmpty {
@@ -272,8 +266,7 @@ struct ClawdisCLI {
guard let path else { throw CLIError.help } guard let path else { throw CLIError.help }
return ParsedCLIRequest( return ParsedCLIRequest(
request: .canvasGoto(session: session, path: path, placement: placement), request: .canvasGoto(session: session, path: path, placement: placement),
kind: .generic kind: .generic)
)
case "eval": case "eval":
var session = "main" var session = "main"
var js: String? var js: String?
@@ -307,8 +300,8 @@ struct ClawdisCLI {
private static func parseCanvasPlacement( private static func parseCanvasPlacement(
args: inout [String], args: inout [String],
session: inout String, session: inout String,
path: inout String? path: inout String?) -> CanvasPlacement?
) -> CanvasPlacement? { {
var x: Double? var x: Double?
var y: Double? var y: Double?
var width: Double? var width: Double?