Skip to content

Commit 01308dd

Browse files
committed
Add macOS launcher and setup script for command line access
1 parent 13f7ff1 commit 01308dd

6 files changed

Lines changed: 300 additions & 28 deletions

File tree

CMakeLists.txt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.14)
22

33
project(kolosal-cli
4-
LANGUAGES CXX
4+
LANGUAGES C CXX
55
VERSION 1.0.0
66
DESCRIPTION "Kolosal CLI - A Command Line Interface to use, deploy, and manage LLMs locally"
77
)
@@ -113,6 +113,14 @@ file(GLOB_RECURSE HEADERS
113113
# Add executable
114114
add_executable(kolosal ${SOURCES} ${HEADERS})
115115

116+
# Add macOS launcher executable (small C program that launches the shell script)
117+
if(APPLE)
118+
add_executable(kolosal-launcher src/kolosal_launcher.c)
119+
set_target_properties(kolosal-launcher PROPERTIES
120+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
121+
)
122+
endif()
123+
116124
# Make kolosal depend on the server targets to ensure proper build order
117125
add_dependencies(kolosal kolosal_server_exe kolosal_server)
118126

@@ -667,6 +675,19 @@ elseif(APPLE)
667675
COMPONENT Runtime
668676
)
669677

678+
# Install the launcher executable that handles PATH setup
679+
install(TARGETS kolosal-launcher
680+
RUNTIME DESTINATION "Kolosal.app/Contents/MacOS"
681+
COMPONENT Runtime
682+
)
683+
684+
# Install the launcher script that the C launcher will execute
685+
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/kolosal-launcher.sh"
686+
DESTINATION "Kolosal.app/Contents/MacOS"
687+
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
688+
COMPONENT Runtime
689+
)
690+
670691
# Install kolosal-server executable
671692
install(TARGETS kolosal_server_exe
672693
RUNTIME DESTINATION "Kolosal.app/Contents/MacOS"
@@ -823,6 +844,16 @@ elseif(APPLE)
823844
endforeach()
824845
825846
message(STATUS \"Fixed RPATH for all installed libraries in \${ACTUAL_INSTALL_PREFIX}\")
847+
848+
# Ensure the launcher executable and script have executable permissions
849+
if(EXISTS \"\${ACTUAL_INSTALL_PREFIX}/Kolosal.app/Contents/MacOS/kolosal-launcher\")
850+
execute_process(COMMAND chmod +x \"\${ACTUAL_INSTALL_PREFIX}/Kolosal.app/Contents/MacOS/kolosal-launcher\")
851+
message(STATUS \"Set executable permissions for kolosal-launcher\")
852+
endif()
853+
if(EXISTS \"\${ACTUAL_INSTALL_PREFIX}/Kolosal.app/Contents/MacOS/kolosal-launcher.sh\")
854+
execute_process(COMMAND chmod +x \"\${ACTUAL_INSTALL_PREFIX}/Kolosal.app/Contents/MacOS/kolosal-launcher.sh\")
855+
message(STATUS \"Set executable permissions for kolosal-launcher.sh\")
856+
endif()
826857
" COMPONENT Runtime)
827858

828859
elseif(UNIX)
@@ -1136,6 +1167,7 @@ elseif(APPLE)
11361167
message(STATUS "macOS DMG packaging configured.")
11371168
message(STATUS "• Use 'make package' for standard DMG creation")
11381169
message(STATUS "• Use 'make dmg' for DMG with custom background image")
1170+
message(STATUS "• Double-clicking Kolosal.app will offer to add kolosal/kolosal-server to PATH")
11391171

11401172
else()
11411173
# DEB package specific settings (Linux)

MACOS_PACKAGING_GUIDE.md

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,65 @@
11
# macOS Packaging Guide for Kolosal CLI
22

33
## Overview
4-
The macOS packaging creates a DMG installer that contains all the necessary executables and libraries. Users can extract the contents and run an installation script for easy terminal access.
4+
The macOS packaging creates a DMG installer that contains a macOS app bundle (`Kolosal.app`). When users double-click the app, it offers to set up command line access automatically.
55

66
## New Structure
77

88
### DMG Contents
99
```
10-
Install Kolosal CLI (DMG Volume)/
11-
├── bin/
12-
│ ├── kolosal # CLI executable
13-
│ ├── kolosal-server # Server executable
14-
│ └── kolosal-launcher # Installation script
15-
├── lib/
16-
│ ├── libkolosal_server.dylib # Server library
17-
│ ├── libllama-metal.dylib # Metal inference engine
18-
│ ├── libllama-vulkan.dylib # Vulkan inference engine
19-
│ ├── libllama-cuda.dylib # CUDA inference engine
20-
│ └── libllama-cpu.dylib # CPU inference engine
21-
├── etc/
22-
│ └── config.yaml # Default configuration
10+
Kolosal [VERSION] (DMG Volume)/
11+
├── Kolosal.app/ # macOS App Bundle
12+
│ ├── Contents/
13+
│ │ ├── Info.plist # Bundle metadata
14+
│ │ ├── MacOS/
15+
│ │ │ ├── kolosal # CLI executable
16+
│ │ │ ├── kolosal-server # Server executable
17+
│ │ │ └── kolosal-launcher.sh # Setup launcher script
18+
│ │ ├── Frameworks/ # Shared libraries
19+
│ │ │ ├── libkolosal_server.dylib
20+
│ │ │ ├── libllama-metal.dylib
21+
│ │ │ ├── libllama-vulkan.dylib
22+
│ │ │ ├── libllama-cuda.dylib
23+
│ │ │ └── libllama-cpu.dylib
24+
│ │ └── Resources/ # App resources
25+
│ │ ├── config.yaml # Default configuration
26+
│ │ ├── kolosal.icns # App icon
27+
│ │ ├── README.md # Documentation
28+
│ │ └── LICENSE # License file
2329
└── Applications -> /Applications # Symlink for easy access
2430
```
2531

2632
### Installation Process
2733

2834
1. **User downloads DMG file** (e.g., `kolosal-1.0.0-apple-silicon.dmg`)
29-
2. **User opens DMG** - Mounts the disk image showing the contents
30-
3. **User runs installation** - Either:
31-
- Run the automated installation script: `./bin/kolosal-launcher`
32-
- Or manually copy files and set up paths (see manual installation below)
35+
2. **User opens DMG** - Mounts the disk image showing `Kolosal.app`
36+
3. **User drags app to Applications** - Standard macOS app installation
37+
4. **User double-clicks Kolosal.app** - Triggers command line setup
3338

34-
### Automatic Installation
39+
### Automatic Command Line Setup
3540

36-
The `kolosal-launcher` script handles:
37-
- Creates symlinks in `/usr/local/bin/` for `kolosal` and `kolosal-server`
38-
- Updates shell profiles (`.zshrc`, `.bash_profile`, etc.) to include `/usr/local/bin` in PATH
39-
- Creates user directories in `~/Library/Application Support/Kolosal/`
40-
- Copies config file to user directory
41-
- Shows completion message with usage instructions
41+
When the user double-clicks `Kolosal.app`, the `kolosal-launcher.sh` script:
42+
43+
1. **Checks current setup** - Verifies if symlinks already exist
44+
2. **Shows setup dialog** - Asks user if they want command line access
45+
3. **Creates symlinks** - Places `kolosal` and `kolosal-server` in `/usr/local/bin/`
46+
4. **Shows completion** - Offers to open Terminal to test commands
47+
48+
### User Experience
49+
50+
#### First Launch
51+
- User sees dialog: "Welcome to Kolosal CLI! To use Kolosal commands from Terminal..."
52+
- Options: "Skip" or "Set Up Command Line Access"
53+
- If setup chosen: Prompts for admin password, creates symlinks
54+
- Shows success dialog with option to open Terminal
55+
56+
#### Subsequent Launches
57+
- If already set up: Shows "Commands are ready!" with option to open Terminal
58+
- If setup incomplete: Re-offers setup process
4259

4360
### Terminal Access
4461

45-
After installation, users can immediately use:
62+
After setup, users can use from any Terminal:
4663
```bash
4764
kolosal --help
4865
kolosal-server --help

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ A cross-platform command-line interface for discovering, downloading, and runnin
1717

1818
The easiest way to get started is to download a pre-built binary for your operating system from the [**Releases**](https://github.com/KolosalAI/kolosal-cli/releases) page.
1919

20+
### macOS App Bundle
21+
22+
On macOS, Kolosal is distributed as a `.dmg` file containing a `Kolosal.app` bundle. When you double-click the app:
23+
24+
1. **First Launch**: The app will offer to set up command line access by creating symlinks in `/usr/local/bin`
25+
2. **Subsequent Launches**: If already set up, it will show status and optionally open Terminal
26+
3. **Manual Setup**: If you decline automatic setup, the app provides instructions for manual PATH configuration
27+
28+
This allows you to use `kolosal` and `kolosal-server` commands from any Terminal window after setup.
29+
2030
### Quick Start
2131

2232
Once installed, you can run the application from your terminal:

resources/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>CFBundleExecutable</key>
6-
<string>kolosal</string>
6+
<string>kolosal-launcher</string>
77
<key>CFBundleGetInfoString</key>
88
<string>Kolosal - Command Line Interface for LLM Management</string>
99
<key>CFBundleIconFile</key>

resources/kolosal-launcher.sh

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/bin/bash
2+
3+
# Kolosal macOS App Launcher Script
4+
# This script is executed when the user double-clicks the Kolosal.app
5+
# It sets up PATH access and provides user-friendly setup
6+
7+
# Get the directory where this script is located (Contents/MacOS)
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
APP_CONTENTS="$(dirname "$SCRIPT_DIR")"
10+
APP_BUNDLE="$(dirname "$APP_CONTENTS")"
11+
12+
# Paths to the executables
13+
KOLOSAL_EXECUTABLE="$SCRIPT_DIR/kolosal"
14+
KOLOSAL_SERVER_EXECUTABLE="$SCRIPT_DIR/kolosal-server"
15+
16+
# Function to create symlinks in /usr/local/bin
17+
create_symlinks() {
18+
local target_dir="/usr/local/bin"
19+
20+
# Check if /usr/local/bin exists and is writable, or if we can create it
21+
if [[ ! -d "$target_dir" ]]; then
22+
# Use osascript to run mkdir with admin privileges
23+
if ! osascript -e "do shell script \"mkdir -p '$target_dir'\" with administrator privileges" 2>/dev/null; then
24+
echo "Failed to create $target_dir directory"
25+
return 1
26+
fi
27+
fi
28+
29+
# Create both symlinks in a single osascript call to avoid double authentication
30+
if [[ -f "$KOLOSAL_EXECUTABLE" && -f "$KOLOSAL_SERVER_EXECUTABLE" ]]; then
31+
local combined_command="ln -sf '$KOLOSAL_EXECUTABLE' '$target_dir/kolosal' && ln -sf '$KOLOSAL_SERVER_EXECUTABLE' '$target_dir/kolosal-server'"
32+
if ! osascript -e "do shell script \"$combined_command\" with administrator privileges" 2>/dev/null; then
33+
echo "Failed to create symlinks"
34+
return 1
35+
fi
36+
echo "✓ Created symlink: $target_dir/kolosal"
37+
echo "✓ Created symlink: $target_dir/kolosal-server"
38+
else
39+
echo "Error: One or both executables not found"
40+
return 1
41+
fi
42+
43+
return 0
44+
}
45+
46+
# Function to show notification
47+
show_notification() {
48+
local title="$1"
49+
local message="$2"
50+
51+
osascript -e "display notification \"$message\" with title \"$title\"" 2>/dev/null || true
52+
}
53+
54+
# Function to check if symlinks already exist and are correct
55+
check_symlinks() {
56+
local target_dir="/usr/local/bin"
57+
local kolosal_ok=false
58+
local server_ok=false
59+
60+
# Check kolosal symlink
61+
if [[ -L "$target_dir/kolosal" ]]; then
62+
local current_target="$(readlink "$target_dir/kolosal")"
63+
if [[ "$current_target" == "$KOLOSAL_EXECUTABLE" ]]; then
64+
kolosal_ok=true
65+
fi
66+
fi
67+
68+
# Check kolosal-server symlink
69+
if [[ -L "$target_dir/kolosal-server" ]]; then
70+
local current_target="$(readlink "$target_dir/kolosal-server")"
71+
if [[ "$current_target" == "$KOLOSAL_SERVER_EXECUTABLE" ]]; then
72+
server_ok=true
73+
fi
74+
fi
75+
76+
# Return true only if both are correctly set up
77+
if [[ "$kolosal_ok" == true && "$server_ok" == true ]]; then
78+
return 0
79+
fi
80+
81+
return 1
82+
}
83+
84+
# Function to show setup dialog
85+
show_setup_dialog() {
86+
local message="Welcome to Kolosal CLI!\n\nTo use Kolosal commands (kolosal, kolosal-server) from any Terminal window, we can set up command line access.\n\nThis will:\n• Create symlinks in /usr/local/bin\n• Require administrator privileges\n• Make commands available globally\n\nAlternatively, you can skip this and use the executables directly from the app bundle."
87+
88+
local response
89+
response=$(osascript -e "display dialog \"$message\" buttons {\"Skip\", \"Set Up Command Line Access\"} default button \"Set Up Command Line Access\" with title \"Kolosal Setup\" with icon note" 2>/dev/null)
90+
91+
if [[ "$response" == *"Set Up Command Line Access"* ]]; then
92+
return 0
93+
else
94+
return 1
95+
fi
96+
}
97+
98+
# Function to show alternative setup instructions
99+
show_manual_setup() {
100+
local manual_path="export PATH=\"$SCRIPT_DIR:\$PATH\""
101+
local alias_kolosal="alias kolosal='$KOLOSAL_EXECUTABLE'"
102+
local alias_server="alias kolosal-server='$KOLOSAL_SERVER_EXECUTABLE'"
103+
104+
local message="Command line setup was not completed.\n\nTo use Kolosal from Terminal manually, you can:\n\n1. Add to your shell profile (~/.zshrc or ~/.bash_profile):\n $manual_path\n\n2. Or create aliases:\n $alias_kolosal\n $alias_server\n\n3. Or run directly:\n $KOLOSAL_EXECUTABLE\n $KOLOSAL_SERVER_EXECUTABLE"
105+
106+
osascript -e "display dialog \"$message\" buttons {\"Copy Path to Clipboard\", \"OK\"} default button \"OK\" with title \"Kolosal Manual Setup\"" 2>/dev/null
107+
108+
if [[ $? -eq 0 ]]; then
109+
# Check if user clicked "Copy Path to Clipboard"
110+
local button_response
111+
button_response=$(osascript -e "display dialog \"$message\" buttons {\"Copy Path to Clipboard\", \"OK\"} default button \"OK\" with title \"Kolosal Manual Setup\"" 2>/dev/null)
112+
if [[ "$button_response" == *"Copy Path to Clipboard"* ]]; then
113+
echo "$SCRIPT_DIR" | pbcopy
114+
show_notification "Kolosal" "Path copied to clipboard!"
115+
fi
116+
fi
117+
}
118+
119+
# Function to show success message and open terminal
120+
show_success() {
121+
local message="✅ Kolosal command line access is now set up!\n\nYou can now use these commands in any Terminal window:\n• kolosal --help\n• kolosal-server --help\n\nWould you like to open Terminal to try it out?"
122+
123+
local response
124+
response=$(osascript -e "display dialog \"$message\" buttons {\"Later\", \"Open Terminal\"} default button \"Open Terminal\" with title \"Kolosal Setup Complete\"" 2>/dev/null)
125+
126+
if [[ "$response" == *"Open Terminal"* ]]; then
127+
# Open terminal with helpful commands
128+
osascript -e 'tell application "Terminal"
129+
activate
130+
do script "echo \"🎉 Kolosal is now available! Try these commands:\"; echo \" kolosal --help\"; echo \" kolosal-server --help\"; echo \"\""
131+
end tell' 2>/dev/null || true
132+
fi
133+
}
134+
135+
# Main execution
136+
main() {
137+
# Check if this is being run from within the app bundle
138+
if [[ ! -f "$KOLOSAL_EXECUTABLE" ]]; then
139+
echo "Error: kolosal executable not found at $KOLOSAL_EXECUTABLE"
140+
show_notification "Kolosal Error" "Installation appears to be corrupted"
141+
exit 1
142+
fi
143+
144+
# Check if symlinks are already set up correctly
145+
if check_symlinks; then
146+
echo "✅ Kolosal commands are already accessible from Terminal"
147+
show_notification "Kolosal" "Commands are ready to use in Terminal"
148+
149+
# Show reminder message
150+
local message="Kolosal commands are already set up and ready to use!\n\n• kolosal --help\n• kolosal-server --help\n\nWould you like to open Terminal?"
151+
local response
152+
response=$(osascript -e "display dialog \"$message\" buttons {\"No\", \"Open Terminal\"} default button \"Open Terminal\" with title \"Kolosal Ready\"" 2>/dev/null)
153+
154+
if [[ "$response" == *"Open Terminal"* ]]; then
155+
osascript -e 'tell application "Terminal"
156+
activate
157+
do script "echo \"Kolosal commands are ready:\"; echo \" kolosal --help\"; echo \" kolosal-server --help\"; echo \"\""
158+
end tell' 2>/dev/null || true
159+
fi
160+
else
161+
# Show setup dialog
162+
if show_setup_dialog; then
163+
echo "Setting up command line access..."
164+
165+
if create_symlinks; then
166+
echo "✅ Successfully created command line access!"
167+
show_notification "Kolosal" "Setup complete! Commands available in Terminal"
168+
show_success
169+
else
170+
echo "❌ Failed to set up command line access"
171+
show_notification "Kolosal" "Setup failed - you may need administrator privileges"
172+
show_manual_setup
173+
fi
174+
else
175+
echo "User chose to skip automatic setup"
176+
show_notification "Kolosal" "App bundle is ready to use"
177+
show_manual_setup
178+
fi
179+
fi
180+
}
181+
182+
# Run the main function
183+
main "$@"

0 commit comments

Comments
 (0)