@@ -89,6 +89,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, UNUs
8989 NSApp . applicationIconImage = icon
9090 }
9191
92+ // Install `kanban` CLI to ~/.local/bin
93+ Self . installCLI ( )
94+
9295 // Set up notifications: delegate must be set BEFORE requesting authorization
9396 let center = UNUserNotificationCenter . current ( )
9497 center. delegate = self
@@ -101,6 +104,51 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, UNUs
101104 }
102105 }
103106
107+ /// Install a `kanban` shell script to ~/.local/bin so users can run
108+ /// `kanban`, `kanban .`, or `kanban /path/to/project` from any terminal.
109+ private static func installCLI( ) {
110+ let binDir = FileManager . default. homeDirectoryForCurrentUser
111+ . appendingPathComponent ( " .local/bin " )
112+ let scriptPath = binDir. appendingPathComponent ( " kanban " )
113+ // The CLI writes the path to a file, then activates the app.
114+ // The app picks up the file in applicationDidBecomeActive.
115+ // This avoids URL scheme issues that create duplicate windows.
116+ let script = """
117+ #!/bin/sh
118+ # Installed by Kanban Code — opens the app and selects a project.
119+ # Usage: kanban [path] (defaults to current directory)
120+ if [ -n " $1 " ]; then
121+ resolved= " $(cd " $1 " 2>/dev/null && pwd -P || echo " $1 " ) "
122+ mkdir -p ~/.kanban-code
123+ echo " $resolved " > ~/.kanban-code/open-project
124+ fi
125+ open -a " KanbanCode "
126+ """
127+ do {
128+ try FileManager . default. createDirectory ( at: binDir, withIntermediateDirectories: true )
129+ try script. write ( to: scriptPath, atomically: true , encoding: . utf8)
130+ try FileManager . default. setAttributes (
131+ [ . posixPermissions: 0o755 ] , ofItemAtPath: scriptPath. path
132+ )
133+ } catch {
134+ print ( " [Kanban Code] Failed to install CLI: \( error) " )
135+ }
136+ }
137+
138+ /// Check for a pending project open request from the CLI.
139+ func applicationDidBecomeActive( _ notification: Notification ) {
140+ let file = FileManager . default. homeDirectoryForCurrentUser
141+ . appendingPathComponent ( " .kanban-code/open-project " )
142+ guard let path = try ? String ( contentsOf: file, encoding: . utf8)
143+ . trimmingCharacters ( in: . whitespacesAndNewlines) ,
144+ !path. isEmpty else { return }
145+ try ? FileManager . default. removeItem ( at: file)
146+ NotificationCenter . default. post (
147+ name: . kanbanCodeOpenProject, object: nil ,
148+ userInfo: [ " path " : path]
149+ )
150+ }
151+
104152 /// Prevent Cmd+W from closing the single window — close terminal tab instead.
105153 func windowShouldClose( _ sender: NSWindow ) -> Bool {
106154 NotificationCenter . default. post ( name: . kanbanCloseTerminalTab, object: nil )
@@ -150,7 +198,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, UNUs
150198 process. waitUntilExit ( )
151199 }
152200
153- // Handle kanbancode:// deep links (from Pushover tap, browser, etc.)
201+ // Handle kanbancode:// deep links (from Pushover tap, browser, CLI, etc.)
154202 func application( _ application: NSApplication , open urls: [ URL ] ) {
155203 for url in urls {
156204 guard url. scheme == " kanbancode " else { continue }
@@ -162,6 +210,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, UNUs
162210 userInfo: [ " cardId " : cardId]
163211 )
164212 }
213+ // kanbancode://open?path=/some/project
214+ if url. host == " open " ,
215+ let components = URLComponents ( url: url, resolvingAgainstBaseURL: false ) ,
216+ let path = components. queryItems? . first ( where: { $0. name == " path " } ) ? . value,
217+ !path. isEmpty {
218+ NotificationCenter . default. post (
219+ name: . kanbanCodeOpenProject, object: nil ,
220+ userInfo: [ " path " : path]
221+ )
222+ }
165223 }
166224 NSApp . activate ( ignoringOtherApps: true )
167225 if let window = NSApp . windows. first ( where: { $0. canBecomeMain } ) {
@@ -236,4 +294,5 @@ extension Notification.Name {
236294 static let kanbanCloseTerminalTab = Notification . Name ( " kanbanCloseTerminalTab " )
237295 static let chatCardExpanded = Notification . Name ( " chatCardExpanded " )
238296 static let kanbanCodeAddLink = Notification . Name ( " kanbanCodeAddLink " )
297+ static let kanbanCodeOpenProject = Notification . Name ( " kanbanCodeOpenProject " )
239298}
0 commit comments