feat: add mac companion app
This commit is contained in:
114
apps/macos/Sources/ClawdisIPC/IPC.swift
Normal file
114
apps/macos/Sources/ClawdisIPC/IPC.swift
Normal file
@@ -0,0 +1,114 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Capabilities
|
||||
|
||||
public enum Capability: String, Codable, CaseIterable, Sendable {
|
||||
case notifications
|
||||
case accessibility
|
||||
case screenRecording
|
||||
}
|
||||
|
||||
// MARK: - Requests
|
||||
|
||||
public enum Request: Sendable {
|
||||
case notify(title: String, body: String, sound: String?)
|
||||
case ensurePermissions([Capability], interactive: Bool)
|
||||
case screenshot(displayID: UInt32?, windowID: UInt32?, format: String)
|
||||
case runShell(command: [String], cwd: String?, env: [String: String]?, timeoutSec: Double?, needsScreenRecording: Bool)
|
||||
case status
|
||||
}
|
||||
|
||||
// MARK: - Responses
|
||||
|
||||
public struct Response: Codable, Sendable {
|
||||
public var ok: Bool
|
||||
public var message: String?
|
||||
/// Optional payload (PNG bytes, stdout text, etc.).
|
||||
public var payload: Data?
|
||||
|
||||
public init(ok: Bool, message: String? = nil, payload: Data? = nil) {
|
||||
self.ok = ok
|
||||
self.message = message
|
||||
self.payload = payload
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codable conformance for Request
|
||||
|
||||
extension Request: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case title, body, sound
|
||||
case caps, interactive
|
||||
case displayID, windowID, format
|
||||
case command, cwd, env, timeoutSec, needsScreenRecording
|
||||
}
|
||||
|
||||
private enum Kind: String, Codable {
|
||||
case notify
|
||||
case ensurePermissions
|
||||
case screenshot
|
||||
case runShell
|
||||
case status
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case let .notify(title, body, sound):
|
||||
try container.encode(Kind.notify, forKey: .type)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(body, forKey: .body)
|
||||
try container.encodeIfPresent(sound, forKey: .sound)
|
||||
case let .ensurePermissions(caps, interactive):
|
||||
try container.encode(Kind.ensurePermissions, forKey: .type)
|
||||
try container.encode(caps, forKey: .caps)
|
||||
try container.encode(interactive, forKey: .interactive)
|
||||
case let .screenshot(displayID, windowID, format):
|
||||
try container.encode(Kind.screenshot, forKey: .type)
|
||||
try container.encodeIfPresent(displayID, forKey: .displayID)
|
||||
try container.encodeIfPresent(windowID, forKey: .windowID)
|
||||
try container.encode(format, forKey: .format)
|
||||
case let .runShell(command, cwd, env, timeoutSec, needsSR):
|
||||
try container.encode(Kind.runShell, forKey: .type)
|
||||
try container.encode(command, forKey: .command)
|
||||
try container.encodeIfPresent(cwd, forKey: .cwd)
|
||||
try container.encodeIfPresent(env, forKey: .env)
|
||||
try container.encodeIfPresent(timeoutSec, forKey: .timeoutSec)
|
||||
try container.encode(needsSR, forKey: .needsScreenRecording)
|
||||
case .status:
|
||||
try container.encode(Kind.status, forKey: .type)
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let kind = try container.decode(Kind.self, forKey: .type)
|
||||
switch kind {
|
||||
case .notify:
|
||||
let title = try container.decode(String.self, forKey: .title)
|
||||
let body = try container.decode(String.self, forKey: .body)
|
||||
let sound = try container.decodeIfPresent(String.self, forKey: .sound)
|
||||
self = .notify(title: title, body: body, sound: sound)
|
||||
case .ensurePermissions:
|
||||
let caps = try container.decode([Capability].self, forKey: .caps)
|
||||
let interactive = try container.decode(Bool.self, forKey: .interactive)
|
||||
self = .ensurePermissions(caps, interactive: interactive)
|
||||
case .screenshot:
|
||||
let displayID = try container.decodeIfPresent(UInt32.self, forKey: .displayID)
|
||||
let windowID = try container.decodeIfPresent(UInt32.self, forKey: .windowID)
|
||||
let format = try container.decode(String.self, forKey: .format)
|
||||
self = .screenshot(displayID: displayID, windowID: windowID, format: format)
|
||||
case .runShell:
|
||||
let command = try container.decode([String].self, forKey: .command)
|
||||
let cwd = try container.decodeIfPresent(String.self, forKey: .cwd)
|
||||
let env = try container.decodeIfPresent([String: String].self, forKey: .env)
|
||||
let timeout = try container.decodeIfPresent(Double.self, forKey: .timeoutSec)
|
||||
let needsSR = try container.decode(Bool.self, forKey: .needsScreenRecording)
|
||||
self = .runShell(command: command, cwd: cwd, env: env, timeoutSec: timeout, needsScreenRecording: needsSR)
|
||||
case .status:
|
||||
self = .status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user