From df3c066e8e8a5dcba73fcd2f20e7e657d3ce4d08 Mon Sep 17 00:00:00 2001 From: arzafran Date: Thu, 8 Oct 2026 12:15:02 -0300 Subject: [PATCH] chore: remove cmux upgrade shims --- CLI/CLI+AgentWrappers.swift | 2 -- CLI/SocketClient.swift | 3 +- CLI/programa.swift | 2 +- Resources/settings.schema.json | 4 +-- Resources/shell-integration/fish/config.fish | 4 +-- .../programa-bash-integration.bash | 4 +-- .../programa-zsh-integration.zsh | 4 +-- Sources/ContentView.swift | 4 +-- Sources/GitWorktreeManager.swift | 8 ++--- Sources/JSONCParser.swift | 2 +- Sources/ProgramaApp.swift | 19 ------------ Sources/ProgramaConfig.swift | 17 +++-------- Sources/ProgramaDirectoryTrust.swift | 10 ++----- Sources/ProgramaSettingsFileStore.swift | 12 ++------ Sources/SocketControlSettings.swift | 3 +- docs/environment-variables.md | 4 +-- docs/plans/rust-core-concepts.md | 3 +- docs/programa-json.md | 5 ++-- docs/removed/README.md | 1 + docs/removed/cmux-upgrade-shims.md | 29 +++++++++++++++++++ docs/settings-json.md | 4 +-- docs/socket-api.md | 2 +- programaTests/GhosttyConfigTests.swift | 2 -- .../Bonsplit/Public/DebugEventLog.swift | 20 +------------ .../BonsplitTests/DebugEventLogTests.swift | 12 +------- 25 files changed, 61 insertions(+), 119 deletions(-) create mode 100644 docs/removed/cmux-upgrade-shims.md diff --git a/CLI/CLI+AgentWrappers.swift b/CLI/CLI+AgentWrappers.swift index c98ee305a..ae912b4bf 100644 --- a/CLI/CLI+AgentWrappers.swift +++ b/CLI/CLI+AgentWrappers.swift @@ -13,9 +13,7 @@ extension ProgramaCLI { guard let data = FileManager.default.contents(atPath: path) else { return false } let prefixData = data.prefix(512) guard let prefix = String(data: prefixData, encoding: .utf8) else { return false } - // Legacy cmux name, still matched so wrappers installed by older versions are recognized. return prefix.contains("programa claude wrapper - injects hooks and session tracking") - || prefix.contains("cmux claude wrapper - injects hooks and session tracking") } func resolveExecutableInSearchPath( diff --git a/CLI/SocketClient.swift b/CLI/SocketClient.swift index 6cf5e4887..2b1edaee0 100644 --- a/CLI/SocketClient.swift +++ b/CLI/SocketClient.swift @@ -35,8 +35,7 @@ final class SocketClient { private static let maxSocketTimeoutSeconds: TimeInterval = 9_007_199_254_740_991 private static let responseTimeoutSeconds: TimeInterval = { let env = ProcessInfo.processInfo.environment - // Legacy cmux name (CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC), still read so existing scripts keep working. - if let raw = env["PROGRAMA_CLI_RESPONSE_TIMEOUT_SEC"] ?? env["CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC"], + if let raw = env["PROGRAMA_CLI_RESPONSE_TIMEOUT_SEC"], let seconds = Double(raw), seconds.isFinite, seconds > 0 { diff --git a/CLI/programa.swift b/CLI/programa.swift index 7d8fabf71..a4f12226a 100644 --- a/CLI/programa.swift +++ b/CLI/programa.swift @@ -111,7 +111,7 @@ enum SocketPasswordResolver { } let candidate = URL(fileURLWithPath: socketPath).lastPathComponent - let prefixes = ["programa-debug-", "programa-", "cmux-debug-", "cmux-"] // Legacy cmux socket names, still read so older sockets keep resolving a scope. + let prefixes = ["programa-debug-", "programa-"] for prefix in prefixes { guard candidate.hasPrefix(prefix), candidate.hasSuffix(".sock") else { continue } let start = candidate.index(candidate.startIndex, offsetBy: prefix.count) diff --git a/Resources/settings.schema.json b/Resources/settings.schema.json index 1d78de791..0942e9f91 100644 --- a/Resources/settings.schema.json +++ b/Resources/settings.schema.json @@ -258,7 +258,7 @@ "properties": { "socketControlMode": { "type": "string", - "enum": ["off", "programaOnly", "cmuxOnly", "automation", "password", "allowAll", "openAccess", "fullOpenAccess", "notifications", "full"], + "enum": ["off", "programaOnly", "automation", "password", "allowAll", "openAccess", "fullOpenAccess", "notifications", "full"], "default": "programaOnly", "description": "Socket control mode. Legacy aliases are accepted and normalized." }, @@ -312,7 +312,7 @@ "type": "string" }, "default": [], - "description": "Directories whose programa.json (or legacy cmux.json) commands can run without confirmation." + "description": "Directories whose programa.json commands can run without confirmation." } } }, diff --git a/Resources/shell-integration/fish/config.fish b/Resources/shell-integration/fish/config.fish index 380ad2bf7..01a0f6223 100644 --- a/Resources/shell-integration/fish/config.fish +++ b/Resources/shell-integration/fish/config.fish @@ -50,9 +50,7 @@ if test "$_programa_integration_enabled" != 0 printf '%s\n' "$PROGRAMA_BUNDLED_CLI_PATH" return 0 end - # Rebranded CLI binary ships as "programa"; fall back to the pre-rebrand - # "cmux" name for older installs that only symlinked that binary. - command -v programa 2>/dev/null; or command -v cmux 2>/dev/null + command -v programa 2>/dev/null end function _programa_socket_uses_remote_relay diff --git a/Resources/shell-integration/programa-bash-integration.bash b/Resources/shell-integration/programa-bash-integration.bash index 2eea89ad7..f601eb5b0 100644 --- a/Resources/shell-integration/programa-bash-integration.bash +++ b/Resources/shell-integration/programa-bash-integration.bash @@ -9,9 +9,7 @@ _programa_relay_cli_path() { printf '%s\n' "${PROGRAMA_BUNDLED_CLI_PATH}" return 0 fi - # Rebranded CLI binary ships as "programa"; fall back to the pre-rebrand - # "cmux" name for older installs that only symlinked that binary. - command -v programa 2>/dev/null || command -v cmux 2>/dev/null + command -v programa 2>/dev/null } _programa_socket_uses_remote_relay() { diff --git a/Resources/shell-integration/programa-zsh-integration.zsh b/Resources/shell-integration/programa-zsh-integration.zsh index 4cc8eddee..0fe18edc8 100644 --- a/Resources/shell-integration/programa-zsh-integration.zsh +++ b/Resources/shell-integration/programa-zsh-integration.zsh @@ -10,9 +10,7 @@ _programa_relay_cli_path() { print -r -- "${PROGRAMA_BUNDLED_CLI_PATH}" return 0 fi - # Rebranded CLI binary ships as "programa"; fall back to the pre-rebrand - # "cmux" name for older installs that only symlinked that binary. - command -v programa 2>/dev/null || command -v cmux 2>/dev/null + command -v programa 2>/dev/null } _programa_socket_uses_remote_relay() { diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 53743a08f..c89d85237 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -4923,9 +4923,7 @@ struct ContentView: View { guard let data = UserDefaults.standard.data(forKey: Self.commandPaletteUsageDefaultsKey) else { return [:] } - let history = (try? JSONDecoder().decode([String: CommandPaletteUsageEntry].self, from: data)) ?? [:] - // Legacy cmux name, still read so existing custom-command usage history keeps working. - return Dictionary(history.map { ($0.key.replacingOccurrences(of: "cmux.config.", with: "programa.config.", options: .anchored), $0.value) }) { current, _ in current } + return (try? JSONDecoder().decode([String: CommandPaletteUsageEntry].self, from: data)) ?? [:] } private func persistCommandPaletteUsageHistory(_ history: [String: CommandPaletteUsageEntry]) { diff --git a/Sources/GitWorktreeManager.swift b/Sources/GitWorktreeManager.swift index 526d25a0e..2b220654b 100644 --- a/Sources/GitWorktreeManager.swift +++ b/Sources/GitWorktreeManager.swift @@ -306,12 +306,8 @@ enum ProgramaWorktreeSettings { private static func readSettingsRoot() -> [String: Any]? { let home = FileManager.default.homeDirectoryForCurrentUser.path - let newPath = (home as NSString).appendingPathComponent(".config/programa/settings.json") - // Legacy cmux name, still read so existing ~/.config/cmux/settings.json files keep working. - let legacyPath = (home as NSString).appendingPathComponent(".config/cmux/settings.json") - let fm = FileManager.default - let path = fm.fileExists(atPath: newPath) ? newPath : legacyPath - guard let data = fm.contents(atPath: path), !data.isEmpty else { return nil } + let path = (home as NSString).appendingPathComponent(".config/programa/settings.json") + guard let data = FileManager.default.contents(atPath: path), !data.isEmpty else { return nil } guard let sanitized = try? JSONCParser.preprocess(data: data), let object = try? JSONSerialization.jsonObject(with: sanitized, options: []) else { return nil diff --git a/Sources/JSONCParser.swift b/Sources/JSONCParser.swift index 1b3e766fe..1c67410bf 100644 --- a/Sources/JSONCParser.swift +++ b/Sources/JSONCParser.swift @@ -9,7 +9,7 @@ import Foundation /// so a comment marker or trailing comma inside a quoted string is left alone. /// /// Shared by `ProgramaConfigStore` (`~/.config/programa/programa.json` and -/// project-local `programa.json`/legacy `cmux.json`) and `ProgramaSettingsFileStore` +/// project-local `programa.json`) and `ProgramaSettingsFileStore` /// (`~/.config/programa/settings.json`) so both config surfaces accept the /// same JSONC dialect. enum JSONCParser { diff --git a/Sources/ProgramaApp.swift b/Sources/ProgramaApp.swift index 0fb1ac20b..e5468f38d 100644 --- a/Sources/ProgramaApp.swift +++ b/Sources/ProgramaApp.swift @@ -133,9 +133,6 @@ struct programaApp: App { ) let primaryTabManagerStore = PrimaryTabManagerStore() _primaryTabManagerStore = StateObject(wrappedValue: primaryTabManagerStore) - // Rebrand: forward every legacy cmux-prefixed default to its programa key - // before anything reads the new keys, so existing users keep their prefs. - Self.migrateCmuxDefaultsToProgramaIfNeeded(defaults: defaults) // Migrate legacy and old-format socket mode values to the new enum. if let stored = defaults.string(forKey: SocketControlSettings.appStorageKey) { let migrated = SocketControlSettings.migrateMode(stored) @@ -240,22 +237,6 @@ struct programaApp: App { setenv(key, updated, 1) } - /// One-time rebrand migration: copy every `cmux`-prefixed UserDefaults value to - /// the corresponding `programa`-prefixed key. Version-gated so it runs once, and - /// never deletes the legacy keys (a downgrade still finds its old values). - private static func migrateCmuxDefaultsToProgramaIfNeeded(defaults: UserDefaults) { - let migrationKey = "programaDefaultsRebrandMigrationVersion" - let targetVersion = 1 - guard defaults.integer(forKey: migrationKey) < targetVersion else { return } - for (key, value) in defaults.dictionaryRepresentation() where key.hasPrefix("cmux") { - let newKey = "programa" + key.dropFirst("cmux".count) - if defaults.object(forKey: newKey) == nil { - defaults.set(value, forKey: newKey) - } - } - defaults.set(targetVersion, forKey: migrationKey) - } - private static func migrateLanguageOverrideRemovalIfNeeded(defaults: UserDefaults) { let migrationKey = "programaLanguageOverrideRemovalVersion" let targetVersion = 1 diff --git a/Sources/ProgramaConfig.swift b/Sources/ProgramaConfig.swift index 41ebcd338..a4fd2141c 100644 --- a/Sources/ProgramaConfig.swift +++ b/Sources/ProgramaConfig.swift @@ -376,13 +376,7 @@ final class ProgramaConfigStore: ObservableObject { var localConfigPath: String? var globalConfigPath: String = { let home = FileManager.default.homeDirectoryForCurrentUser.path - let newPath = (home as NSString).appendingPathComponent(".config/programa/programa.json") - // Legacy cmux name, still read so existing ~/.config/cmux/cmux.json files keep working. - let legacyPath = (home as NSString).appendingPathComponent(".config/cmux/cmux.json") - let fm = FileManager.default - if fm.fileExists(atPath: newPath) { return newPath } - if fm.fileExists(atPath: legacyPath) { return legacyPath } - return newPath + return (home as NSString).appendingPathComponent(".config/programa/programa.json") }() private var cancellables = Set() @@ -455,12 +449,9 @@ final class ProgramaConfigStore: ObservableObject { var current = directory let fs = FileManager.default while true { - // Legacy cmux name, still read so existing project roots keep working. - for name in ["programa.json", "cmux.json"] { - let candidate = (current as NSString).appendingPathComponent(name) - if fs.fileExists(atPath: candidate) { - return candidate - } + let candidate = (current as NSString).appendingPathComponent("programa.json") + if fs.fileExists(atPath: candidate) { + return candidate } let parent = (current as NSString).deletingLastPathComponent if parent == current { break } diff --git a/Sources/ProgramaDirectoryTrust.swift b/Sources/ProgramaDirectoryTrust.swift index b0d86a8ee..f17eda575 100644 --- a/Sources/ProgramaDirectoryTrust.swift +++ b/Sources/ProgramaDirectoryTrust.swift @@ -248,7 +248,7 @@ final class ProgramaDirectoryTrust: @unchecked Sendable { /// means "nothing trusted yet". /// /// A version-2 digest was recorded for whichever config was trusted under that root, which - /// in practice is the root's own `programa.json` (or legacy `cmux.json`), so it migrates to + /// in practice is the root's own `programa.json`, so it migrates to /// that file's key. If the guess is wrong the affected config prompts once as `.changed`. /// Entries with no digest migrate to an empty map and prompt once too. private static func load(fromStorePath path: String) -> [String: ConfigDigests] { @@ -283,13 +283,7 @@ final class ProgramaDirectoryTrust: @unchecked Sendable { } private static func migratedConfigPath(forRoot root: String) -> String { - let programaPath = (root as NSString).appendingPathComponent("programa.json") - let legacyPath = (root as NSString).appendingPathComponent("cmux.json") - let fm = FileManager.default - if !fm.fileExists(atPath: programaPath), fm.fileExists(atPath: legacyPath) { - return legacyPath - } - return programaPath + (root as NSString).appendingPathComponent("programa.json") } /// Caller holds `stateLock`; notification is deliberately posted after unlocking so a diff --git a/Sources/ProgramaSettingsFileStore.swift b/Sources/ProgramaSettingsFileStore.swift index ac500b279..16ec7c2e4 100644 --- a/Sources/ProgramaSettingsFileStore.swift +++ b/Sources/ProgramaSettingsFileStore.swift @@ -39,13 +39,7 @@ final class ProgramaSettingsFileStore { static var defaultPrimaryPath: String { let home = FileManager.default.homeDirectoryForCurrentUser.path - let newPath = (home as NSString).appendingPathComponent(".config/programa/settings.json") - // Legacy cmux name, still read so existing ~/.config/cmux/settings.json files keep working. - let legacyPath = (home as NSString).appendingPathComponent(".config/cmux/settings.json") - let fm = FileManager.default - if fm.fileExists(atPath: newPath) { return newPath } - if fm.fileExists(atPath: legacyPath) { return legacyPath } - return newPath + return (home as NSString).appendingPathComponent(".config/programa/settings.json") } static var defaultFallbackPath: String? { @@ -721,7 +715,7 @@ final class ProgramaSettingsFileStore { ) { if let raw = jsonString(section["socketControlMode"]) { let knownModes = Set([ - "off", "cmuxonly", "programaonly", "automation", "password", "allowall", "openaccess", "fullopenaccess", + "off", "programaonly", "automation", "password", "allowall", "openaccess", "fullopenaccess", "notifications", "full", ]) let normalizedRaw = raw.replacingOccurrences(of: "-", with: "").lowercased() @@ -1788,7 +1782,7 @@ private enum BackupValue: Codable, Equatable { } // JSONCParser (comments + trailing-comma stripping) now lives in Sources/JSONCParser.swift, -// shared with ProgramaConfigStore's programa.json (and legacy cmux.json) parsing. +// shared with ProgramaConfigStore's programa.json parsing. // NOTE (drift, refs #100): unlike ProgramaConfigStore's local/global config watchers, this // watcher has no delayed retry/backoff at all — on delete/rename it re-evaluates and diff --git a/Sources/SocketControlSettings.swift b/Sources/SocketControlSettings.swift index 767435f57..f6682470c 100644 --- a/Sources/SocketControlSettings.swift +++ b/Sources/SocketControlSettings.swift @@ -433,8 +433,7 @@ struct SocketControlSettings { switch normalizeMode(raw) { case "off": return .off - // Legacy cmux name, still read so existing socket mode settings keep working. - case "cmuxonly", "programaonly": + case "programaonly": return .programaOnly case "automation": return .automation diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 26174563f..99b4c4217 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -47,7 +47,7 @@ Read by the app, the CLI and `programa-mcp`. Modes and discovery are described i | `PROGRAMA_SOCKET_PATH` | CLI, MCP, app | Socket path to use. The app honors it for Debug and Staging builds, or when `PROGRAMA_ALLOW_SOCKET_OVERRIDE` is truthy. | | `PROGRAMA_SOCKET` | CLI, MCP | Second choice for the socket path. `PROGRAMA_SOCKET_PATH` wins when both are set. The app never reads it. | | `PROGRAMA_SOCKET_PASSWORD` | CLI, MCP, app | Socket password for `password` mode. The CLI checks `--password` first. The app checks it before the saved password file. | -| `PROGRAMA_SOCKET_MODE` | app | Socket mode for this launch: `off`, `programaOnly` (legacy alias `cmuxOnly`), `automation`, `password` or `allowAll`. Overrides the setting. | +| `PROGRAMA_SOCKET_MODE` | app | Socket mode for this launch: `off`, `programaOnly`, `automation`, `password` or `allowAll`. Overrides the setting. | | `PROGRAMA_SOCKET_ENABLE` | app | `1`/`true`/`yes`/`on` or `0`/`false`/`no`/`off`. `0` turns the socket off regardless of the mode. | | `PROGRAMA_ALLOW_SOCKET_OVERRIDE` | app | Truthy value lets `PROGRAMA_SOCKET_PATH` move the socket in a Release build or a tagged Debug build. | | `PROGRAMA_TAG` | app, CLI | Name of a tagged build. The app uses it for the tag badge (at most 10 characters), for the tagged socket path, and to allow a Debug launch. The CLI adds `/tmp/programa-debug-.sock` to its socket search. An untagged Debug build refuses to launch. | @@ -59,7 +59,7 @@ Read by the app, the CLI and `programa-mcp`. Modes and discovery are described i | `PROGRAMA_PANE_ID` | Default pane target for the tmux-compatible commands when they are given none. | | `PROGRAMA_COMMIT` | Commit hash the CLI and the About box show when the build has no embedded value. | | `PROGRAMA_CLI_TTY_NAME`, `PROGRAMA_TTY_NAME` | Terminal device name that the agent hooks report for the surface. They fall back to `TTY` and `SSH_TTY`. | -| `PROGRAMA_CLI_RESPONSE_TIMEOUT_SEC` | CLI | Seconds the CLI waits for a socket response (default 15). The legacy name `CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC` is still read. | +| `PROGRAMA_CLI_RESPONSE_TIMEOUT_SEC` | CLI | Seconds the CLI waits for a socket response (default 15). | | `PROGRAMA_CLAUDE_HOOK_STATE_PATH` | Overrides where the Claude, Codex and OpenCode hooks keep their session state file. | | `PROGRAMA_CLAUDE_PID` | Process id of the Claude Code instance a hook belongs to. Set by the `claude` wrapper. | | `PROGRAMA_CLAUDE_HOOK_PROGRAMA_BIN` | `programa` binary the `claude` wrapper's hooks call. | diff --git a/docs/plans/rust-core-concepts.md b/docs/plans/rust-core-concepts.md index 4278fce48..c41276d10 100644 --- a/docs/plans/rust-core-concepts.md +++ b/docs/plans/rust-core-concepts.md @@ -265,8 +265,7 @@ Swift source of truth: `Sources/ProgramaSettingsFileStore.swift`, **`programa.json`** (`docs/programa-json.md`, `Sources/ProgramaConfig.swift`, `Sources/ProgramaConfigExecutor.swift`). Command-palette entries, walked up from the focused -workspace's cwd plus a global `~/.config/programa/programa.json` fallback (legacy names -`cmux.json`/`~/.config/cmux/cmux.json` still read). Two entry kinds: `commands` (either a +workspace's cwd plus a global `~/.config/programa/programa.json` fallback. Two entry kinds: `commands` (either a `workspace` command — opens/recreates a named workspace with a saved layout — or a `command` command — types+submits shell text) and `recipes` (fills a prompt template and types it into the focused terminal **without** auto-submitting, so a cloned repo can't fire attacker-chosen text diff --git a/docs/programa-json.md b/docs/programa-json.md index 1ee7eb8b5..fbd42163a 100644 --- a/docs/programa-json.md +++ b/docs/programa-json.md @@ -1,9 +1,8 @@ # `programa.json` Custom command-palette entries, shareable via git. Programa looks for `programa.json` -(falling back to the legacy `cmux.json`) by walking up from the focused workspace's current -directory, and always loads `~/.config/programa/programa.json` (or the legacy -`~/.config/cmux/cmux.json`) as a global fallback. Local entries take precedence over global ones +by walking up from the focused workspace's current +directory, and always loads `~/.config/programa/programa.json` as a global fallback. Local entries take precedence over global ones with the same `name`. The Swift structs in `Sources/ProgramaConfig.swift` (`ProgramaConfigFile`, diff --git a/docs/removed/README.md b/docs/removed/README.md index fe79042e1..1d169dbe1 100644 --- a/docs/removed/README.md +++ b/docs/removed/README.md @@ -16,5 +16,6 @@ Reductive pass of 2026-09-02, base commit 903027ccef. Core kept: the Ghostty ter | Inline VS Code (serve-web) | [inline-vscode.md](inline-vscode.md) | 610 | | Custom notification sound files | [custom-notification-sounds.md](custom-notification-sounds.md) | 400 | | tmux active-pane overlay target | [tmux-active-pane-overlay.md](tmux-active-pane-overlay.md) | 70 | +| cmux upgrade shims (old config paths, defaults migration, env and alias fallbacks) | [cmux-upgrade-shims.md](cmux-upgrade-shims.md) | 100 | Line counts are git-tracked lines at removal time and include tests and vendored code. diff --git a/docs/removed/cmux-upgrade-shims.md b/docs/removed/cmux-upgrade-shims.md new file mode 100644 index 000000000..d1c8fda69 --- /dev/null +++ b/docs/removed/cmux-upgrade-shims.md @@ -0,0 +1,29 @@ +# cmux upgrade shims + +Removed 2026-10-08. Last present at commit 6a64a9e0bd. Restore with `git checkout 6a64a9e0bd -- Sources/ProgramaApp.swift Sources/ProgramaConfig.swift Sources/ProgramaSettingsFileStore.swift Sources/ProgramaDirectoryTrust.swift Sources/GitWorktreeManager.swift Sources/SocketControlSettings.swift Sources/ContentView.swift CLI/CLI+AgentWrappers.swift CLI/SocketClient.swift CLI/programa.swift vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift Resources/shell-integration`. + +## What it did + +Programa was renamed from cmux. These fallbacks let someone upgrading from a cmux install keep working without touching their setup: + +- A one-time launch migration copied every `cmux`-prefixed UserDefaults key to the matching `programa` key. +- Config files were found at the old paths: `~/.config/cmux/settings.json`, `~/.config/cmux/cmux.json`, and a project-local `cmux.json` (also when migrating a version-2 directory trust store). +- `cmuxOnly` was accepted as an alias of the `programaOnly` socket mode (settings file, env var, JSON schema enum). +- `CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC`, `CMUX_DEBUG_LOG`, `CMUX_TAG` and `CMUX_SOCKET_PATH` were read as env fallbacks. +- The CLI resolved a scope from `cmux-` and `cmux-debug-` socket file names. +- The Claude wrapper detector also matched the `cmux claude wrapper` header. +- Command-palette usage history keys with a `cmux.config.` prefix were rewritten to `programa.config.`. +- Shell integration fell back to a `cmux` binary on `PATH` when `programa` was missing. +- The bonsplit debug log skipped the legacy `com.cmuxterm.app.debug` bundle id. + +## Files removed and files edited + +No files were deleted. The shims were edited out of the files in the restore command, plus the matching docs (`docs/settings-json.md`, `docs/environment-variables.md`, `docs/socket-api.md`, `docs/programa-json.md`, `docs/plans/rust-core-concepts.md`), `Resources/settings.schema.json`, `programaTests/GhosttyConfigTests.swift` and `vendor/bonsplit/Tests/BonsplitTests/DebugEventLogTests.swift`. + +## What we learned + +The UserDefaults migration was the only shim that ran unconditionally; the rest only matter when a cmux-era file or variable exists. The `cmuxPortBase` and `cmuxPortRange` defaults keys were kept: they are the live keys current users store their port settings under, so renaming them without a migration would reset those settings. Rename them together with a one-time copy if it is ever wanted. Also kept: the `"protocol": "cmux-socket"` value in the capabilities reply (other clients may read it) and the Sparkle signing keychain account name `cmux` in `scripts/sparkle_generate_keys.sh` (the existing key lives under it). + +## Why removed + +The cmux-to-Programa upgrade window has passed and the shims were code on hot startup paths that only helped people moving from cmux. diff --git a/docs/settings-json.md b/docs/settings-json.md index 399c51f46..ca174f428 100644 --- a/docs/settings-json.md +++ b/docs/settings-json.md @@ -68,7 +68,7 @@ Socket control and automation settings from Settings > Automation. | Key | Type | Default | What it does | |---|---|---|---| -| `socketControlMode` | string | `"programaOnly"` | Socket control mode. Legacy aliases are accepted and normalized. One of: `off`, `programaOnly`, `cmuxOnly` (legacy alias of `programaOnly`), `automation`, `password`, `allowAll`, `openAccess`, `fullOpenAccess`, `notifications`, `full`. | +| `socketControlMode` | string | `"programaOnly"` | Socket control mode. Legacy aliases are accepted and normalized. One of: `off`, `programaOnly`, `automation`, `password`, `allowAll`, `openAccess`, `fullOpenAccess`, `notifications`, `full`. | | `socketPassword` | object | | Password for password-mode socket access. Use null or an empty string to clear it. | | `claudeCodeIntegration` | boolean | `true` | Enable Programa integration hooks for Claude Code. | | `claudeBinaryPath` | string | | Custom path to the claude binary. | @@ -81,7 +81,7 @@ Custom command trust settings from Settings > Custom Commands. | Key | Type | Default | What it does | |---|---|---|---| -| `trustedDirectories` | array | `[]` | Directories whose programa.json (or legacy cmux.json) commands can run without confirmation. | +| `trustedDirectories` | array | `[]` | Directories whose programa.json commands can run without confirmation. | ## `browser` diff --git a/docs/socket-api.md b/docs/socket-api.md index 05872b4bc..4fcd3454a 100644 --- a/docs/socket-api.md +++ b/docs/socket-api.md @@ -57,7 +57,7 @@ Rules that follow from the diagram: ### Socket modes The mode is `automation.socketControlMode` in `settings.json` or **Settings > Automation**. -The default is `programaOnly`. The legacy name `cmuxOnly` is still accepted. +The default is `programaOnly`. | Mode | Shown in Settings as | Who can connect | |---|---|---| diff --git a/programaTests/GhosttyConfigTests.swift b/programaTests/GhosttyConfigTests.swift index 8935bef39..1eb702bec 100644 --- a/programaTests/GhosttyConfigTests.swift +++ b/programaTests/GhosttyConfigTests.swift @@ -1439,8 +1439,6 @@ final class SocketControlSettingsTests: XCTestCase { func testMigrateModeSupportsExpandedSocketModes() { XCTAssertEqual(SocketControlSettings.migrateMode("off"), .off) XCTAssertEqual(SocketControlSettings.migrateMode("programaOnly"), .programaOnly) - // Legacy cmux name, still read so existing socket mode settings keep working. - XCTAssertEqual(SocketControlSettings.migrateMode("cmuxOnly"), .programaOnly) XCTAssertEqual(SocketControlSettings.migrateMode("automation"), .automation) XCTAssertEqual(SocketControlSettings.migrateMode("password"), .password) XCTAssertEqual(SocketControlSettings.migrateMode("allow-all"), .allowAll) diff --git a/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift b/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift index feba42b26..ea35a8261 100644 --- a/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift +++ b/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift @@ -29,21 +29,11 @@ public final class DebugEventLog: @unchecked Sendable { !explicit.isEmpty { return explicit } - // Legacy cmux name, still read so existing shells keep working. - if let explicit = env["CMUX_DEBUG_LOG"]?.trimmingCharacters(in: .whitespacesAndNewlines), - !explicit.isEmpty { - return explicit - } if let tag = env["PROGRAMA_TAG"]?.trimmingCharacters(in: .whitespacesAndNewlines), !tag.isEmpty { return "/tmp/programa-debug-\(sanitizePathToken(tag)).log" } - // Legacy cmux name, still read so existing shells keep working. - if let tag = env["CMUX_TAG"]?.trimmingCharacters(in: .whitespacesAndNewlines), - !tag.isEmpty { - return "/tmp/cmux-debug-\(sanitizePathToken(tag)).log" - } if let socketPath = env["PROGRAMA_SOCKET_PATH"]?.trimmingCharacters(in: .whitespacesAndNewlines), !socketPath.isEmpty { @@ -52,17 +42,9 @@ public final class DebugEventLog: @unchecked Sendable { return "/tmp/\(socketBase).log" } } - // Legacy cmux name, still read so existing shells keep working. - if let socketPath = env["CMUX_SOCKET_PATH"]?.trimmingCharacters(in: .whitespacesAndNewlines), - !socketPath.isEmpty { - let socketBase = URL(fileURLWithPath: socketPath).deletingPathExtension().lastPathComponent - if socketBase.hasPrefix("cmux-debug-") { - return "/tmp/\(socketBase).log" - } - } if let bundleId = Bundle.main.bundleIdentifier, - bundleId != "com.cmuxterm.app.debug", /* legacy cmux bundle id */ bundleId != "com.darkroom.programa.debug" { + bundleId != "com.darkroom.programa.debug" { return "/tmp/programa-debug-\(sanitizePathToken(bundleId)).log" } return "/tmp/programa-debug.log" diff --git a/vendor/bonsplit/Tests/BonsplitTests/DebugEventLogTests.swift b/vendor/bonsplit/Tests/BonsplitTests/DebugEventLogTests.swift index 331fb7ff2..069d4e0dd 100644 --- a/vendor/bonsplit/Tests/BonsplitTests/DebugEventLogTests.swift +++ b/vendor/bonsplit/Tests/BonsplitTests/DebugEventLogTests.swift @@ -6,19 +6,9 @@ final class DebugEventLogTests: XCTestCase { func testProgramaDebugLogWinsOverEverything() { let env: [String: String] = [ "PROGRAMA_DEBUG_LOG": "/tmp/explicit-programa.log", - "CMUX_DEBUG_LOG": "/tmp/explicit-cmux.log", - "PROGRAMA_TAG": "my-tag", - "CMUX_TAG": "old-tag" - ] - XCTAssertEqual(DebugEventLog.resolveLogPath(env: env), "/tmp/explicit-programa.log") - } - - func testCmuxDebugLogWinsWhenNoProgramaDebugLog() { - let env: [String: String] = [ - "CMUX_DEBUG_LOG": "/tmp/explicit-cmux.log", "PROGRAMA_TAG": "my-tag" ] - XCTAssertEqual(DebugEventLog.resolveLogPath(env: env), "/tmp/explicit-cmux.log") + XCTAssertEqual(DebugEventLog.resolveLogPath(env: env), "/tmp/explicit-programa.log") } func testProgramaTagProducesProgramaDebugPath() {