clawdis-mac: enrich node list output
This commit is contained in:
@@ -5,6 +5,23 @@ import OSLog
|
||||
enum ControlRequestHandler {
|
||||
private static let cameraCapture = CameraCaptureService()
|
||||
|
||||
struct NodeListNode: Codable {
|
||||
var nodeId: String
|
||||
var displayName: String?
|
||||
var platform: String?
|
||||
var version: String?
|
||||
var remoteAddress: String?
|
||||
var connected: Bool
|
||||
var capabilities: [String]?
|
||||
}
|
||||
|
||||
struct NodeListResult: Codable {
|
||||
var ts: Int
|
||||
var connectedNodeIds: [String]
|
||||
var pairedNodeIds: [String]
|
||||
var nodes: [NodeListNode]
|
||||
}
|
||||
|
||||
static func process(
|
||||
request: Request,
|
||||
notifier: NotificationManager = NotificationManager(),
|
||||
@@ -394,14 +411,56 @@ enum ControlRequestHandler {
|
||||
}
|
||||
|
||||
private static func handleNodeList() async -> Response {
|
||||
let ids = await BridgeServer.shared.connectedNodeIds()
|
||||
let payload = (try? JSONSerialization.data(
|
||||
withJSONObject: ["connectedNodeIds": ids],
|
||||
options: [.prettyPrinted]))
|
||||
let paired = await BridgeServer.shared.pairedNodes()
|
||||
let connected = await BridgeServer.shared.connectedNodes()
|
||||
let result = self.buildNodeListResult(paired: paired, connected: connected)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
let payload = (try? encoder.encode(result))
|
||||
.flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
|
||||
return Response(ok: true, payload: Data(payload.utf8))
|
||||
}
|
||||
|
||||
static func buildNodeListResult(paired: [PairedNode], connected: [BridgeNodeInfo]) -> NodeListResult {
|
||||
let connectedById = Dictionary(uniqueKeysWithValues: connected.map { ($0.nodeId, $0) })
|
||||
|
||||
var nodesById: [String: NodeListNode] = [:]
|
||||
|
||||
for p in paired {
|
||||
let live = connectedById[p.nodeId]
|
||||
nodesById[p.nodeId] = NodeListNode(
|
||||
nodeId: p.nodeId,
|
||||
displayName: (live?.displayName ?? p.displayName),
|
||||
platform: (live?.platform ?? p.platform),
|
||||
version: (live?.version ?? p.version),
|
||||
remoteAddress: live?.remoteAddress,
|
||||
connected: live != nil,
|
||||
capabilities: live?.caps)
|
||||
}
|
||||
|
||||
for c in connected where nodesById[c.nodeId] == nil {
|
||||
nodesById[c.nodeId] = NodeListNode(
|
||||
nodeId: c.nodeId,
|
||||
displayName: c.displayName,
|
||||
platform: c.platform,
|
||||
version: c.version,
|
||||
remoteAddress: c.remoteAddress,
|
||||
connected: true,
|
||||
capabilities: c.caps)
|
||||
}
|
||||
|
||||
let nodes = nodesById.values.sorted { a, b in
|
||||
(a.displayName ?? a.nodeId) < (b.displayName ?? b.nodeId)
|
||||
}
|
||||
|
||||
return NodeListResult(
|
||||
ts: Int(Date().timeIntervalSince1970 * 1000),
|
||||
connectedNodeIds: connected.map(\.nodeId).sorted(),
|
||||
pairedNodeIds: paired.map(\.nodeId).sorted(),
|
||||
nodes: nodes)
|
||||
}
|
||||
|
||||
private static func handleNodeInvoke(
|
||||
nodeId: String,
|
||||
command: String,
|
||||
|
||||
@@ -433,6 +433,45 @@ struct ClawdisCLI {
|
||||
return
|
||||
}
|
||||
|
||||
if case .nodeList = parsed.request, let payload = response.payload {
|
||||
struct NodeListResult: Decodable {
|
||||
struct Node: Decodable {
|
||||
var nodeId: String
|
||||
var displayName: String?
|
||||
var remoteAddress: String?
|
||||
var connected: Bool
|
||||
var capabilities: [String]?
|
||||
}
|
||||
|
||||
var pairedNodeIds: [String]?
|
||||
var connectedNodeIds: [String]?
|
||||
var nodes: [Node]
|
||||
}
|
||||
|
||||
if let decoded = try? JSONDecoder().decode(NodeListResult.self, from: payload) {
|
||||
let pairedCount = decoded.pairedNodeIds?.count ?? decoded.nodes.count
|
||||
let connectedCount = decoded.connectedNodeIds?.count ?? decoded.nodes.filter(\.connected).count
|
||||
print("Paired: \(pairedCount) · Connected: \(connectedCount)")
|
||||
|
||||
for n in decoded.nodes {
|
||||
let nameTrimmed = n.displayName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let name = nameTrimmed.isEmpty ? n.nodeId : nameTrimmed
|
||||
|
||||
let ipTrimmed = n.remoteAddress?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let ip = ipTrimmed.isEmpty ? nil : ipTrimmed
|
||||
let caps = n.capabilities?.sorted().joined(separator: ",")
|
||||
let capsText = caps.map { "[\($0)]" } ?? "?"
|
||||
|
||||
var parts: [String] = ["- \(name)", n.nodeId]
|
||||
if let ip { parts.append(ip) }
|
||||
parts.append(n.connected ? "connected" : "disconnected")
|
||||
parts.append("caps: \(capsText)")
|
||||
print(parts.joined(separator: " · "))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch parsed.kind {
|
||||
case .generic:
|
||||
if let payload = response.payload, let text = String(data: payload, encoding: .utf8), !text.isEmpty {
|
||||
@@ -509,7 +548,7 @@ struct ClawdisCLI {
|
||||
[--session <key>] [--deliver] [--to <E.164>]
|
||||
|
||||
Nodes:
|
||||
clawdis-mac node list
|
||||
clawdis-mac node list # paired + connected nodes (+ capabilities when available)
|
||||
clawdis-mac node invoke --node <id> --command <name> [--params-json <json>]
|
||||
|
||||
Canvas:
|
||||
|
||||
@@ -74,6 +74,9 @@ UI automation is not part of `ClawdisIPC.Request`:
|
||||
- UI automation + capture: use `peekaboo …` (Clawdis hosts PeekabooBridge; see `docs/mac/peekaboo.md`)
|
||||
- `run -- cmd args... [--cwd] [--env KEY=VAL] [--timeout 30] [--needs-screen-recording]`
|
||||
- `status`
|
||||
- Nodes (bridge-connected companions):
|
||||
- `node list` — lists paired + currently connected nodes, including advertised capabilities (e.g. `canvas`, `camera`).
|
||||
- `node invoke --node <id> --command <name> [--params-json <json>]`
|
||||
- Sounds: supply any macOS alert name with `--sound` per notification; omit the flag to use the system default. There is no longer a persisted “default sound” in the app UI.
|
||||
- Priority: `timeSensitive` is best-effort and falls back to `active` unless the app is signed with the Time Sensitive Notifications entitlement.
|
||||
- Delivery: `overlay` and `auto` show an in-app toast panel (bypasses Notification Center/Focus).
|
||||
|
||||
Reference in New Issue
Block a user