Russian UI: settings, notch views, agent steps, errors; visible success after installing hooks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -329,7 +329,7 @@ final class HookServer: @unchecked Sendable {
|
||||
?? "unknown"
|
||||
let cwd = payload["cwd"] as? String ?? ""
|
||||
let rawName = URL(fileURLWithPath: cwd).lastPathComponent
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Session" : rawName)
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Сессия" : rawName)
|
||||
|
||||
// Determine which pill this event belongs to.
|
||||
// coucou_agent must be lowercase, digits and hyphens, ≤ 24 chars.
|
||||
@@ -387,9 +387,9 @@ final class HookServer: @unchecked Sendable {
|
||||
if let pending = state.pendingApproval, agentId == pending.pillId {
|
||||
let handledNote: String
|
||||
switch pending.pillId {
|
||||
case "agent_cursor": handledNote = "Handled in Cursor."
|
||||
case "agent_codex": handledNote = "Handled in Codex."
|
||||
default: handledNote = "Handled in VS Code."
|
||||
case "agent_cursor": handledNote = "Решено в Cursor."
|
||||
case "agent_codex": handledNote = "Решено в Codex."
|
||||
default: handledNote = "Решено в VS Code."
|
||||
}
|
||||
var resolved = false
|
||||
switch name {
|
||||
@@ -461,7 +461,7 @@ final class HookServer: @unchecked Sendable {
|
||||
|
||||
case "PostToolUseFailure":
|
||||
state.updateTask(id: agentId, state: .working)
|
||||
appendStep(id: agentId, step: "⚠ failed")
|
||||
appendStep(id: agentId, step: "⚠ ошибка")
|
||||
|
||||
case "Notification":
|
||||
let message = payload["message"] as? String ?? ""
|
||||
@@ -522,10 +522,10 @@ final class HookServer: @unchecked Sendable {
|
||||
state.removeTask(id: agentId)
|
||||
|
||||
case "SubagentStart":
|
||||
appendStep(id: agentId, step: "+ subagent")
|
||||
appendStep(id: agentId, step: "+ субагент")
|
||||
|
||||
case "SubagentStop":
|
||||
appendStep(id: agentId, step: "• subagent done")
|
||||
appendStep(id: agentId, step: "• субагент готов")
|
||||
|
||||
default:
|
||||
break
|
||||
@@ -613,7 +613,7 @@ final class HookServer: @unchecked Sendable {
|
||||
?? "unknown"
|
||||
let cwd = payload["cwd"] as? String ?? ""
|
||||
let rawName = URL(fileURLWithPath: cwd).lastPathComponent
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Session" : rawName)
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Сессия" : rawName)
|
||||
|
||||
let rawAgent = payload["coucou_agent"] as? String ?? ""
|
||||
let termProgram = payload["term_program"] as? String ?? ""
|
||||
@@ -710,9 +710,9 @@ final class HookServer: @unchecked Sendable {
|
||||
guard let self, self.pendingApprovalFD == fd else { return }
|
||||
let note: String
|
||||
switch capturedPillId {
|
||||
case "agent_cursor": note = "Handled in Cursor."
|
||||
case "agent_codex": note = "Handled in Codex."
|
||||
default: note = "Handled in VS Code."
|
||||
case "agent_cursor": note = "Решено в Cursor."
|
||||
case "agent_codex": note = "Решено в Codex."
|
||||
default: note = "Решено в VS Code."
|
||||
}
|
||||
self.dismissApprovalCard(note: note)
|
||||
}
|
||||
@@ -727,9 +727,9 @@ final class HookServer: @unchecked Sendable {
|
||||
guard let self, self.pendingApprovalFD == captured else { return }
|
||||
let note: String
|
||||
switch capturedPillId {
|
||||
case "agent_cursor": note = "Still waiting in Cursor."
|
||||
case "agent_codex": note = "Still waiting in Codex."
|
||||
default: note = "Still waiting in VS Code."
|
||||
case "agent_cursor": note = "Всё ещё ждёт в Cursor."
|
||||
case "agent_codex": note = "Всё ещё ждёт в Codex."
|
||||
default: note = "Всё ещё ждёт в VS Code."
|
||||
}
|
||||
self.dismissApprovalCard(note: note)
|
||||
}
|
||||
@@ -789,7 +789,7 @@ final class HookServer: @unchecked Sendable {
|
||||
?? "unknown"
|
||||
let cwd = payload["cwd"] as? String ?? ""
|
||||
let rawName = URL(fileURLWithPath: cwd).lastPathComponent
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Session" : rawName)
|
||||
let projectName = aliasProjectName(rawName.isEmpty ? "Сессия" : rawName)
|
||||
|
||||
let rawAgent = payload["coucou_agent"] as? String ?? ""
|
||||
let termProgram = payload["term_program"] as? String ?? ""
|
||||
@@ -930,27 +930,27 @@ final class HookServer: @unchecked Sendable {
|
||||
return aliases[name.lowercased()] ?? name
|
||||
}
|
||||
|
||||
// MARK: - French step labels
|
||||
// MARK: - Step labels (Russian)
|
||||
|
||||
private func frenchStep(tool: String, input: [String: Any]) -> String {
|
||||
let labels: [String: String] = [
|
||||
"Bash": "Exécute",
|
||||
"Read": "Lit",
|
||||
"Write": "Écrit",
|
||||
"Edit": "Modifie",
|
||||
"Glob": "Cherche",
|
||||
"Grep": "Recherche",
|
||||
"WebSearch": "Recherche web",
|
||||
"WebFetch": "Récupère",
|
||||
"TodoWrite": "Tâches",
|
||||
"Task": "Agent",
|
||||
"LS": "Liste",
|
||||
"MultiEdit": "Modifie",
|
||||
"NotebookEdit": "Notebook",
|
||||
"Bash": "Выполняет",
|
||||
"Read": "Читает",
|
||||
"Write": "Пишет",
|
||||
"Edit": "Правит",
|
||||
"Glob": "Ищет",
|
||||
"Grep": "Ищет",
|
||||
"WebSearch": "Веб-поиск",
|
||||
"WebFetch": "Загружает",
|
||||
"TodoWrite": "Задачи",
|
||||
"Task": "Агент",
|
||||
"LS": "Список",
|
||||
"MultiEdit": "Правит",
|
||||
"NotebookEdit": "Блокнот",
|
||||
// Codex tools
|
||||
"apply_patch": "Modifie",
|
||||
"update_plan": "Tâches",
|
||||
"spawn_agent": "Agent",
|
||||
"apply_patch": "Правит",
|
||||
"update_plan": "Задачи",
|
||||
"spawn_agent": "Агент",
|
||||
]
|
||||
var label = labels[tool] ?? tool
|
||||
|
||||
@@ -991,19 +991,19 @@ final class HookServer: @unchecked Sendable {
|
||||
return label
|
||||
}
|
||||
|
||||
/// Infers a French verb from a shell command's first word.
|
||||
/// Infers a Russian verb from a shell command's first word.
|
||||
private func bashVerb(_ command: String) -> String {
|
||||
let first = command.split(whereSeparator: { $0.isWhitespace }).first.map(String.init) ?? ""
|
||||
switch first {
|
||||
case "cat", "bat", "head", "tail", "less", "more", "nl": return "Lit"
|
||||
case "rg", "grep", "find", "fd", "ls", "tree", "wc": return "Cherche"
|
||||
case "cat", "bat", "head", "tail", "less", "more", "nl": return "Читает"
|
||||
case "rg", "grep", "find", "fd", "ls", "tree", "wc": return "Ищет"
|
||||
default: break
|
||||
}
|
||||
let testRunners = ["pytest", "vitest", "jest", "npm test", "npm run test",
|
||||
"cargo test", "go test", "swift test", "make test",
|
||||
"xcodebuild test", "unittest"]
|
||||
if testRunners.contains(where: { command.contains($0) }) { return "Teste" }
|
||||
return "Exécute"
|
||||
if testRunners.contains(where: { command.contains($0) }) { return "Тестирует" }
|
||||
return "Выполняет"
|
||||
}
|
||||
|
||||
// MARK: - Live diff helpers
|
||||
@@ -1284,7 +1284,7 @@ final class HookServer: @unchecked Sendable {
|
||||
if clCheck.terminationStatus != 0 {
|
||||
throw NSError(domain: "Coucou", code: 1,
|
||||
userInfo: [NSLocalizedDescriptionKey:
|
||||
"Command Line Tools are required but not installed. Run: xcode-select --install"])
|
||||
"Нужны Command Line Tools, но они не установлены. Выполните: xcode-select --install"])
|
||||
}
|
||||
|
||||
if let existing = oldSL,
|
||||
@@ -1332,13 +1332,13 @@ final class HookServer: @unchecked Sendable {
|
||||
|
||||
// Build a compact diff: show only the statusLine key before → after
|
||||
func slJSON(_ val: [String: Any]?) throws -> String {
|
||||
guard let v = val else { return "(none)" }
|
||||
guard let v = val else { return "(нет)" }
|
||||
let d = try JSONSerialization.data(withJSONObject: v, options: [.prettyPrinted, .sortedKeys])
|
||||
return String(data: d, encoding: .utf8) ?? "(none)"
|
||||
return String(data: d, encoding: .utf8) ?? "(нет)"
|
||||
}
|
||||
let before = try slJSON(oldSL)
|
||||
let after = try slJSON(newSL)
|
||||
return "statusLine\nBefore:\n\(before)\n\nAfter:\n\(after)"
|
||||
return "statusLine\nБыло:\n\(before)\n\nСтало:\n\(after)"
|
||||
}
|
||||
|
||||
/// Writes settings.json and commits side effects (call after user confirms).
|
||||
@@ -1511,7 +1511,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let exists = FileManager.default.fileExists(atPath: url.path)
|
||||
if !install && !exists {
|
||||
throw NSError(domain: "CoucouNoop", code: 0, userInfo: [
|
||||
NSLocalizedDescriptionKey: "No Gemini CLI hooks to remove."
|
||||
NSLocalizedDescriptionKey: "Нет хуков Gemini CLI для удаления."
|
||||
])
|
||||
}
|
||||
let current = exists ? try Data(contentsOf: url) : Data()
|
||||
@@ -1527,7 +1527,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let current = (try? Data(contentsOf: url)) ?? Data()
|
||||
guard sha256Hex(current) == fp else {
|
||||
throw NSError(domain: "Coucou", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json changed since preview. Refresh and try again."
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json изменился после предпросмотра. Обновите и попробуйте снова."
|
||||
])
|
||||
}
|
||||
try writeJSONFile(data, to: url, suffix: "settings.json")
|
||||
@@ -1540,7 +1540,7 @@ final class HookServer: @unchecked Sendable {
|
||||
label: "~/.gemini/settings.json")
|
||||
if let raw = settings["hooks"], !(raw is [String: Any]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\" has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\" имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
let base = hookBase()
|
||||
@@ -1557,7 +1557,7 @@ final class HookServer: @unchecked Sendable {
|
||||
for (geminiEvent, normalizedEvent, timeout) in events {
|
||||
if let raw = hooks[geminiEvent], !(raw is [[String: Any]]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\"[\"\(geminiEvent)\"] has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\"[\"\(geminiEvent)\"] имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
var groups = hooks[geminiEvent] as? [[String: Any]] ?? []
|
||||
@@ -1581,7 +1581,7 @@ final class HookServer: @unchecked Sendable {
|
||||
label: "~/.gemini/settings.json")
|
||||
if let raw = settings["hooks"], !(raw is [String: Any]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\" has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.gemini/settings.json: \"hooks\" имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
if var hooks = settings["hooks"] as? [String: Any] {
|
||||
@@ -1607,7 +1607,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let exists = FileManager.default.fileExists(atPath: url.path)
|
||||
if !install && !exists {
|
||||
throw NSError(domain: "CoucouNoop", code: 0, userInfo: [
|
||||
NSLocalizedDescriptionKey: "No Antigravity hooks to remove."
|
||||
NSLocalizedDescriptionKey: "Нет хуков Antigravity для удаления."
|
||||
])
|
||||
}
|
||||
let current = exists ? try Data(contentsOf: url) : Data()
|
||||
@@ -1623,7 +1623,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let current = (try? Data(contentsOf: url)) ?? Data()
|
||||
guard sha256Hex(current) == fp else {
|
||||
throw NSError(domain: "Coucou", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.gemini/config/hooks.json changed since preview. Refresh and try again."
|
||||
NSLocalizedDescriptionKey: "~/.gemini/config/hooks.json изменился после предпросмотра. Обновите и попробуйте снова."
|
||||
])
|
||||
}
|
||||
try writeJSONFile(data, to: url, suffix: "hooks.json")
|
||||
@@ -1679,12 +1679,12 @@ final class HookServer: @unchecked Sendable {
|
||||
do { data = try Data(contentsOf: url) }
|
||||
catch {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "\(label) cannot be read — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "Не удалось прочитать \(label) — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
guard let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "\(label) is not valid JSON — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "\(label) — некорректный JSON, BroV его не трогал."
|
||||
])
|
||||
}
|
||||
return obj
|
||||
@@ -1702,7 +1702,7 @@ final class HookServer: @unchecked Sendable {
|
||||
do { try fm.copyItem(at: url, to: backupURL) }
|
||||
catch {
|
||||
throw NSError(domain: "Coucou", code: 3, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Could not back up \(url.lastPathComponent): \(error.localizedDescription)"
|
||||
NSLocalizedDescriptionKey: "Не удалось сделать резервную копию \(url.lastPathComponent): \(error.localizedDescription)"
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -1762,7 +1762,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let exists = FileManager.default.fileExists(atPath: url.path)
|
||||
if !install && !exists {
|
||||
throw NSError(domain: "CoucouNoop", code: 0, userInfo: [
|
||||
NSLocalizedDescriptionKey: "No Codex hooks to remove."
|
||||
NSLocalizedDescriptionKey: "Нет хуков Codex для удаления."
|
||||
])
|
||||
}
|
||||
let current = exists ? try Data(contentsOf: url) : Data()
|
||||
@@ -1778,7 +1778,7 @@ final class HookServer: @unchecked Sendable {
|
||||
let current = (try? Data(contentsOf: url)) ?? Data()
|
||||
guard sha256Hex(current) == fp else {
|
||||
throw NSError(domain: "Coucou", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json changed since preview. Refresh and try again."
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json изменился после предпросмотра. Обновите и попробуйте снова."
|
||||
])
|
||||
}
|
||||
try writeJSONFile(data, to: url, suffix: "hooks.json")
|
||||
@@ -1790,7 +1790,7 @@ final class HookServer: @unchecked Sendable {
|
||||
var root = try Self.strictReadJSONObject(at: Self.codexHooksURL, label: "~/.codex/hooks.json")
|
||||
if let raw = root["hooks"], !(raw is [String: Any]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\" has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\" имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
let base = hookBase()
|
||||
@@ -1812,7 +1812,7 @@ final class HookServer: @unchecked Sendable {
|
||||
for (event, timeout, statusMsg) in events {
|
||||
if let raw = hooks[event], !(raw is [[String: Any]]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\"[\"\(event)\"] has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\"[\"\(event)\"] имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
var groups = hooks[event] as? [[String: Any]] ?? []
|
||||
@@ -1836,7 +1836,7 @@ final class HookServer: @unchecked Sendable {
|
||||
var root = try Self.strictReadJSONObject(at: Self.codexHooksURL, label: "~/.codex/hooks.json")
|
||||
if let raw = root["hooks"], !(raw is [String: Any]) {
|
||||
throw NSError(domain: "Coucou", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\" has an unexpected type — BroV has not touched it."
|
||||
NSLocalizedDescriptionKey: "~/.codex/hooks.json: \"hooks\" имеет неожиданный тип — BroV его не трогал."
|
||||
])
|
||||
}
|
||||
if var hooks = root["hooks"] as? [String: Any] {
|
||||
|
||||
Reference in New Issue
Block a user