mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-18 05:24:36 +00:00
Adds a "Config Directories" section under the Claude settings tab so users can aggregate usage across multiple Claude config directories (work / personal accounts) from the GUI. The menubar is an accessory app that doesn't inherit the user's shell environment, so CLAUDE_CONFIG_DIRS was previously unreachable from the app. Rather than injecting the value as an env var into every spawned subprocess (which would force arbitrary user paths through the shell/AppleScript allowlist that deliberately excludes shell metacharacters), the list is persisted to the shared ~/.config/codeburn/config.json. The CLI reads it regardless of how it's launched, so both the menubar's data refresh and terminal-launched `report`/`optimize` honor it consistently. CLI: getClaudeConfigDirs() now reads a claudeConfigDirs array from config as a fallback below the CLAUDE_CONFIG_DIRS / CLAUDE_CONFIG_DIR env vars (env still wins for per-shell overrides), above the ~/.claude default. Menubar: CLIClaudeConfig mirrors CLICurrencyConfig's flock-guarded write; the Settings UI offers an add/remove list with a folder picker and forces a refresh on edit.
74 lines
3.2 KiB
Swift
74 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
/// Reads and writes the CLI's `claudeConfigDirs` list in
|
|
/// `~/.config/codeburn/config.json`. The menubar is a GUI app that doesn't
|
|
/// inherit the user's shell environment, so it can't rely on `CLAUDE_CONFIG_DIRS`
|
|
/// to aggregate usage across multiple Claude config directories (work / personal
|
|
/// accounts). Persisting to the shared config file instead means every `codeburn`
|
|
/// invocation honors the list — whether the menubar spawns it directly or the
|
|
/// user launches `codeburn report` in a terminal — without injecting environment
|
|
/// variables through the shell/AppleScript paths (which only accept a strict
|
|
/// metacharacter-free allowlist that arbitrary directory paths can't satisfy).
|
|
///
|
|
/// Shares the same on-disk flock as `CLICurrencyConfig` so a concurrent
|
|
/// `codeburn` write from a terminal can't race the menubar and drop the other's
|
|
/// changes (TOCTOU on config.json).
|
|
enum CLIClaudeConfig {
|
|
private static var configDir: String {
|
|
(NSHomeDirectory() as NSString).appendingPathComponent(".config/codeburn")
|
|
}
|
|
private static var configPath: String {
|
|
(configDir as NSString).appendingPathComponent("config.json")
|
|
}
|
|
private static var lockPath: String {
|
|
(configDir as NSString).appendingPathComponent(".config.lock")
|
|
}
|
|
|
|
/// Returns the persisted config directories, or an empty array when none are
|
|
/// set (the CLI then falls back to `~/.claude`).
|
|
static func load() -> [String] {
|
|
guard
|
|
let data = try? SafeFile.read(from: configPath),
|
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
let dirs = json["claudeConfigDirs"] as? [Any]
|
|
else {
|
|
return []
|
|
}
|
|
return dirs.compactMap { $0 as? String }.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
|
}
|
|
|
|
/// Persists the given directories. An empty list removes the key entirely so
|
|
/// the CLI reverts to its default `~/.claude` behavior. Entries are trimmed
|
|
/// and blanks dropped; order is preserved.
|
|
static func persist(dirs: [String]) {
|
|
let cleaned = dirs
|
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
|
.filter { !$0.isEmpty }
|
|
|
|
do {
|
|
try SafeFile.withExclusiveLock(at: lockPath) {
|
|
var existing: [String: Any] = [:]
|
|
if let data = try? SafeFile.read(from: configPath),
|
|
let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
existing = parsed
|
|
}
|
|
|
|
if cleaned.isEmpty {
|
|
existing.removeValue(forKey: "claudeConfigDirs")
|
|
} else {
|
|
existing["claudeConfigDirs"] = cleaned
|
|
}
|
|
|
|
guard let data = try? JSONSerialization.data(
|
|
withJSONObject: existing,
|
|
options: [.prettyPrinted, .sortedKeys]
|
|
) else {
|
|
return
|
|
}
|
|
try SafeFile.write(data, to: configPath, mode: 0o600)
|
|
}
|
|
} catch {
|
|
NSLog("CodeBurn: failed to persist claudeConfigDirs config: \(error)")
|
|
}
|
|
}
|
|
}
|