chore: fix swiftlint after split

This commit is contained in:
Peter Steinberger
2025-12-07 00:14:03 +01:00
parent 82e751a153
commit 7b7c4bd116
12 changed files with 123 additions and 124 deletions

View File

@@ -62,7 +62,7 @@ final class AppState: ObservableObject {
@Published var isWorking: Bool = false
@Published var earBoostActive: Bool = false
private var earBoostTask: Task<Void, Never>? = nil
private var earBoostTask: Task<Void, Never>?
init() {
self.isPaused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)

View File

@@ -23,15 +23,15 @@ struct ClawdisApp: App {
earBoostActive: self.state.earBoostActive,
relayStatus: self.relayManager.status)
}
.menuBarExtraStyle(.menu)
.menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in
self.statusItem = item
self.applyStatusItemAppearance(paused: self.state.isPaused)
}
.onChange(of: self.state.isPaused) { _, paused in
self.applyStatusItemAppearance(paused: paused)
self.relayManager.setActive(!paused)
}
.menuBarExtraStyle(.menu)
.menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in
self.statusItem = item
self.applyStatusItemAppearance(paused: self.state.isPaused)
}
.onChange(of: self.state.isPaused) { _, paused in
self.applyStatusItemAppearance(paused: paused)
self.relayManager.setActive(!paused)
}
Settings {
SettingsRootView(state: self.state)
@@ -90,10 +90,10 @@ private struct MenuContent: View {
private func statusColor(_ status: RelayProcessManager.Status) -> Color {
switch status {
case .running: return .green
case .starting, .restarting: return .orange
case .failed: return .red
case .stopped: return .secondary
case .running: .green
case .starting, .restarting: .orange
case .failed: .red
case .stopped: .secondary
}
}
@@ -257,17 +257,17 @@ private struct CritterStatusLabel: View {
private var relayNeedsAttention: Bool {
switch self.relayStatus {
case .failed, .stopped:
return !self.isPaused
!self.isPaused
case .starting, .restarting, .running:
return false
false
}
}
private var relayBadgeColor: Color {
switch self.relayStatus {
case .failed: return .red
case .stopped: return .orange
default: return .clear
case .failed: .red
case .stopped: .orange
default: .clear
}
}
}
@@ -279,8 +279,8 @@ enum CritterIconRenderer {
blink: CGFloat,
legWiggle: CGFloat = 0,
earWiggle: CGFloat = 0,
earScale: CGFloat = 1
) -> NSImage {
earScale: CGFloat = 1) -> NSImage
{
let image = NSImage(size: size)
image.lockFocus()
defer { image.unlockFocus() }

View File

@@ -120,7 +120,8 @@ struct OnboardingView: View {
self.onboardingCard {
self.featureRow(
title: "Owns the TCC prompts",
subtitle: "Requests Notifications, Accessibility, and Screen Recording so your agents stay unblocked.",
subtitle: "Requests Notifications, Accessibility, and Screen Recording "
+ "so your agents stay unblocked.",
systemImage: "lock.shield")
self.featureRow(
title: "Native notifications",
@@ -128,7 +129,8 @@ struct OnboardingView: View {
systemImage: "bell.and.waveform")
self.featureRow(
title: "Privileged helpers",
subtitle: "Runs screenshots or shell actions from the `clawdis-mac` CLI with the right permissions.",
subtitle: "Runs screenshots or shell actions from the `clawdis-mac` CLI "
+ "with the right permissions.",
systemImage: "terminal")
}
}
@@ -232,7 +234,8 @@ struct OnboardingView: View {
Spacer()
}
Text(
"You can pause from the menu bar anytime. Settings keeps a \"Show onboarding\" button if you need to revisit.")
"You can pause from the menu bar anytime. Settings keeps a \"Show onboarding\" "
+ "button if you need to revisit.")
.font(.footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .center)
@@ -247,7 +250,8 @@ struct OnboardingView: View {
self.onboardingCard {
self.featureRow(
title: "Run the dashboard",
subtitle: "Use the CLI helper from your scripts, and reopen onboarding from Settings if you add a new user.",
subtitle: "Use the CLI helper from your scripts, and reopen onboarding from "
+ "Settings if you add a new user.",
systemImage: "checkmark.seal")
self.featureRow(
title: "Test a notification",
@@ -273,10 +277,10 @@ struct OnboardingView: View {
.disabled(true)
if self.currentPage > 0 {
Button(action: { self.handleBack() }) {
Button(action: self.handleBack, label: {
Label("Back", systemImage: "chevron.left")
.labelStyle(.iconOnly)
}
})
.buttonStyle(.plain)
.foregroundColor(.secondary)
.opacity(0.8)

View File

@@ -23,7 +23,8 @@ enum PermissionManager {
case .notDetermined:
if interactive {
let granted = (try? await center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false
let granted = await (try? center.requestAuthorization(options: [.alert, .sound, .badge])) ??
false
let updated = await center.notificationSettings()
results[cap] = granted && (updated.authorizationStatus == .authorized || updated
.authorizationStatus == .provisional)

View File

@@ -46,7 +46,7 @@ final class RelayProcessManager: ObservableObject {
private var recentCrashes: [Date] = []
private let logger = Logger(subsystem: "com.steipete.clawdis", category: "relay")
private let logLimit = 20_000 // characters to keep in-memory
private let logLimit = 20000 // characters to keep in-memory
private let maxCrashes = 3
private let crashWindow: TimeInterval = 120 // seconds
@@ -98,8 +98,8 @@ final class RelayProcessManager: ObservableObject {
.name(command.first ?? "clawdis"),
arguments: Arguments(Array(command.dropFirst())),
environment: self.makeEnvironment(),
workingDirectory: FilePath(cwd)
) { execution, stdin, stdout, stderr in
workingDirectory: FilePath(cwd))
{ execution, stdin, stdout, stderr in
self.didStart(execution)
async let out: Void = self.stream(output: stdout, label: "stdout")
async let err: Void = self.stream(output: stderr, label: "stderr")
@@ -122,12 +122,10 @@ final class RelayProcessManager: ObservableObject {
}
private func handleTermination(status: TerminationStatus) async {
let code: Int32 = {
switch status {
case let .exited(exitCode): return exitCode
case let .unhandledException(sig): return -Int32(sig)
}
}()
let code: Int32 = switch status {
case let .exited(exitCode): exitCode
case let .unhandledException(sig): -Int32(sig)
}
self.execution = nil
if self.stopping || !self.desiredActive {
@@ -161,7 +159,7 @@ final class RelayProcessManager: ObservableObject {
}
self.appendLog("[relay] failed: \(message)\n")
self.logger.error("relay failed: \(message, privacy: .public)")
if self.desiredActive && !self.shouldGiveUpAfterCrashes() {
if self.desiredActive, !self.shouldGiveUpAfterCrashes() {
self.status = .restarting
self.recentCrashes.append(Date())
self.startIfNeeded()
@@ -200,7 +198,8 @@ final class RelayProcessManager: ObservableObject {
// Keep it simple: rely on system-installed clawdis/warelay.
// Default to `clawdis relay`; users can provide an override via env if needed.
if let override = ProcessInfo.processInfo.environment["CLAWDIS_RELAY_CMD"],
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
return override.split(separator: " ").map(String.init)
}
@@ -257,7 +256,8 @@ final class RelayProcessManager: ObservableObject {
private func defaultProjectRoot() -> URL {
if let stored = UserDefaults.standard.string(forKey: Defaults.projectRootPath),
let url = self.expandPath(stored) {
let url = self.expandPath(stored)
{
return url
}
let fallback = FileManager.default.homeDirectoryForCurrentUser
@@ -275,7 +275,7 @@ final class RelayProcessManager: ObservableObject {
func projectRootPath() -> String {
UserDefaults.standard.string(forKey: Defaults.projectRootPath)
?? FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Projects/clawdis").path
.appendingPathComponent("Projects/clawdis").path
}
private func expandPath(_ path: String) -> URL? {

View File

@@ -115,11 +115,9 @@ extension [String] {
fileprivate func dedupedPreserveOrder() -> [String] {
var seen = Set<String>()
var result: [String] = []
for item in self {
if !seen.contains(item) {
seen.insert(item)
result.append(item)
}
for item in self where !seen.contains(item) {
seen.insert(item)
result.append(item)
}
return result
}

View File

@@ -16,11 +16,11 @@ private enum InstallMethod: Equatable {
let .npm(_, binary),
let .go(_, binary),
let .pnpm(_, _, binary):
return binary
binary
case .gitClone:
return nil
nil
case .mcporter:
return "mcporter"
"mcporter"
}
}
}
@@ -57,80 +57,70 @@ struct ToolsSettings: View {
url: URL(string: "https://github.com/steipete/mcporter")!,
description: "MCP runtime/CLI to discover servers, run tools, and sync configs across AI clients.",
method: .npm(package: "mcporter", binary: "mcporter"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "peekaboo",
name: "Peekaboo",
url: URL(string: "https://github.com/steipete/Peekaboo")!,
description: "Lightning-fast macOS screenshots with AI vision helpers for step-by-step automation.",
method: .brew(formula: "steipete/tap/peekaboo", binary: "peekaboo"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "camsnap",
name: "camsnap",
url: URL(string: "https://github.com/steipete/camsnap")!,
description: "One command to grab frames, clips, or motion alerts from RTSP/ONVIF cameras.",
method: .brew(formula: "steipete/tap/camsnap", binary: "camsnap"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "oracle",
name: "oracle",
url: URL(string: "https://github.com/steipete/oracle")!,
description: "Runs OpenAI-ready agent workflows from the CLI with session replay and browser control.",
method: .npm(package: "@steipete/oracle", binary: "oracle"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "eightctl",
name: "eightctl",
url: URL(string: "https://github.com/steipete/eightctl")!,
description: "Control Eight Sleep Pods (temp, alarms, schedules, metrics) from scripts or cron.",
method: .go(module: "github.com/steipete/eightctl/cmd/eightctl@latest", binary: "eightctl"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "imsg",
name: "imsg",
url: URL(string: "https://github.com/steipete/imsg")!,
description: "CLI for macOS Messages: read/tail chats and send iMessage/SMS with attachments.",
method: .go(module: "github.com/steipete/imsg/cmd/imsg@latest", binary: "imsg"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "spotify-player",
name: "spotify-player",
url: URL(string: "https://github.com/aome510/spotify-player")!,
description: "Terminal Spotify client to queue, search, and control playback without leaving chat.",
method: .brew(formula: "spotify_player", binary: "spotify_player"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "openhue-cli",
name: "OpenHue CLI",
url: URL(string: "https://github.com/openhue/openhue-cli")!,
description: "Control Philips Hue lights from scripts—scenes, dimming, and automations.",
method: .brew(formula: "openhue/cli/openhue-cli", binary: "openhue"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "openai-whisper",
name: "OpenAI Whisper",
url: URL(string: "https://github.com/openai/whisper")!,
description: "On-device speech-to-text for quick note taking or voicemail transcription.",
method: .brew(formula: "openai-whisper", binary: "whisper"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "gemini-cli",
name: "Gemini CLI",
url: URL(string: "https://github.com/google-gemini/gemini-cli")!,
description: "Google Gemini models from the terminal for fast Q&A and web-grounded summaries.",
method: .brew(formula: "gemini-cli", binary: "gemini"),
kind: .tool
),
kind: .tool),
ToolEntry(
id: "bird",
name: "bird",
@@ -139,10 +129,8 @@ struct ToolsSettings: View {
method: .pnpm(
repoPath: "\(NSHomeDirectory())/Projects/bird",
script: "binary",
binary: "bird"
),
kind: .tool
),
binary: "bird"),
kind: .tool),
ToolEntry(
id: "agent-tools",
name: "agent-tools",
@@ -150,10 +138,8 @@ struct ToolsSettings: View {
description: "Collection of utilities and scripts tuned for autonomous agents and MCP clients.",
method: .gitClone(
url: "https://github.com/badlogic/agent-tools.git",
destination: "\(NSHomeDirectory())/agent-tools"
),
kind: .tool
),
destination: "\(NSHomeDirectory())/agent-tools"),
kind: .tool),
ToolEntry(
id: "gmail-mcp",
name: "Gmail MCP",
@@ -162,10 +148,8 @@ struct ToolsSettings: View {
method: .mcporter(
name: "gmail",
command: "npx -y @gongrzhe/server-gmail-autoauth-mcp",
summary: "Adds Gmail MCP via mcporter (stdio transport, auto-auth)."
),
kind: .mcp
),
summary: "Adds Gmail MCP via mcporter (stdio transport, auto-auth)."),
kind: .mcp),
ToolEntry(
id: "google-calendar-mcp",
name: "Google Calendar MCP",
@@ -174,10 +158,8 @@ struct ToolsSettings: View {
method: .mcporter(
name: "google-calendar",
command: "npx -y @cocal/google-calendar-mcp",
summary: "Adds Google Calendar MCP via mcporter (stdio transport)."
),
kind: .mcp
),
summary: "Adds Google Calendar MCP via mcporter (stdio transport)."),
kind: .mcp),
]
var body: some View {
@@ -188,8 +170,8 @@ struct ToolsSettings: View {
ScrollView {
LazyVStack(spacing: 12) {
section(for: .tool, title: "CLI Tools")
section(for: .mcp, title: "MCP Servers")
self.section(for: .tool, title: "CLI Tools")
self.section(for: .mcp, title: "MCP Servers")
}
}
}
@@ -198,7 +180,7 @@ struct ToolsSettings: View {
}
private func section(for kind: ToolEntry.Kind, title: String) -> some View {
let filtered = tools.filter { $0.kind == kind }
let filtered = self.tools.filter { $0.kind == kind }
return VStack(alignment: .leading, spacing: 10) {
Text(title)
.font(.callout.weight(.semibold))
@@ -212,8 +194,7 @@ struct ToolsSettings: View {
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.secondary.opacity(0.15), lineWidth: 1)
)
.stroke(Color.secondary.opacity(0.15), lineWidth: 1))
}
}
}
@@ -231,15 +212,15 @@ private struct ToolRow: View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .top, spacing: 10) {
VStack(alignment: .leading, spacing: 4) {
Link(tool.name, destination: tool.url)
Link(self.tool.name, destination: self.tool.url)
.font(.headline)
Text(tool.description)
Text(self.tool.description)
.font(.subheadline)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
actionButton
self.actionButton
}
if let statusMessage, !statusMessage.isEmpty {
@@ -248,12 +229,12 @@ private struct ToolRow: View {
.foregroundStyle(.secondary)
}
}
.onAppear { refresh() }
.onAppear { self.refresh() }
}
private var actionButton: some View {
VStack {
switch state {
switch self.state {
case .installed:
Label("Installed", systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
@@ -261,12 +242,12 @@ private struct ToolRow: View {
case .installing:
ProgressView().controlSize(.small)
case .failed:
Button("Retry") { install() }
Button("Retry") { self.install() }
.buttonStyle(.borderedProminent)
case .checking:
ProgressView().controlSize(.small)
case .notInstalled:
Button("Install") { install() }
Button("Install") { self.install() }
.buttonStyle(.borderedProminent)
}
}
@@ -274,21 +255,21 @@ private struct ToolRow: View {
private func refresh() {
Task {
state = .checking
let installed = await ToolInstaller.isInstalled(tool.method)
self.state = .checking
let installed = await ToolInstaller.isInstalled(self.tool.method)
await MainActor.run {
state = installed ? .installed : .notInstalled
self.state = installed ? .installed : .notInstalled
}
}
}
private func install() {
Task {
state = .installing
let result = await ToolInstaller.install(tool.method)
self.state = .installing
let result = await ToolInstaller.install(self.tool.method)
await MainActor.run {
statusMessage = result.message
state = result.installed ? .installed : .failed(result.message)
self.statusMessage = result.message
self.state = result.installed ? .installed : .failed(result.message)
}
}
}
@@ -305,30 +286,30 @@ private enum ToolInstaller {
static func isInstalled(_ method: InstallMethod) async -> Bool {
switch method {
case let .brew(formula, _):
return await shellSucceeds("brew list --versions \(formula)")
return await self.shellSucceeds("brew list --versions \(formula)")
case let .npm(_, binary),
let .go(_, binary),
let .pnpm(_, _, binary):
return await commandExists(binary)
return await self.commandExists(binary)
case let .gitClone(_, destination):
return FileManager.default.fileExists(atPath: destination)
case let .mcporter(name, _, _):
guard await commandExists("mcporter") else { return false }
return await shellSucceeds("mcporter config get \(name) --json")
guard await self.commandExists("mcporter") else { return false }
return await self.shellSucceeds("mcporter config get \(name) --json")
}
}
static func install(_ method: InstallMethod) async -> InstallResult {
switch method {
case let .brew(formula, _):
return await runInstall("brew install \(formula)")
return await self.runInstall("brew install \(formula)")
case let .npm(package, _):
return await runInstall("npm install -g \(package)")
return await self.runInstall("npm install -g \(package)")
case let .go(module, _):
return await runInstall("GO111MODULE=on go install \(module)")
return await self.runInstall("GO111MODULE=on go install \(module)")
case let .pnpm(repoPath, script, _):
let cmd = "cd \(escape(repoPath)) && pnpm install && pnpm run \(script)"
return await runInstall(cmd)
return await self.runInstall(cmd)
case let .gitClone(url, destination):
let cmd = """
if [ -d \(escape(destination)) ]; then
@@ -337,19 +318,19 @@ private enum ToolInstaller {
git clone \(url) \(escape(destination))
fi
"""
return await runInstall(cmd)
return await self.runInstall(cmd)
case let .mcporter(name, command, summary):
let cmd = """
mcporter config add \(name) --command "\(command)" --transport stdio --scope home --description "\(summary)"
"""
return await runInstall(cmd)
return await self.runInstall(cmd)
}
}
// MARK: - Helpers
private static func commandExists(_ binary: String) async -> Bool {
await shellSucceeds("command -v \(binary)")
await self.shellSucceeds("command -v \(binary)")
}
private static func shellSucceeds(_ command: String) async -> Bool {

View File

@@ -1,5 +1,5 @@
import Foundation
import AppKit
import Foundation
enum LaunchdManager {
private static func runLaunchctl(_ args: [String]) {

View File

@@ -103,7 +103,10 @@ final class VoiceWakeTester {
domain: "VoiceWakeTester",
code: 3,
userInfo: [
NSLocalizedDescriptionKey: "Missing mic/speech privacy strings. Rebuild the mac app (scripts/restart-mac.sh) to include usage descriptions.",
NSLocalizedDescriptionKey: """
Missing mic/speech privacy strings. Rebuild the mac app (scripts/restart-mac.sh) \
to include usage descriptions.
""",
])
}
@@ -256,7 +259,8 @@ struct VoiceWakeSettings: View {
VStack(alignment: .leading, spacing: 14) {
SettingsToggleRow(
title: "Enable Voice Wake",
subtitle: "Listen for a wake phrase (e.g. \"Claude\") before running voice commands. Voice recognition runs fully on-device.",
subtitle: "Listen for a wake phrase (e.g. \"Claude\") before running voice commands. "
+ "Voice recognition runs fully on-device.",
binding: self.$state.swabbleEnabled)
.disabled(!voiceWakeSupported)
@@ -314,7 +318,8 @@ struct VoiceWakeSettings: View {
.stroke(Color.secondary.opacity(0.25), lineWidth: 1))
Text(
"Clawdis reacts when any trigger appears in a transcription. Keep them short to avoid false positives.")
"Clawdis reacts when any trigger appears in a transcription. "
+ "Keep them short to avoid false positives.")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)

View File

@@ -69,7 +69,7 @@ final class WebChatWindowController: NSWindowController, WKScriptMessageHandler,
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
private func loadPage() {
let messagesJSON = self.initialMessagesJSON.replacingOccurrences(of: "</script>", with: "<\\/script>")
@@ -112,7 +112,13 @@ final class WebChatWindowController: NSWindowController, WKScriptMessageHandler,
"{}"
}
let html = """
let html = self.makeHTML(importMapJSON: importMapJSON, messagesJSON: messagesJSON)
self.webView.loadHTMLString(html, baseURL: webChatURL)
}
// swiftlint:disable line_length
private func makeHTML(importMapJSON: String, messagesJSON: String) -> String {
"""
<!doctype html>
<html>
<head>
@@ -156,7 +162,7 @@ final class WebChatWindowController: NSWindowController, WKScriptMessageHandler,
class NativeTransport {
async *run(messages, userMessage, cfg, signal) {
const result = await window.__clawdisSend({ type: 'chat', payload: { text: userMessage.content?.[0]?.text ?? '', sessionKey: '\(
sessionKey)' } });
self.sessionKey)' } });
const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
const assistant = {
role: 'assistant',
@@ -225,9 +231,10 @@ final class WebChatWindowController: NSWindowController, WKScriptMessageHandler,
</body>
</html>
"""
self.webView.loadHTMLString(html, baseURL: webChatURL)
}
// swiftlint:enable line_length
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.body.innerText") { result, error in
if let error {

View File

@@ -1,6 +1,6 @@
import ClawdisIPC
import Foundation
import OSLog
import ClawdisIPC
@objc protocol ClawdisXPCProtocol {
func handle(_ data: Data, withReply reply: @escaping @Sendable (Data?, Error?) -> Void)

View File

@@ -37,6 +37,7 @@ struct ClawdisCLI {
}
}
// swiftlint:disable cyclomatic_complexity
private static func parseCommandLine() throws -> Request {
var args = Array(CommandLine.arguments.dropFirst())
guard let command = args.first else { throw CLIError.help }
@@ -126,6 +127,8 @@ struct ClawdisCLI {
}
}
// swiftlint:enable cyclomatic_complexity
private static func send(request: Request) async throws -> Response {
let conn = NSXPCConnection(machServiceName: serviceName)
let interface = NSXPCInterface(with: ClawdisXPCProtocol.self)