Add local CLI access via Unix-socket server

New CLIServerService listens on a Unix domain socket
(~/Library/Application Support/oAI/cli.sock) speaking a minimal
HTTP/1.1 subset, for one-shot non-streaming shell access to a single
fixed model — e.g. an `ai "prompt"` zsh function — without opening
the app window and without going through the tool-calling loop.

Configured in Settings > MCP > CLI Access (toggle, provider, model —
deliberately independent of the chat UI's active model). JSON
request/response envelope rather than raw text so new fields (model
override, streaming, tool support) can be added later without a
breaking wire-format change.

Verified live end-to-end against a real OpenRouter request, error
paths, and clean-shutdown socket cleanup. 10 new unit tests cover
the HTTP framing/parsing logic.
This commit is contained in:
2026-08-05 13:33:02 +02:00
parent 2adce758f1
commit 897bfdce10
6 changed files with 552 additions and 0 deletions
+30
View File
@@ -1043,6 +1043,36 @@ class SettingsService {
}
}
// MARK: - CLI Server
/// Local Unix-socket server (CLIServerService) for one-shot, non-streaming shell access to a
/// single fixed model deliberately separate from the chat UI's defaultProvider/defaultModel
/// so switching models in the GUI never changes shell-tool behavior, same reasoning as the
/// email handler's own dedicated provider/model pair above.
var cliServerEnabled: Bool {
get { cache["cliServerEnabled"] == "true" }
set {
cache["cliServerEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "cliServerEnabled", value: String(newValue))
}
}
var cliServerProvider: String {
get { cache["cliServerProvider"] ?? "openrouter" }
set {
cache["cliServerProvider"] = newValue
DatabaseService.shared.setSetting(key: "cliServerProvider", value: newValue)
}
}
var cliServerModel: String {
get { cache["cliServerModel"] ?? "" }
set {
cache["cliServerModel"] = newValue
DatabaseService.shared.setSetting(key: "cliServerModel", value: newValue)
}
}
var emailSubjectIdentifier: String {
get { cache["emailSubjectIdentifier"] ?? "[OAIBOT]" }
set {