style(macos): tidy settings and CLI
parent
02fe19effa
commit
0b990443de
|
|
@ -38,4 +38,3 @@ enum ClawdisConfigFile {
|
||||||
self.saveDict(root)
|
self.saveDict(root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,14 +203,14 @@ 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("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 {
|
GridRow {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
.frame(width: self.labelColumnWidth, height: 1)
|
||||||
Text(
|
Text(
|
||||||
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it won’t interfere with your daily browser."
|
"Clawd uses a separate Chrome profile and ports (default 18791/18792) so it won’t interfere with your daily browser.")
|
||||||
)
|
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,8 @@ 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("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)
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
@ -570,7 +571,8 @@ private struct CronJobEditor: View {
|
||||||
GridRow {
|
GridRow {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
.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)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
@ -594,7 +596,8 @@ private struct CronJobEditor: View {
|
||||||
GridRow {
|
GridRow {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
.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)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
@ -604,7 +607,10 @@ private struct CronJobEditor: View {
|
||||||
case .at:
|
case .at:
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("At")
|
self.gridLabel("At")
|
||||||
DatePicker("", selection: self.$atDate, displayedComponents: [.date, .hourAndMinute])
|
DatePicker(
|
||||||
|
"",
|
||||||
|
selection: self.$atDate,
|
||||||
|
displayedComponents: [.date, .hourAndMinute])
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
@ -635,7 +641,8 @@ 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("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)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
@ -655,7 +662,8 @@ private struct CronJobEditor: View {
|
||||||
GridRow {
|
GridRow {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
.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)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
@ -687,7 +695,8 @@ private struct CronJobEditor: View {
|
||||||
GridRow {
|
GridRow {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
.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)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,13 @@ struct MenuContent: View {
|
||||||
WebChatManager.shared.show(sessionKey: WebChatManager.shared.preferredSessionKey())
|
WebChatManager.shared.show(sessionKey: WebChatManager.shared.preferredSessionKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Toggle(isOn: Binding(
|
Toggle(
|
||||||
|
isOn: Binding(
|
||||||
get: { self.browserControlEnabled },
|
get: { self.browserControlEnabled },
|
||||||
set: { enabled in
|
set: { enabled in
|
||||||
self.browserControlEnabled = enabled
|
self.browserControlEnabled = enabled
|
||||||
ClawdisConfigFile.setBrowserControlEnabled(enabled)
|
ClawdisConfigFile.setBrowserControlEnabled(enabled)
|
||||||
})
|
})) {
|
||||||
) {
|
|
||||||
Text("Browser Control")
|
Text("Browser Control")
|
||||||
}
|
}
|
||||||
Toggle(isOn: Binding(get: { self.state.canvasEnabled }, set: { self.state.canvasEnabled = $0 })) {
|
Toggle(isOn: Binding(get: { self.state.canvasEnabled }, set: { self.state.canvasEnabled = $0 })) {
|
||||||
|
|
@ -110,7 +110,9 @@ struct MenuContent: View {
|
||||||
await self.reloadSessionMenu()
|
await self.reloadSessionMenu()
|
||||||
}
|
}
|
||||||
} label: {
|
} 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()
|
await self.reloadSessionMenu()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(level.capitalized, systemImage: row.verboseLevel == normalized ? "checkmark" : "")
|
Label(
|
||||||
|
level.capitalized,
|
||||||
|
systemImage: row.verboseLevel == normalized ? "checkmark" : "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ final class PeekabooBridgeHostCoordinator {
|
||||||
self.host = host
|
self.host = host
|
||||||
|
|
||||||
await host.start()
|
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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,3 @@ extension View {
|
||||||
self.modifier(PointingHandCursorModifier())
|
self.modifier(PointingHandCursorModifier())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,3 @@ enum ScreenshotSize {
|
||||||
return Size(width: width, height: height)
|
return Size(width: width, height: height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -639,9 +639,9 @@ enum CommandResolver {
|
||||||
return URL(fileURLWithPath: expanded)
|
return URL(fileURLWithPath: expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SWIFT_PACKAGE
|
#if SWIFT_PACKAGE
|
||||||
static func _testNodeManagerBinPaths(home: URL) -> [String] {
|
static func _testNodeManagerBinPaths(home: URL) -> [String] {
|
||||||
self.nodeManagerBinPaths(home: home)
|
self.nodeManagerBinPaths(home: home)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ struct GatewayChatMessage: Codable, Identifiable {
|
||||||
id: UUID = .init(),
|
id: UUID = .init(),
|
||||||
role: String,
|
role: String,
|
||||||
content: [GatewayChatMessageContent],
|
content: [GatewayChatMessageContent],
|
||||||
timestamp: Double?
|
timestamp: Double?)
|
||||||
) {
|
{
|
||||||
self.id = id
|
self.id = id
|
||||||
self.role = role
|
self.role = role
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
@ -124,6 +124,7 @@ final class WebChatViewModel: ObservableObject {
|
||||||
private var pendingRuns = Set<String>() {
|
private var pendingRuns = Set<String>() {
|
||||||
didSet { self.pendingRunCount = self.pendingRuns.count }
|
didSet { self.pendingRunCount = self.pendingRuns.count }
|
||||||
}
|
}
|
||||||
|
|
||||||
private var lastHealthPollAt: Date?
|
private var lastHealthPollAt: Date?
|
||||||
|
|
||||||
init(sessionKey: String) {
|
init(sessionKey: String) {
|
||||||
|
|
@ -162,7 +163,8 @@ final class WebChatViewModel: ObservableObject {
|
||||||
do {
|
do {
|
||||||
let data = try await Task.detached { try Data(contentsOf: url) }.value
|
let data = try await Task.detached { try Data(contentsOf: url) }.value
|
||||||
guard data.count <= 5_000_000 else {
|
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
|
continue
|
||||||
}
|
}
|
||||||
let uti = UTType(filenameExtension: url.pathExtension) ?? .data
|
let uti = UTType(filenameExtension: url.pathExtension) ?? .data
|
||||||
|
|
@ -445,7 +447,8 @@ struct WebChatView: View {
|
||||||
.foregroundStyle(Color.accentColor.opacity(0.9))
|
.foregroundStyle(Color.accentColor.opacity(0.9))
|
||||||
Text("Say hi to Clawd")
|
Text("Say hi to Clawd")
|
||||||
.font(.headline)
|
.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)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +461,9 @@ struct WebChatView: View {
|
||||||
} else {
|
} else {
|
||||||
ForEach(self.viewModel.messages) { msg in
|
ForEach(self.viewModel.messages) { msg in
|
||||||
MessageBubble(message: msg)
|
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 {
|
switch block.kind {
|
||||||
case .text:
|
case .text:
|
||||||
MarkdownTextView(text: block.text)
|
MarkdownTextView(text: block.text)
|
||||||
case .code(let language):
|
case let .code(language):
|
||||||
CodeBlockView(code: block.text, language: language)
|
CodeBlockView(code: block.text, language: language)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -747,7 +752,7 @@ private struct AttachmentRow: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "paperclip")
|
Image(systemName: "paperclip")
|
||||||
Text(att.fileName ?? "Attachment")
|
Text(self.att.fileName ?? "Attachment")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
|
||||||
|
|
@ -83,25 +83,34 @@ enum BrowserCLI {
|
||||||
do {
|
do {
|
||||||
switch sub {
|
switch sub {
|
||||||
case "status":
|
case "status":
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
jsonOutput: jsonOutput,
|
||||||
res: try await self.httpJSON(method: "GET", url: baseURL.appendingPathComponent("/")))
|
res: self.httpJSON(method: "GET", url: baseURL.appendingPathComponent("/")))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
case "start":
|
case "start":
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
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
|
return 0
|
||||||
|
|
||||||
case "stop":
|
case "stop":
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
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
|
return 0
|
||||||
|
|
||||||
case "tabs":
|
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 {
|
if jsonOutput {
|
||||||
self.printJSON(ok: true, result: res)
|
self.printJSON(ok: true, result: res)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -114,9 +123,9 @@ enum BrowserCLI {
|
||||||
self.printHelp()
|
self.printHelp()
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
jsonOutput: jsonOutput,
|
||||||
res: try await self.httpJSON(
|
res: self.httpJSON(
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: baseURL.appendingPathComponent("/tabs/open"),
|
url: baseURL.appendingPathComponent("/tabs/open"),
|
||||||
body: ["url": url],
|
body: ["url": url],
|
||||||
|
|
@ -128,9 +137,9 @@ enum BrowserCLI {
|
||||||
self.printHelp()
|
self.printHelp()
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
jsonOutput: jsonOutput,
|
||||||
res: try await self.httpJSON(
|
res: self.httpJSON(
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: baseURL.appendingPathComponent("/tabs/focus"),
|
url: baseURL.appendingPathComponent("/tabs/focus"),
|
||||||
body: ["targetId": id],
|
body: ["targetId": id],
|
||||||
|
|
@ -142,9 +151,9 @@ enum BrowserCLI {
|
||||||
self.printHelp()
|
self.printHelp()
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
self.printResult(
|
try await self.printResult(
|
||||||
jsonOutput: jsonOutput,
|
jsonOutput: jsonOutput,
|
||||||
res: try await self.httpJSON(
|
res: self.httpJSON(
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: baseURL.appendingPathComponent("/tabs/\(id)"),
|
url: baseURL.appendingPathComponent("/tabs/\(id)"),
|
||||||
timeoutInterval: 5.0))
|
timeoutInterval: 5.0))
|
||||||
|
|
@ -345,8 +354,8 @@ enum BrowserCLI {
|
||||||
method: String,
|
method: String,
|
||||||
url: URL,
|
url: URL,
|
||||||
body: [String: Any]? = nil,
|
body: [String: Any]? = nil,
|
||||||
timeoutInterval: TimeInterval = 2.0
|
timeoutInterval: TimeInterval = 2.0) async throws -> [String: Any]
|
||||||
) async throws -> [String: Any] {
|
{
|
||||||
var req = URLRequest(url: url, timeoutInterval: timeoutInterval)
|
var req = URLRequest(url: url, timeoutInterval: timeoutInterval)
|
||||||
req.httpMethod = method
|
req.httpMethod = method
|
||||||
if let body {
|
if let body {
|
||||||
|
|
@ -369,7 +378,7 @@ enum BrowserCLI {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
if status >= 200 && status < 300 {
|
if status >= 200, status < 300 {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,11 +526,11 @@ enum BrowserCLI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SWIFT_PACKAGE
|
#if SWIFT_PACKAGE
|
||||||
static func _testFormatTabs(res: [String: Any]) -> [String] {
|
static func _testFormatTabs(res: [String: Any]) -> [String] {
|
||||||
self.formatTabs(res: res)
|
self.formatTabs(res: res)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private static func printJSON(ok: Bool, result: Any) {
|
private static func printJSON(ok: Bool, result: Any) {
|
||||||
let obj: [String: Any] = ["ok": ok, "result": result]
|
let obj: [String: Any] = ["ok": ok, "result": result]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Foundation
|
|
||||||
import Darwin
|
import Darwin
|
||||||
|
import Foundation
|
||||||
import PeekabooAutomationKit
|
import PeekabooAutomationKit
|
||||||
import PeekabooBridge
|
import PeekabooBridge
|
||||||
import PeekabooFoundation
|
import PeekabooFoundation
|
||||||
|
|
@ -94,7 +94,7 @@ enum UICLI {
|
||||||
|
|
||||||
private static func runPermissions(args: [String], jsonOutput: Bool, context: Context) async throws -> Int32 {
|
private static func runPermissions(args: [String], jsonOutput: Bool, context: Context) async throws -> Int32 {
|
||||||
let sub = args.first ?? "status"
|
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()
|
self.printHelp()
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ enum UICLI {
|
||||||
try self.writeJSON([
|
try self.writeJSON([
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"result": try self.toJSONObject(status),
|
"result": self.toJSONObject(status),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
FileHandle.standardOutput.write(Data((self.formatPermissions(status) + "\n").utf8))
|
FileHandle.standardOutput.write(Data((self.formatPermissions(status) + "\n").utf8))
|
||||||
|
|
@ -123,7 +123,7 @@ enum UICLI {
|
||||||
try self.writeJSON([
|
try self.writeJSON([
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"app": try self.toJSONObject(app),
|
"app": self.toJSONObject(app),
|
||||||
"window": windowObject,
|
"window": windowObject,
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -131,7 +131,7 @@ enum UICLI {
|
||||||
let line = "\(bundle) (pid \(app.processIdentifier))"
|
let line = "\(bundle) (pid \(app.processIdentifier))"
|
||||||
FileHandle.standardOutput.write(Data((line + "\n").utf8))
|
FileHandle.standardOutput.write(Data((line + "\n").utf8))
|
||||||
if let window {
|
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
|
return 0
|
||||||
|
|
@ -143,12 +143,12 @@ enum UICLI {
|
||||||
try self.writeJSON([
|
try self.writeJSON([
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"result": try self.toJSONObject(apps),
|
"result": self.toJSONObject(apps),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
for app in apps {
|
for app in apps {
|
||||||
let bundle = app.bundleIdentifier ?? "<unknown>"
|
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
|
return 0
|
||||||
|
|
@ -176,11 +176,11 @@ enum UICLI {
|
||||||
try self.writeJSON([
|
try self.writeJSON([
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"result": try self.toJSONObject(windows),
|
"result": self.toJSONObject(windows),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
for window in windows {
|
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
|
return 0
|
||||||
|
|
@ -217,20 +217,19 @@ enum UICLI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let capture: CaptureResult
|
let capture: CaptureResult = if let bundleId, !bundleId.isEmpty {
|
||||||
if let bundleId, !bundleId.isEmpty {
|
try await context.client.captureWindow(
|
||||||
capture = try await context.client.captureWindow(
|
|
||||||
appIdentifier: bundleId,
|
appIdentifier: bundleId,
|
||||||
windowIndex: windowIndex,
|
windowIndex: windowIndex,
|
||||||
visualizerMode: mode,
|
visualizerMode: mode,
|
||||||
scale: scale)
|
scale: scale)
|
||||||
} else if displayIndex != nil {
|
} else if displayIndex != nil {
|
||||||
capture = try await context.client.captureScreen(
|
try await context.client.captureScreen(
|
||||||
displayIndex: displayIndex,
|
displayIndex: displayIndex,
|
||||||
visualizerMode: mode,
|
visualizerMode: mode,
|
||||||
scale: scale)
|
scale: scale)
|
||||||
} else {
|
} 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)
|
let path = try self.writeTempPNG(capture.imageData)
|
||||||
|
|
@ -240,7 +239,7 @@ enum UICLI {
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"path": path,
|
"path": path,
|
||||||
"metadata": try self.toJSONObject(capture.metadata),
|
"metadata": self.toJSONObject(capture.metadata),
|
||||||
"warning": capture.warning ?? "",
|
"warning": capture.warning ?? "",
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -287,7 +286,8 @@ enum UICLI {
|
||||||
let resolvedSnapshotId: String = if let snapshotId, !snapshotId.isEmpty {
|
let resolvedSnapshotId: String = if let snapshotId, !snapshotId.isEmpty {
|
||||||
snapshotId
|
snapshotId
|
||||||
} else if let bundleId, !bundleId.isEmpty, let existing = try? await context.client
|
} else if let bundleId, !bundleId.isEmpty, let existing = try? await context.client
|
||||||
.getMostRecentSnapshot(applicationBundleId: bundleId) {
|
.getMostRecentSnapshot(applicationBundleId: bundleId)
|
||||||
|
{
|
||||||
existing
|
existing
|
||||||
} else {
|
} else {
|
||||||
try await context.client.createSnapshot()
|
try await context.client.createSnapshot()
|
||||||
|
|
@ -321,7 +321,7 @@ enum UICLI {
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"snapshotId": resolvedSnapshotId,
|
"snapshotId": resolvedSnapshotId,
|
||||||
"screenshotPath": screenshotPath,
|
"screenshotPath": screenshotPath,
|
||||||
"result": try self.toJSONObject(detection),
|
"result": self.toJSONObject(detection),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
FileHandle.standardOutput.write(Data((screenshotPath + "\n").utf8))
|
FileHandle.standardOutput.write(Data((screenshotPath + "\n").utf8))
|
||||||
|
|
@ -494,7 +494,7 @@ enum UICLI {
|
||||||
try self.writeJSON([
|
try self.writeJSON([
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"host": context.hostDescription,
|
"host": context.hostDescription,
|
||||||
"result": try self.toJSONObject(result),
|
"result": self.toJSONObject(result),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
FileHandle.standardOutput.write(Data((result.found ? "found\n" : "not found\n").utf8))
|
FileHandle.standardOutput.write(Data((result.found ? "found\n" : "not found\n").utf8))
|
||||||
|
|
@ -549,7 +549,7 @@ enum UICLI {
|
||||||
return "\(sr) \(ax) \(ascr)"
|
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()
|
let encoder = JSONEncoder()
|
||||||
encoder.dateEncodingStrategy = .iso8601
|
encoder.dateEncodingStrategy = .iso8601
|
||||||
let data = try encoder.encode(value)
|
let data = try encoder.encode(value)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@
|
||||||
"clawdis": "tsx src/index.ts",
|
"clawdis": "tsx src/index.ts",
|
||||||
"clawdis:rpc": "tsx src/index.ts agent --mode rpc --json",
|
"clawdis:rpc": "tsx src/index.ts agent --mode rpc --json",
|
||||||
"lint": "biome check src",
|
"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",
|
"lint:fix": "biome check --write --unsafe src && biome format --write src",
|
||||||
"format": "biome format 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",
|
"format:fix": "biome format src --write",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:force": "tsx scripts/test-force.ts",
|
"test:force": "tsx scripts/test-force.ts",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue