1+ // ActionUI - SwiftUI component library
2+ // Copyright (c) 2025-2026 Tomasz Kukielka
13//
2- // ActionUIViewer main.swift
4+ // Licensed under the PolyForm Small Business License 1.0.0
5+ // https://polyformproject.org/licenses/small-business/1.0.0
6+
7+ //
8+ // ActionUIViewer.swift
39// ActionUIViewer
410//
511// A command-line tool to view ActionUI JSON files in a window.
6- // Usage: ActionUIViewer <path-to-json-file>
712//
813
914import SwiftUI
@@ -42,6 +47,13 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
4247 var screenshotPath : String ?
4348
4449 func applicationDidFinishLaunching( _ notification: Notification ) {
50+ Task { @MainActor in
51+ await handleApplicationLaunch ( )
52+ }
53+ }
54+
55+ @MainActor
56+ func handleApplicationLaunch( ) async {
4557 var jsonFilePath : String ?
4658 var screenshotPath : String ?
4759
@@ -62,27 +74,63 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
6274 i += 1
6375 }
6476
65- guard let path = jsonFilePath else {
66- print ( " Usage: ActionUIViewer [--screenshot <output.png>] <input.json> " )
77+ guard let pathOrUrl = jsonFilePath else {
78+ print ( " Usage: ActionUIViewer [--screenshot <output.png>] <path/to/input.json|https://url.to/ input.json> " )
6779 NSApp . terminate ( nil )
6880 return
6981 }
7082
71- let url = URL ( fileURLWithPath : path )
72- self . screenshotPath = screenshotPath
83+ var url : URL
84+ var displayTitle : String
7385
74- guard FileManager . default. fileExists ( atPath: path) else {
75- print ( " Error: File not found at path: \( path) " )
76- window = NSWindow ( contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 200 ) ,
77- styleMask: [ . titled, . closable] , backing: . buffered, defer: false )
78- window. contentView = NSHostingView ( rootView: ErrorView ( message: " File not found: \n \( path) " ) )
79- window. delegate = self
80- window. center ( )
81- window. makeKeyAndOrderFront ( nil )
82- NSApp . activate ( ignoringOtherApps: true )
83- return
86+ if pathOrUrl. hasPrefix ( " http:// " ) || pathOrUrl. hasPrefix ( " https:// " ) {
87+ guard let parsedUrl = URL ( string: pathOrUrl) else {
88+ print ( " Error: Invalid URL: \( pathOrUrl) " )
89+ NSApp . terminate ( nil )
90+ return
91+ }
92+ url = parsedUrl
93+ displayTitle = parsedUrl. lastPathComponent. isEmpty ? parsedUrl. host ?? parsedUrl. absoluteString : parsedUrl. lastPathComponent
94+
95+ print ( " Fetching remote JSON... " )
96+
97+ do {
98+ let ( data, response) = try await URLSession . shared. data ( from: url)
99+ guard let httpResponse = response as? HTTPURLResponse , httpResponse. statusCode == 200 else {
100+ print ( " Error: Failed to fetch remote JSON " )
101+ NSApp . terminate ( nil )
102+ return
103+ }
104+
105+ let tempDir = FileManager . default. temporaryDirectory
106+ let tempFile = tempDir. appendingPathComponent ( " remote_ \( UUID ( ) . uuidString) .json " )
107+ try data. write ( to: tempFile)
108+ url = tempFile
109+ print ( " Saved remote JSON to temp file: \( tempFile. path) " )
110+ } catch {
111+ print ( " Error fetching remote JSON: \( error) " )
112+ NSApp . terminate ( nil )
113+ return
114+ }
115+ } else {
116+ url = URL ( fileURLWithPath: pathOrUrl)
117+ displayTitle = url. lastPathComponent
118+
119+ guard FileManager . default. fileExists ( atPath: pathOrUrl) else {
120+ print ( " Error: File not found at path: \( pathOrUrl) " )
121+ window = NSWindow ( contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 200 ) ,
122+ styleMask: [ . titled, . closable] , backing: . buffered, defer: false )
123+ window. contentView = NSHostingView ( rootView: ErrorView ( message: " File not found: \n \( pathOrUrl) " ) )
124+ window. delegate = self
125+ window. center ( )
126+ window. makeKeyAndOrderFront ( nil )
127+ NSApp . activate ( ignoringOtherApps: true )
128+ return
129+ }
84130 }
85131
132+ self . screenshotPath = screenshotPath
133+
86134 let windowUUID = UUID ( ) . uuidString
87135 let logger = CustomLogger ( )
88136 ActionUISwift . setLogger ( logger)
@@ -93,7 +141,7 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
93141 window = NSWindow ( contentRect: NSRect ( x: 100 , y: 100 , width: 800 , height: 600 ) ,
94142 styleMask: [ . titled, . closable, . miniaturizable, . resizable] ,
95143 backing: . buffered, defer: false )
96- window. title = " ActionUI Viewer - \( url . lastPathComponent ) "
144+ window. title = " ActionUI Viewer - \( displayTitle ) "
97145 window. center ( )
98146 window. delegate = self
99147
@@ -123,7 +171,7 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
123171 return
124172 }
125173
126- let image = CGWindowListCreateImage ( CGRect . zero, . optionIncludingWindow, CGWindowID ( windowNumber) , . boundsIgnoreFraming )
174+ let image = CGWindowListCreateImage ( CGRect . zero, . optionIncludingWindow, CGWindowID ( windowNumber) , [ ] )
127175 guard let cgImage = image else {
128176 print ( " Error: Failed to capture window " )
129177 return
0 commit comments