Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions libs/lume/src/FileSystem/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,31 +48,52 @@ final class SettingsManager: @unchecked Sendable {

static let shared = SettingsManager()
private let fileManager: FileManager
private var customConfigFilePath: String?

// Get the config directory following XDG spec
// Get the config directory
private var configDir: String {
// Check XDG_CONFIG_HOME environment variable first
// If a custom config file path is set (via --config), use its directory
if let customPath = customConfigFilePath {
let expanded = (customPath as NSString).expandingTildeInPath
let dirPath = (expanded as NSString).deletingLastPathComponent
if !dirPath.isEmpty {
return dirPath
}
}
// Check XDG_CONFIG_HOME environment variable
if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] {
return "\(xdgConfigHome)/lume"
}
// Fall back to default
return (Constants.fallbackConfigDir as NSString).expandingTildeInPath
}

private var configFileName: String {
if let customPath = customConfigFilePath {
return (customPath as NSString).lastPathComponent
} else {
return Constants.configFileName
}
}

// Path to config file
private var configFilePath: String {
return "\(configDir)/\(Constants.configFileName)"
return "\(configDir)/\(configFileName)"
}

// MARK: - Initialization

init(fileManager: FileManager = .default) {
self.fileManager = fileManager
ensureConfigDirectoryExists()
}

// MARK: - Settings Access

/// Sets a custom config file path (for --config option)
func setCustomConfigPath(_ path: String) {
self.customConfigFilePath = path
}

func getSettings() -> LumeSettings {
if let settings = readSettingsFromFile() {
return settings
Expand Down Expand Up @@ -288,10 +309,6 @@ final class SettingsManager: @unchecked Sendable {

// MARK: - Private Helpers

private func ensureConfigDirectoryExists() {
try? fileManager.createDirectory(atPath: configDir, withIntermediateDirectories: true)
}

private func readSettingsFromFile() -> LumeSettings? {
// Read from YAML file
if fileExists(at: configFilePath) {
Expand Down
69 changes: 31 additions & 38 deletions libs/lume/src/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,41 @@ import Foundation
@main
struct Lume: AsyncParsableCommand {
static var configuration: CommandConfiguration {
CommandConfiguration(
_ = _parseTopLevelOptions // Trigger parsing of top-level options and banner display

return CommandConfiguration(
commandName: "lume",
abstract: "A lightweight CLI and local API server to build, run and manage macOS VMs.",
version: Version.current,
subcommands: CommandRegistry.allCommands,
helpNames: .long
)
}

// Run top-level parse once using a static stored property
private static let _parseTopLevelOptions: Void = {
let args = CommandLine.arguments

// Check if help is requested and print banner
if args.count == 1 || args.contains("--help") ||
(args.count == 2 && args[1] == "help") {
printBanner()
}

// Parse --config option
for (index, arg) in args.enumerated() {
if arg == "--config" && index + 1 < args.count {
let configPath = args[index + 1]
SettingsManager.shared.setCustomConfigPath(configPath)
break
}
}
}()

@Option(name: .long,
help: "Path to config file (default: $XDG_CONFIG_HOME/lume/config.yaml, falling back to ~/.config/lume/config.yaml if XDG_CONFIG_HOME is not set)"
)
var config: String?
}

// MARK: - Version Management
Expand All @@ -36,49 +63,15 @@ extension Lume {
print(banner)
print()
}

static func shouldShowBanner() -> Bool {
let args = CommandLine.arguments.dropFirst()
// Show banner when: no args, --help, -h, help, or just the root command
if args.isEmpty {
return true
}
if args.contains("--help") || args.contains("-h") {
return true
}
// Check if first arg is "help" (e.g., "lume help")
if args.first == "help" && args.count == 1 {
return true
}
return false
}
}

// MARK: - Command Execution
extension Lume {
public static func main() async {
func run() async throws {
// Record installation event on first run (sent regardless of telemetry opt-out)
TelemetryClient.shared.recordInstallation()

// Print banner when showing help
if shouldShowBanner() {
printBanner()
}

do {
try await executeCommand()
} catch {
exit(withError: error)
}
}

private static func executeCommand() async throws {
var command = try parseAsRoot()

if var asyncCommand = command as? AsyncParsableCommand {
try await asyncCommand.run()
} else {
try command.run()
}
// If no subcommand is provided, show help
throw CleanExit.helpRequest(self)
}
}