import AppKit import SwiftUI // 🤖 Settings → Advanced → "Command-line tool": install the bundled eleven CLI // (Contents/Helpers/eleven, embedded by the "Embed eleven CLI" build phase — // see ios/project.yml and docs/eleven-cli.md) onto the user's PATH. // // The app is sandboxed, so it can't just write /usr/local/bin: the Install // button runs an NSOpenPanel (the powerbox grants write access to whatever // folder the user picks) and drops a symlink to the bundled binary there. When // even that fails — stock /usr/local/bin is root-owned — we fall back to // showing the one Terminal command that does it, with a Copy button. A symlink // (not a copy) means Sparkle updates update the CLI too. // The Advanced tab (Settings window, LChatMacApp): power-user machinery that // would crowd General — today just the CLI install (Paul, 2026-07-15). struct MacAdvancedSettingsView: View { var body: some View { Form { CLIToolSection() // CLIToolSection renders nothing when the helper isn't embedded // (a build without Go) — a wholly blank tab would read as broken, // so say the quiet part. if !FileManager.default.fileExists( atPath: Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/eleven").path) { Section { Text("The command-line tool isn't included in this build.") .font(.footnote).foregroundStyle(.secondary) } } PerfSettingsSection() } .formStyle(.grouped) // No frame — the Settings window's single fixed size lives on the // TabView (LChatMacApp), same as every other pane. } } struct CLIToolSection: View { @AppStorage("cliInstalledPath") private var installedPath = "" @State private var note: String? @State private var fallbackCommand: String? private var bundled: URL { Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/eleven") } var body: some View { // Older builds (or a build without Go on the machine) carry no helper; // don't offer to install what isn't there. if FileManager.default.fileExists(atPath: bundled.path) { Section("Command-line tool") { Text("Chat from your terminal: `eleven chats`, `eleven chat`, and `eleven send`. It signs in with the same passkey (`eleven signin`).") .font(.callout) .foregroundStyle(.secondary) HStack { if installedPath.isEmpty { Text("Not installed").foregroundStyle(.secondary) } else { Label(installedPath, systemImage: installStillValid ? "checkmark.circle" : "exclamationmark.triangle") .foregroundStyle(.secondary) .font(.callout) } Spacer() Button(installedPath.isEmpty ? "Install…" : "Reinstall…") { install() } } if let note { Text(note).font(.footnote).foregroundStyle(.secondary) } if let fallbackCommand { // That folder wasn't writable even via the powerbox (stock // /usr/local/bin is root-owned) — hand over the one-liner. VStack(alignment: .leading, spacing: 6) { Text("That folder needs an administrator — run this in Terminal instead:") .font(.footnote) HStack { Text(fallbackCommand) .font(.system(.footnote, design: .monospaced)) .textSelection(.enabled) .lineLimit(2) Button("Copy") { NSPasteboard.general.clearContents() NSPasteboard.general.setString(fallbackCommand, forType: .string) } } } } } } } // A best-effort staleness check: the symlink should still resolve to a // binary (the app may have moved). Sandbox may deny the stat — treat // unreadable as fine rather than alarm on a false negative. private var installStillValid: Bool { guard !installedPath.isEmpty else { return false } let dest = (try? FileManager.default.destinationOfSymbolicLink(atPath: installedPath)) ?? bundled.path return FileManager.default.fileExists(atPath: dest) } private func install() { note = nil fallbackCommand = nil let panel = NSOpenPanel() panel.canChooseDirectories = true panel.canChooseFiles = false panel.canCreateDirectories = true panel.allowsMultipleSelection = false panel.message = "Choose where to install the eleven command. /usr/local/bin keeps it on your PATH." panel.prompt = "Install Here" panel.directoryURL = URL(fileURLWithPath: "/usr/local/bin", isDirectory: true) guard panel.runModal() == .OK, let dir = panel.url else { return } let target = dir.appendingPathComponent("eleven") let accessing = dir.startAccessingSecurityScopedResource() defer { if accessing { dir.stopAccessingSecurityScopedResource() } } do { try? FileManager.default.removeItem(at: target) try FileManager.default.createSymbolicLink(at: target, withDestinationURL: bundled) installedPath = target.path note = onPath(dir.path) ? "Installed — open a new terminal and run `eleven signin`." : "Installed. \(dir.path) isn’t usually on PATH — add it, or reinstall to /usr/local/bin." } catch { fallbackCommand = "sudo mkdir -p /usr/local/bin && sudo ln -sf \"\(bundled.path)\" /usr/local/bin/eleven" } } // The folders shells have on PATH without any profile edits. private func onPath(_ dir: String) -> Bool { ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin", "/bin"].contains(dir) } }