clawdis-mac: enrich node list output

This commit is contained in:
Peter Steinberger
2025-12-17 20:03:30 +00:00
parent 079c1d8786
commit c452f8c430
3 changed files with 106 additions and 5 deletions

View File

@@ -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,

View File

@@ -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:

View File

@@ -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).