Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
3.1 MiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "atomgit-atomcode/atomcode", "files": {"crates/atomcode-config/src/i18n/en.rs": {"858ab6db7d1354e6e0e33168c8aacf28c313d913": {"t": 1785746256.9234304, "n": 84124, "h": "af478de61c0619e3", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n Msg::VisionPreprocessorUnresolvable { model, provider } => format!(\n \"Current model \\\"{}\\\" does not support image input; the configured \\\n vision_preprocessor_provider \\\"{}\\\" does not resolve (check the name \\\n matches a provider/model in your config). Fix the name, or use \\\n /model to switch to a vision-capable model.\",\n model, provider\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "271ca9b321176e1f46008ed79d34f08c515e69af": {"t": 1785761152.930359, "n": 84124, "h": "af478de61c0619e3", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n Msg::VisionPreprocessorUnresolvable { model, provider } => format!(\n \"Current model \\\"{}\\\" does not support image input; the configured \\\n vision_preprocessor_provider \\\"{}\\\" does not resolve (check the name \\\n matches a provider/model in your config). Fix the name, or use \\\n /model to switch to a vision-capable model.\",\n model, provider\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a9f457f67562a45b063bb12f6c1192ddc0b7eaf1": {"t": 1785761160.4805698, "n": 83691, "h": "755084c80aba8293", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "05143e0ccd2bd3de5715c06bb0c4dc53ad69a197": {"t": 1785761162.0418375, "n": 83684, "h": "17685c3be45375f8", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "25a89874e928dd8bfe6f1c92f67574cacfa85a2d": {"t": 1785761165.5384965, "n": 83687, "h": "8eada3fe1712f43e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "cf56254e8d343aa07efae2f903eab81180c489fa": {"t": 1785761168.5642412, "n": 83668, "h": "d1cd9670a32cb7fd", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "3c4f5d4d6d446c3643f930d7858c310ac3871f27": {"t": 1785761170.2132463, "n": 83664, "h": "f24a590e08f1fd0b", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "8e5f52b8509892e6dc911ade11bbe22c10bd2e3c": {"t": 1785761172.0840542, "n": 80903, "h": "90310fc2926bc9a2", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "2f77d05520f187fbb7626e81f8c6c462b5b926fe": {"t": 1785761174.2203047, "n": 80708, "h": "aed7b6309cbf238e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "cc80249971b5a5c03a51c50e6333eb4bfff65a70": {"t": 1785761177.0305781, "n": 80649, "h": "7225afd51fb3d3a9", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "4c585071b3b0c4692897c38c7fbaf896f207d717": {"t": 1785761178.7797902, "n": 80473, "h": "2626a294e5bfcd79", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "d8d4d7b4cdd4b5edff232d66cfa025228d0a75ce": {"t": 1785761180.3725264, "n": 80227, "h": "703e4af77792a78a", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnknown => \"unknown\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\\n Estimated cost: unknown\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "87c732c36ec4113e8d3c34684fb88ecfe147d8cd": {"t": 1785761181.961528, "n": 79496, "h": "88ab4f8925218d94", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "2ac9327aff2caaa490276809fc4cccba69c3e249": {"t": 1785761183.477338, "n": 79266, "h": "47ea8939c1fcf05d", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "1d1eb66cabac724e40b5977a733a3ea8a6af77d9": {"t": 1785761184.9628448, "n": 78590, "h": "0a11729e301230e0", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "52a6a106b50728d704ebc53efa438f8e0b32dc1a": {"t": 1785761186.546126, "n": 78455, "h": "3eafc04b82ddfaaf", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "6e7c48585479799d52cccb4d82c9258b47a6bae3": {"t": 1785761191.0551636, "n": 78491, "h": "dce355dd87e52d08", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "d571afd422d9d4b9a535c17ccf302ad676c1588f": {"t": 1785761192.7458239, "n": 78356, "h": "331911bbd6200e73", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a202067522c7e493d27640760dc2371f5d3ae6c6": {"t": 1785761194.6161773, "n": 78086, "h": "70c61de6e84c55b0", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "bc3a5f9f2367bcbf9e09b6c09c554a5380bc28bf": {"t": 1785761196.464072, "n": 78561, "h": "97bb5c2a99a89cf8", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a28c215857b0e87dba221b4ebebe1d16d00b1fbe": {"t": 1785761198.1006916, "n": 78621, "h": "8d274ec64c744e7e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "036da2caca5bcb22601ca9efc7f0e69f554e4e06": {"t": 1785761199.5834646, "n": 79043, "h": "ac607bb8d015f3e1", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "90a92733211fea64fece290dd4e8a9a4f1e57bbb": {"t": 1785761201.1893806, "n": 79041, "h": "d190269de20e0f8c", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "be7d1085abf676327b821392d02767c4199192fd": {"t": 1785761202.923231, "n": 78959, "h": "65c7f5e358c44bae", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "0b305583930f5d8af7bc87547a44aab9cb23c1c3": {"t": 1785761204.41125, "n": 78935, "h": "017eb6bfcd4a889b", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "fb430c1aeabae6bddca8f0cb92c93b0984ef74f3": {"t": 1785761206.4992933, "n": 78781, "h": "46bfb17f390392b9", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "1681d040ec14fd8bff9a71faba85b33fbdf82cc0": {"t": 1785761208.0822484, "n": 78698, "h": "0954f3e0a8d7727e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "859c41e0fdda32e26ea17efb8b732897028244cc": {"t": 1785761209.6694033, "n": 78301, "h": "b1f08701f2916e6c", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "53d1791863008c1d4126dcf4f8f9a6d028b450a3": {"t": 1785761211.3544762, "n": 78197, "h": "6325fc113bd17380", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "46b1a87c3120df050c9ae641ab064663c8a6bb3c": {"t": 1785761213.0000718, "n": 78040, "h": "f36f8d17edfbcf77", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"Enter open · F2 rename · Ctrl+D delete\".into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "0a4a5bc434205add79f1d387be9dc255974f719e": {"t": 1785761214.60922, "n": 77585, "h": "93d14d39645e766d", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"Enter open · F2 rename · Ctrl+D delete\".into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}}, "site/docs/zh/configuration.html": {"858ab6db7d1354e6e0e33168c8aacf28c313d913": {"t": 1785746429.8179703, "n": 16081, "h": "31bf04ba5f815987", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n <div class=\"callout callout-info\">\n <strong>v5.0.3 的 Provider 管理</strong>\n <p>推荐直接运行 <code>/provider</code>:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 <code>[providers.*]</code> 配置仍可继续使用,AtomCode 会自动兼容。</p>\n </div>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 <code>/model</code> 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2>回合轮数</h2>\n <pre><code>[coding]\nmax_rounds = 200</code></pre>\n <p>达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 <code>0</code> 表示不限制。</p>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式管理供应商账号及其模型,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "bacf7b9d53f04fdb9ead8a7c8d8fdf6b1aa0baa5": {"t": 1785761159.8064015, "n": 16081, "h": "31bf04ba5f815987", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n <div class=\"callout callout-info\">\n <strong>v5.0.3 的 Provider 管理</strong>\n <p>推荐直接运行 <code>/provider</code>:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 <code>[providers.*]</code> 配置仍可继续使用,AtomCode 会自动兼容。</p>\n </div>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 <code>/model</code> 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2>回合轮数</h2>\n <pre><code>[coding]\nmax_rounds = 200</code></pre>\n <p>达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 <code>0</code> 表示不限制。</p>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式管理供应商账号及其模型,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "2a63b30571e1d2fe5ed3714f454a32b341572bc0": {"t": 1785761161.5752933, "n": 15616, "h": "69ea5ced1d1e5603", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "1737a748cfc706db9548e0859a43476728100d2e": {"t": 1785761163.6157043, "n": 15616, "h": "1abf5245bfb931d9", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "16c021d52b3be0b44bc9b49b6d647e41dc2c3f2a": {"t": 1785761165.4364398, "n": 15616, "h": "d880097116d3fdc7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "de1cd59a8d61743707e4904a5375f4413543417c": {"t": 1785761166.6353228, "n": 14210, "h": "93bd270e3417b2a7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "a28c215857b0e87dba221b4ebebe1d16d00b1fbe": {"t": 1785761168.838292, "n": 14210, "h": "29e29f01c26b3385", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "ad90366cba07ceab3cfc652f96bdd323e6c0dabc": {"t": 1785761170.2811244, "n": 14189, "h": "5c96f5ae1de67fd0", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "6a3a1f6933836f94dd7c26ea75c26645ee0d2489": {"t": 1785761171.5069304, "n": 12713, "h": "640e31487900d887", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "86bbaacbc30b3b5fa3105518b4c4052ffddb71df": {"t": 1785761173.1574996, "n": 12714, "h": "7098a7c445cbe180", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.26.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "17ebf59dcaad723bb94ed01721837618bd2da192": {"t": 1785761175.293464, "n": 12714, "h": "c5c1c3c639db3a40", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.9</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "0fd8208a0fe5d4aa56ed8bb12fe033682ba44847": {"t": 1785761176.662956, "n": 12714, "h": "63a350e67cb1c132", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.8</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "3269260453c27bd434d9d8f4770971f9eb339e5a": {"t": 1785761177.826284, "n": 12714, "h": "78dde1cdccfe41a9", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.7</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "431d0485045fe2e51660484e074ef0f42d9f2f39": {"t": 1785761179.2059317, "n": 12714, "h": "d53b7d9da964f695", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.6</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "6264e177874c3472ece603abac5df936bcccd7d8": {"t": 1785761180.721333, "n": 12714, "h": "d8128617f9f85a40", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.5</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "9bd888fc1ae56f7ebb84e3de26a5e96d3a8ba61e": {"t": 1785761182.2480638, "n": 12714, "h": "6d316d2e3471cd98", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.4</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "578ac062a237ed41e871d3ddfcfa18d3906e6790": {"t": 1785761183.5848343, "n": 12714, "h": "8ceeee4f9e424c27", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "a3ee1baf64fb11b73a84be262119f7090e25dbda": {"t": 1785761184.9288945, "n": 12714, "h": "1c9d27214b5e5d6f", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "fcb5525d2ca5b550483a3f35a3a4f8997a4a120d": {"t": 1785761186.1960986, "n": 12714, "h": "b023fe2802b5f882", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "de732c5f4d44b7bde84879f38d7cfefdfbae1a2e": {"t": 1785761187.5358357, "n": 12714, "h": "e67ee203e70d0d04", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "e987bb439507ad0913f8bee478321b140274b839": {"t": 1785761188.6966908, "n": 12714, "h": "a7d7597405541049", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "9eccc8f68beda069fa2526fc8d695deac2547f46": {"t": 1785761190.1039116, "n": 12606, "h": "e1be6b066e9a41fa", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "241659424f17ba76815f4e987533ab3154458ee2": {"t": 1785761191.3511972, "n": 12606, "h": "8b5f102a1033577e", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "46232b8107bda45bc1f106ccc5f57af73ad20c72": {"t": 1785761192.5472076, "n": 12606, "h": "85e16b411e5ee9d7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "bfd45aac4384f4df06f25e2bcfe783cb61ca4bb6": {"t": 1785761193.7686107, "n": 12506, "h": "e1435dce7f7cf72f", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "26aa47211d50a2498cf54fa380edd0fce5486bab": {"t": 1785761195.1286917, "n": 12366, "h": "8caef59791dd5e22", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "13c051411f4bfad7d04897f6c150beee2d5e5592": {"t": 1785761196.4307868, "n": 12366, "h": "e9943643bfc5a330", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "27ee84b6604aad3ac1924f3f7326a95314b3032f": {"t": 1785761197.9718714, "n": 12371, "h": "3012d9a470236695", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "350b15d383e1d610825e7bd445a6a162ff8f63f9": {"t": 1785761199.1567595, "n": 12371, "h": "12605d7c79f48806", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "91dc4e2e916c55e18264a4e1998e78618e16a026": {"t": 1785761200.3606267, "n": 12721, "h": "9c4fb0037b7c8c0e", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>UI 偏好</h2>\n <p>TUI 显示偏好(滚动条开关等)单独存在一个文件,不和 <code>config.toml</code> 混在一起:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/ui-state.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\ui-state.toml</code></li>\n </ul>\n <p>由 <code>/scrollbar</code> 等命令自动维护(原子写,崩溃也不会破文件),通常不需要手动编辑。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "3e115c22a6752ac1146d024739f6ef6b2bb06b3a": {"t": 1785761201.789139, "n": 12371, "h": "12605d7c79f48806", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}}, "crates/atomcode-config/src/i18n/zh_cn.rs": {"0e5432e5a1a994f525965e140271d1328f567c47": {"t": 1786068397.4912536, "n": 69537, "h": "f153ea23b944979f", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn zh_cn(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"欢迎使用 AtomCode,请选择一项开始:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ 切换,Enter 确认,Esc 跳过)\".into(),\n Msg::WelcomeOptionCodingPlan => \"配置 CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"免费额度 · 推荐\".into(),\n Msg::WelcomeOptionConfigureManually => \"手动配置\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"使用 API key\".into(),\n Msg::WelcomeOptionSkip => \"暂时跳过\".into(),\n Msg::WelcomeOptionSkipHint => \"稍后再说\".into(),\n\n // ── /login(完整配置流程) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"CodingPlan 设置失败:{error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ 登录凭证已失效 — 正在重新登录...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"认证已过期,请执行 /login 重新登录\".into(),\n Msg::NetworkConnectHint =>\n \"网络连接失败。若浏览器能打开,可能是代理/防火墙差异:用 /proxy 配置代理或设置 HTTPS_PROXY,或在浏览器打开上面的登录链接完成扫码。可按 Esc 跳过,稍后 /login 重试。\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan 配置:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ 已登录:{} ({},{})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × 登录失败 — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan 已领取 — {}(CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"成功\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan 已领取 — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan 套餐配置失败 — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan 套餐配置失败\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} 生效\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} 生效\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} 套餐配置失败 — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\" ✓ 已添加 {} 个账号 · {} 个模型:\\n\", accounts, models).into(),\n Msg::CpLocked { name } =>\n // SGR 31 / 39 = 标准红前景 + 默认色重置。用标准色(不\n // 是亮色)让终端按当前主题映射 —— Solarized / Dracula /\n // 浅色模式都会落到各自的「红」上,不会被一个写死的 RGB\n // 锁住。retained 渲染器走严格 sanitizer 会把 SGR 剥光,\n // 但 `× … (需要升级成 Pro 以上套餐)` 文本本身仍能传达含义。\n format!(\" \\x1b[31m× {} (需要升级成 Pro 以上套餐)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (默认)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ 视觉预处理器 → {} (自动检测)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ 视觉预处理器 → {} (保留用户设置)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ 视觉预处理器已清除 — 当前模型列表中没有可用的 VL/OCR 模型\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ 模型步骤已跳过 — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × 模型步骤失败 — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan 状态:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" 套餐:{} · 正在激活\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" 套餐:{} · 到期时间 {}(剩余 {}d / 共 {}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" 用量:{} · 重置于 {}({} 后)\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ 当前窗口配额已耗尽\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ 状态获取已跳过 — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ 状态获取失败(非致命) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"此功能需要官方 AtomCode 构建,请前往 \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases 下载安装。\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"未登录 AtomCode CodingPlan。请运行 /login 完成登录后再发送请求。\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"请求被服务端拒绝:签名时间戳已过期。请校准本地系统时间(NTP 同步)后重试。\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"请求多次被识别为重放,请重新运行命令。\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"当前 AtomCode 版本过旧,已不兼容 CodingPlan。请升级 AtomCode 后继续使用。\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"需要升级才能继续使用 CodingPlan,请前往官方发布页安装最新版 AtomCode。\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"不支持的语言:{input}\").into(),\n\n // ── 状态栏 ──\n Msg::StatusNoProvider =>\n \"未配置 Provider · 使用 /provider 配置\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"Runtime 不可用 · 请重启或查看上方错误\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan 需要官方构建\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} 可用 · 使用 /upgrade 升级\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} 可用 · 运行 brew upgrade atomcode 升级\").into(),\n Msg::StatusModelNotConfigured =>\n \"(未配置)\".into(),\n Msg::StatusClipboardImageHint =>\n \"剪贴板有图片 · ctrl+v / ctrl+alt+v 粘贴\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"剪贴板有图片 · /paste 粘贴\".into(),\n Msg::StatusWebuiHint =>\n \"提示:使用 /webui 在浏览器中打开 AtomCode\".into(),\n\n // ── /status 命令主体 ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" 模型: {}\\n 目录: {}\\n 配置文件:{}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" 登录: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" 登录: 未登录(运行 /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan:(未登录 — 运行 /login 进行配置)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan:(状态获取失败 — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan:(登录已过期 — 运行 /login 重新登录)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan:(无激活套餐 — 运行 /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan:{} · 到期 {}({}d / 共 {}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" 用量:{} · 重置于 {}({} 后)\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ 当前窗口配额已耗尽\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" 指令文件:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"用户全局\".into(),\n Msg::StatusInstructionScopeProject => \"项目共享\".into(),\n Msg::StatusInstructionScopeUser => \"用户项目覆盖\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope}({label}):{path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope}({label}):{path} — 未找到\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" 记忆文件:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"用户全局\".into(),\n Msg::StatusMemoryScopeProject => \"项目记忆\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}:{path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}:{path} — 未找到\\n\").into(),\n\n // ── 帮助 ──\n Msg::HelpAvailableCommands =>\n \" 可用命令:\\n\".into(),\n Msg::KeybindingsHelp => r#\" 键盘快捷键\n\n ── 输入 ──\n Enter 发送消息\n \\ 后接 Enter 插入换行(所有终端通用)\n Shift / Alt / Ctrl+Enter 插入换行 *\n Ctrl+J 插入换行 *\n / 打开斜杠命令菜单\n Tab 接受斜杠菜单、文件路径等补全\n Backspace / Ctrl+H 删除上一个字符\n Delete / Ctrl+? 删除下一个字符\n Ctrl+W 删除前一个单词\n Ctrl+U 清空当前行\n Ctrl+K 删除到行尾\n Ctrl+A / Home 跳到行首\n Ctrl+E / End 跳到行尾\n Left / Right 光标左右移动\n\n ── 历史 ──\n Up / Down 上一条 / 下一条输入\n Ctrl+R 反向搜索;再次按下继续向前查找\n Right 接受下一步建议(不会自动发送)\n\n ── 模式与模型 ──\n Tab / Shift+Tab 无补全菜单时切换下一个 / 上一个执行模式\n F2 / Shift+F2 下一个 / 上一个模型(Mac:Fn+F2 / Fn+Shift+F2)\n Ctrl+T 切换 reasoning_effort\n\n ── 翻看输出 ──\n Shift+Up / Shift+Down 向上 / 向下滚动一行\n PageUp / PageDown 向上 / 向下滚动 10 行\n Alt+Up / Alt+Down 跳到上一条 / 下一条消息\n Ctrl+Up / Ctrl+Down 跳到上一条 / 下一条用户消息\n Home / End 空输入时跳到对话顶部 / 底部\n 鼠标滚轮 滚动聊天区\n 鼠标拖选 选择文本\n Shift+鼠标拖选 使用终端原生文本选择\n Ctrl+Shift+C 复制 AtomCode 选区\n\n ── 流程控制 ──\n Esc 清空输入 / 关闭弹层 / 取消当前操作\n Esc Esc 空闲且输入为空时撤销上一轮\n Ctrl+C 取消当前操作;空闲时再次按下退出\n Ctrl+O 切换工具实时输出\n Ctrl+V / Ctrl+Alt+V 粘贴文本或图片 **\n\n ── 斜杠菜单 / 弹层导航 ──\n Up / Down 移动选择\n Enter 确认\n Esc 取消 / 关闭弹层\n Tab 在斜杠菜单中插入当前高亮命令\n 1..9 审批 / 交互提问中选择对应选项\n y / a / n 审批时允许一次 / 始终允许 / 拒绝\n\n * 组合键需要终端能区分修饰键。目前已知支持的有:\n Kitty / WezTerm / iTerm2(启用 Report Modifiers)/\n Windows Terminal / Ghostty / Warp。其他终端(包括 macOS\n Apple Terminal、默认 xterm、GNOME Terminal、VS Code 集成\n 终端)不区分 Shift+Enter 与 Enter,请用 \\ + Enter。\n ** Ctrl+Alt+V 是终端拦截 Ctrl+V 时的备用键;Ctrl+Shift+V\n 保留给终端原生纯文本粘贴。\n\n 提示:输入 /help 查看完整斜杠命令列表。\n\"#.into(),\n\n // ── Provider 向导 ──\n Msg::ProviderWizardHeader =>\n \" 管理 Provider:添加、编辑、删除或设置全局默认。按 Esc 取消。\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(已取消)\".into(),\n Msg::ProviderMenuAdd => \"添加\".into(),\n Msg::ProviderMenuAddDesc => \"新建 Provider 配置\".into(),\n Msg::ProviderMenuEdit => \"编辑\".into(),\n Msg::ProviderMenuEditDesc => \"修改已有 Provider 配置\".into(),\n Msg::ProviderMenuDelete => \"删除\".into(),\n Msg::ProviderMenuDeleteDesc => \"删除已有 Provider 配置\".into(),\n Msg::ProviderMenuSetDefault => \"设为全局默认\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"设置默认 Provider,并切换当前会话\".into(),\n Msg::ProviderImportPrompt =>\n \"粘贴模板自动识别(curl / JSON / TOML),或直接回车手动填写:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"已识别:{base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"未能识别为模板,请重贴 curl / JSON / TOML,或留空回车手动填写。\".into(),\n Msg::ProviderNoProviders =>\n \"尚未配置任何 Provider。\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"删除 \\\"{name}\\\"?[y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"已删除 \\\"{name}\\\"。\").into(),\n Msg::ProviderDeleteKept => \"(已保留)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"默认已设为 {name}。\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"已添加账号 \\\"{name}\\\"。已进入其模型列表,按 Ctrl+A 添加模型。\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"已更新 \\\"{name}\\\"。\").into(),\n Msg::ProviderStepName => \"Provider 名称?\".into(),\n Msg::ProviderStepType => \"类型?(openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"类型?[{current}](openai / claude / ollama,留空保持不变)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL?(例:https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL?[{current}](留空保持不变)\").into(),\n Msg::ProviderDefaultHint => \"Provider 默认值\".into(),\n Msg::ProviderStepApiKey =>\n \"API 密钥?(留空不设置)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API 密钥?[{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"已设置 — 留空保持不变\".into(),\n Msg::ProviderStepApiKeyUnset => \"未设置\".into(),\n Msg::ProviderStepModel => \"模型?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"模型?[{current}](留空保持不变)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"上下文窗口?[{default}] tokens(留空使用默认值;如 128000 / 256000 / 512000 / 1000000,或 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"上下文窗口?[{current}] tokens(留空保持不变;如 128000 / 256000 / 512000 / 1000000,或 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"上下文窗口必须是正整数 tokens,例如 128000 或 128k。\".into(),\n Msg::ProviderStepPricing =>\n \"每百万 token 价格(美元)?输入,输出,缓存输入(留空=未知/保持;输入 clear 可清除;如 2.5,10,0.25;免费填 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"每百万 token 价格(美元)?[{current}](留空保持;输入 clear 可清除)\").into(),\n Msg::ProviderPricingInvalid =>\n \"价格必须是三个有限且非负的数字:输入,输出,缓存输入。\".into(),\n Msg::ProviderNameEmpty => \"名称不能为空。\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL 不能为空。\".into(),\n Msg::ProviderUnknownType =>\n \"未知类型。请选择 openai / claude / ollama。\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"未知类型。请选择 openai / claude / ollama 或留空。\".into(),\n Msg::ProviderModelEmpty => \"模型不能为空。\".into(),\n Msg::ProviderEditKeep => \"(保持不变)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"已识别类型:{type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider 名称?[{default}](留空使用此名)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider 面板 ──\n Msg::ProviderPanelTabAccounts => \"账号\".into(),\n Msg::ProviderPanelTabModels => \"模型\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(尚无 Provider 账号 — 按 Ctrl+A 添加)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(无匹配的 Provider 账号)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(尚无模型 — 按 Ctrl+A 添加)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(无匹配的模型)\".into(),\n Msg::ProviderPanelLegacyBadge => \"旧\".into(),\n Msg::ProviderPanelDefaultBadge => \"默认\".into(),\n Msg::ProviderPanelModelCount { count } => format!(\"{count} 个模型\").into(),\n Msg::ProviderPanelAddModelRow => \"+ 添加模型\".into(),\n Msg::ProviderPanelAccountsHint =>\n \"筛选 · ↑↓选择 · ↵模型 · Ctrl+A添加 · Ctrl+E编辑 · Ctrl+Dx2 删除 · Tab切换 · Esc关闭\".into(),\n Msg::ProviderPanelModelsHint =>\n \"筛选 · ↑↓选择 · ↵默认/添加 · Ctrl+A添加 · Ctrl+E编辑 · Ctrl+Dx2 删除 · Tab切换 · Esc关闭\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"〔{account}〕· ↑↓选择 · ↵默认/添加 · Ctrl+A加模型 · Ctrl+E编辑 · Ctrl+Dx2 删除 · Tab全部 · Esc关闭\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"已保存模型“{model}”。\").into(),\n Msg::ProviderPanelAddTitle => \"【添加 Provider 账号】\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"【编辑账号 {account}】\").into(),\n Msg::ProviderPanelAddModelTitle => \"【添加模型】\".into(),\n Msg::ProviderPanelEditModelTitle => \"【编辑模型】\".into(),\n Msg::ProviderPanelFieldVendor => \"厂商\".into(),\n Msg::ProviderPanelFieldAccount => \"账号\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API 密钥\".into(),\n Msg::ProviderPanelFieldModel => \"模型\".into(),\n Msg::ProviderPanelFieldWindow => \"上下文窗口\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"设为默认\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ 切换\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"留空使用 ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"默认\".into(),\n Msg::ProviderPanelKeepOriginal => \"留空保留原值\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab 下一项 ←→ 切厂商 空格 勾选 ↵ 保存 Esc 返回\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab 切换 ↵ 保存 Esc 返回\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab 下一项 ←→ 切账号 空格 勾选 ↵ 保存 Esc 返回\".into(),\n // ── Model 选择器 ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" 当前会话已切换到 {provider} · {model}\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" 已切换到 {provider} · {model};已设为新会话默认\\n\").into(),\n\n // ── 会话选择器 ──\n Msg::SessionLoadFailed { error } =>\n format!(\"加载会话失败:{error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"已恢复:{name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"最近会话({source_id})正在另一个窗口运行,已从其最后提交状态创建独立分支({fork_id})。\"\n ).into(),\n\n // ── 待办面板 ──\n Msg::TodoPanelTitle => \"待办\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} 已完成\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} 更多…\").into(),\n\n // ── 审批面板 ──\n Msg::ApprovalAllowOnce => \"允许一次\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"本会话总是允许 {tool}\").into(),\n Msg::ApprovalAlwaysAllowFolder => \"本会话总是允许写入此目录\".into(),\n Msg::ApprovalAlwaysAllowCommand => \"本会话总是允许此命令\".into(),\n Msg::ApprovalDeny => \"拒绝\".into(),\n Msg::ApprovalHint => \"↑↓ 选择 · Enter 确认 · Esc 取消\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"允许 {tool}?\").into()\n } else {\n format!(\"允许 {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"已拒绝\".into(),\n\n Msg::CmdSwitchedAutoMode => \" 已切换到自动模式(所有工具自动批准)。\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => \" 已切换到自动接受编辑模式(文件编辑免审批;bash 仍会询问)。\\n\".into(),\n\n Msg::SessionTimeJustNow => \"刚刚\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}分钟前\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}小时前\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}天前\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} 条消息\").into(),\n Msg::SessionNameEmpty =>\n \"会话名不能为空\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"会话名过长(最多 {max} 个字符)\").into(),\n Msg::SessionNameControlChars =>\n \"会话名不能包含控制字符\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"列出会话失败:{error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" 已重命名:'{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"保存会话失败:{error}。未持久化新名称。\").into(),\n Msg::SessionNoneSelected =>\n \"未选中会话\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ 移动 · Enter 打开 · Ctrl+D×2 删除 · 输入内容搜索 · Esc 取消\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"恢复会话({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"恢复会话\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(此项目暂无会话)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(无匹配会话)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(无匹配 \\\"{query}\\\" — Backspace 清除)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"「{name}」已删除\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"再按 Ctrl+D 确认删除「{name}」\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"删除会话失败:{error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: 确认, Esc: 取消]\").into(),\n\n // ── 目录选择器 ──\n Msg::DirPickerTitle { n, total } =>\n format!(\"切换工作目录({n}/{total})\").into(),\n Msg::DirPickerHint =>\n \"↑↓ 移动 · Tab 补全 · Enter 进入 · 输入内容搜索/路径 · Esc 取消\".into(),\n Msg::DirPickerEmptyPath { query } =>\n format!(\"没有匹配的历史目录「{query}」· Enter 按路径进入\").into(),\n Msg::DirCurrent => \"当前\".into(),\n Msg::DirNotExists { path } =>\n format!(\"目录已不存在:{path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" 已切换到:{path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"不是目录:{path}\").into(),\n\n // ── 语言 ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ 已切换语言为 {label}({locale})。\\n\").into(),\n\n // ── 空闲/引导提示 ──\n Msg::IdleHintPrefix =>\n \"输入内容,或按 \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" 浏览命令\".into(),\n Msg::IdleHintFull =>\n \"输入内容,或按 / 浏览命令\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"添加自定义模型\".into(),\n Msg::IdleHintProviderFull =>\n \"使用 /provider 添加自定义模型\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"领取免费 Token 额度\".into(),\n Msg::IdleHintCodingplanFull =>\n \"使用 /login 领取免费 Token 额度\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"在浏览器中同步会话\".into(),\n Msg::IdleHintWebuiFull =>\n \"使用 /webui 在浏览器中同步会话\".into(),\n\n // ── 欢迎屏幕提示 ──\n Msg::WelcomeTipsHeading => \"上手提示\".into(),\n Msg::WelcomeTipLogin => \"领取免费额度\".into(),\n Msg::WelcomeTipProvider => \"添加自定义模型\".into(),\n Msg::WelcomeTipModel => \"设置默认模型\".into(),\n Msg::WelcomeTipResume => \"恢复上次会话\".into(),\n Msg::WelcomeTipSetup => \"一键推荐配置\".into(),\n Msg::WelcomeTipSkills => \"浏览可用技能\".into(),\n Msg::WelcomeTipPlugin => \"安装技能/命令插件\".into(),\n Msg::WelcomeTipWebui => \"在浏览器打开同步会话\".into(),\n Msg::WelcomeTipMcp => \"接入 MCP 工具\".into(),\n Msg::WelcomeTipPlan => \"只读规划模式\".into(),\n Msg::WelcomeTipSession => \"管理与切换会话\".into(),\n Msg::WelcomeTipLoop => \"循环执行提示词\".into(),\n Msg::WelcomeTipGoal => \"为本次会话设定目标\".into(),\n Msg::WelcomeTipInit => \"扫描代码库生成 AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"切换界面语言\".into(),\n Msg::WelcomeTipUsage => \"查看用量与额度\".into(),\n\n // ── 斜杠命令 ──\n Msg::CmdSwitchedPlanMode =>\n \" 已切换到 Plan 模式(只读探索)。\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" 已切换到 Build 模式(完整执行)。\\n\".into(),\n Msg::CmdNewSession =>\n \" 新会话已开始。\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime 正在重配置;就绪前会保留当前输入。\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"会话切换失败,原会话仍可用:{error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime 能力重载失败,原 Runtime 仍可用:{error}\").into(),\n Msg::CmdNoProviders =>\n \" 未配置任何 Provider。\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" 正在加载会话列表…\\n\".into(),\n Msg::CmdNoSessions =>\n \" 未找到历史会话。请先开始一段对话。\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"未知命令:/{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} 需要提供参数。用法:/{name} <你的输入>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"登录失败:{error}\").into(),\n Msg::CmdLogoutDone =>\n \" 已退出 AtomGit 登录。权限已刷新。\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"退出登录失败:{error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" 尚未登录。使用 /login 进行认证。\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" 配置已重载。当前:{provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"重载失败:{error}(保留先前配置)\").into(),\n Msg::CmdUndoNotSupported =>\n \" 撤销功能暂不支持。\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ 已退回到第 {target} 轮之前(删除第 {target}~{last} 轮)。你的提示词已填回输入框。\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ 仅回滚了对话记忆,磁盘文件未恢复。如需还原代码,请手动处理或用 /diff 查看。\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" 没有可撤销的轮次。\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" 无效的轮次 {requested}(当前共 {available} 轮)。\\n\").into(),\n Msg::CmdUndoBusy =>\n \" 当前回合进行中,无法撤销——请先按 Esc 取消。\\n\".into(),\n Msg::CmdRewindBusy =>\n \" 当前回合进行中,无法回退——请先按 Esc 取消。\\n\".into(),\n Msg::CmdRewindUnavailable => \"暂时无法打开回退\".into(),\n Msg::CmdUndoBadArg =>\n \" 用法:/undo 或 /undo N(N 为轮次号)。\\n\".into(),\n Msg::CmdNoChanges =>\n \" (无变更)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff 输出已截断\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" 正在检查更新...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"未配置活跃的 Provider。使用 /provider 添加一个。\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider 当前不可用。请使用 /login 登录,或用 /provider 配置。\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"当前构建不支持 AtomGit 官方网关。请安装官方版本,或使用 /provider 切换其他 Provider。\".into(),\n Msg::CmdProviderReloading =>\n \"正在切换 Provider/模型,请等待切换完成后再发送。\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider 尚未就绪,消息已排队,就绪后将自动发送\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ 尚未登录,消息已排队,执行 /login 登录后将自动发送\\n\".into(),\n\n // ── 审批提示 ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"允许 {}({})?[Y]是=回车 / [N]否 / [A]总是\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ 等待审批:\".into(),\n Msg::ApprovalAllow => \" 允许 \".into(),\n Msg::ApprovalAlways => \" 总是 \".into(),\n\n // ── 取消 / 错误前缀 ──\n Msg::Cancelled => \"(已取消)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[错误:{msg}]\").into(),\n\n // ── 升级 ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ 已升级 {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" 最新版本: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" 下载中 {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" 正在校验 SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" 正在替换二进制文件\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ 已升级到 {}(旧版本保留为 {})\\n 正在重启新版本...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ 已是最新版本,无需更新(当前 {},远端最新 {})。如需重装请加 --force。\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"升级失败: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ 已回滚。当前二进制: {};另一版本保存在 {}\\n 正在重启回滚版本...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider:{}\\n 配置文件:{}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" 提示 Token: {}\\n 补全 Token: {}\\n 缓存 Token: {}({}% 命中率)\\n Token 总计: {}\\n 预估费用: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" 提示 Token: {}\\n 补全 Token: {}\\n 缓存 Token: {}({}% 命中率)\\n Token 总计: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"免费\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"历史未归属用量\\n Token 总计: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" 深度思考:{}\\n 预算:{} Token\\n Provider:{}\\n\\n 用法:/think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" 深度思考已启用(预算:{} Token)。\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" 深度思考已禁用。\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" 思考预算已设为 {} Token。\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"预算必须 >= 1024(当前 {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"用法:/think budget <数字>\".into(),\n Msg::ThinkUsage =>\n \" 用法:/think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"用法:/remember <要记住的内容>(--global 为全局范围)\".into(),\n Msg::ForgetUsage =>\n \"用法:/forget <关键词>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" 用法:/background <任务描述>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" 正在分析项目并生成 AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" 工作目录:{}\\n 无最近项目。使用 `/cd <路径>` 切换。\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff 失败:{}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"本版本由 HarmonyBrew 管理,请运行 `brew upgrade atomcode` 升级\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"未知的 /upgrade 参数:{}\\n 用法:/upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" 没有可调用的技能。\\n\".into(),\n Msg::SkillsAvailable =>\n \" 可用技能:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"未知技能:{}(输入 /skills 查看列表)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" 已加载 skills:{}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" 正在重载 MCP 服务器...({} 个已配置)\\n\", count).into(),\n Msg::McpConnecting =>\n \" 正在连接:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} 连接中...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" 未配置 MCP 服务器。\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" 已请求重载 MCP;旧 MCP 工具会先撤下,再在后台重新连接。\\n\".into(),\n Msg::McpClearedNoServers =>\n \" 已请求重载 MCP;旧 MCP 工具会先撤下,当前没有已配置的服务器。\\n\".into(),\n Msg::McpToolsUsage =>\n \" 用法:/mcp tools <服务器名>\\n 示例:/mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP 服务器:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"MCP 重载失败:无法加载 .mcp.json / $ATOMCODE_HOME/mcp.json:{:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" 用法:/mcp login <服务名>\\n 示例:/mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" 用法:/mcp logout <服务名>\\n 示例:/mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth 登录失败:无法加载配置:{error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth 登录失败:配置中未找到服务 '{server}'。\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" 正在浏览器中启动 '{server}' 的 MCP OAuth 流程...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" 已保存 MCP 服务 '{server}' 的 {provider} OAuth Token。正在重载 MCP 能力。\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth 失败:{error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" 已移除 MCP 服务 '{server}' 保存的 OAuth Token。\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" 未找到 MCP 服务 '{server}' 保存的 OAuth Token。\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth 登出失败:{error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" 已信任本项目 — 正在重连 MCP。\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" 已撤销本项目信任。\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" 本项目未被信任。\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP 服务 '{name}' 已为 .{ext} 启动\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP 服务 '{name}'(.{ext})失败:{error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" 用法:\\n /worktree create <分支> [基准] 创建工作树并切换\\n /worktree list 列出所有工作树\\n /worktree done 切回原始目录\\n /worktree cleanup <分支> 清理工作树\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" 用法:/worktree create <分支> [基准]\\n 示例:/worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ 工作树已创建\\n 分支:{}(基于 {})\\n 路径:{}\\n 工作目录已切换\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"工作树创建失败:{}\", error).into(),\n Msg::WorktreeNoActive =>\n \" 没有活跃的工作树。\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"工作树列表失败:{}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" 活跃工作树:\\n\".into(),\n Msg::WorktreeHasChanges => \"(有变更)\".into(),\n Msg::WorktreeClean => \"(无变更)\".into(),\n Msg::WorktreeCurrent => \" ← 当前\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ 工作目录已切回:{}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" 提示:使用 'git merge {}' 或创建 PR 合入主分支\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" 没有活跃的工作树会话。先使用 /worktree create 创建一个。\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" 用法:/worktree cleanup <分支> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ 工作树 '{}' 已清理\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" 工作目录已切回:{}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ 工作树 '{}' 有未提交的变更。\\n 使用 /worktree cleanup {} --force 强制清理\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"工作树清理失败:{}\", error).into(),\n\n // ── /help commands(自定义命令) ──\n Msg::HelpCustomCommandsHeader =>\n \" 自定义命令:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (无)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" 创建方式:~/.atomcode/commands/<名称>.md 或 .atomcode/commands/<名称>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"全局\".into(),\n Msg::HelpSourceProject => \"项目\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup 完成 — {} 装好, {} 跳过, {} 失败 · 耗时 {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"已安装:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\n跳过:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\n失败:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji — U+1F4A1 has ambiguous terminal display\n // width and desynced the line's cell layout on some terminals.\n // CJK chars below have stable width-2 so they're fine.\n \"提示:运行 \\x1b[1;96m/setup\\x1b[0m 可自动为该项目配置 hooks、skills 和 MCP。\".into(),\n Msg::CmdSetupRunning =>\n \"正在运行 atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills 已重载 — {} 个可用\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup 错误:{error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 正在运行 setup skill — 分析项目并生成推荐...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill 未找到 — 请重新运行 /setup 以重新安装\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"用法:/plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"用法:/plugin marketplace [add|remove|update|list] <参数>\".into(),\n Msg::PluginInstallUsage =>\n \"用法:/plugin install <插件名> 或 <插件>@<市场>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"未在任何市场中找到插件 `{plugin}`。使用 /plugin marketplace list 查看已注册的市场。\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"插件 `{plugin}` 存在于多个市场中,请指定:\").into(),\n Msg::PluginUninstallUsage =>\n \"用法:/plugin uninstall <插件名> 或 <插件>@<市场>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"插件 `{plugin}` 未安装。使用 /plugin list 查看已安装插件。\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"插件 `{plugin}` 从多个市场安装,请指定卸载哪一个:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"未注册任何市场\".into(),\n Msg::PluginMarketplacesHeader =>\n \"已注册的市场:\".into(),\n Msg::PluginNoInstalled =>\n \"未安装任何插件\".into(),\n Msg::PluginInstalledHeader =>\n \"已安装的插件:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"正在从 {url} 克隆 marketplace…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"已移除 marketplace `{name}`\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"移除 marketplace 失败:{error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"正在更新 marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"列出 marketplace 失败:{error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"插件市场同步已跳过(不影响对话):{detail}\").into(),\n Msg::OfflineModeActive =>\n \"离线模式:已停用联网工具、遥测与自动更新。\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} 个插件带未信任的 hook({names})—— 不会运行。运行 atomcode plugin trust <name> 授权。\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"正在安装 `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"正在安装 `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" 插件 `{id}` 已安装。\\n PS: 如需重新安装,请先执行 `/plugin uninstall {id}`,然后再执行 `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"浏览并安装\".into(),\n Msg::PluginMgrAdd => \"添加市场…\".into(),\n Msg::PluginMgrRemove => \"移除市场…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"已安装 ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ 已安装\".into(),\n Msg::PluginMgrInstalledStatus => \"已安装\".into(),\n Msg::PluginMgrInstallableStatus => \"可以安装\".into(),\n Msg::PluginMgrInstallingStatus => \"安装中\".into(),\n Msg::PluginMgrUpdatingStatus => \"更新中\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ 选择 · ⏎ 进入 · esc 返回\".into(),\n Msg::PluginMgrHintToggle => \"⏎ 安装/卸载 · esc 返回\".into(),\n Msg::PluginMgrHintRemove => \"⏎ 移除 · esc 返回\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ 卸载 · esc 返回\".into(),\n Msg::PluginMgrHintUrl => \"⏎ 确认添加 · esc 取消\".into(),\nMsg::PluginMgrHintPending => \"安装中,请稍候… · esc 返回\".into(),\nMsg::PluginMgrHintUpdating => \"更新中,请稍候… · esc 返回\".into(),\nMsg::PluginMgrInstallingLabel => \"安装中…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"暂无市场,请选「添加市场…」 · esc 返回\".into(),\n Msg::PluginMgrEmptyPlugins => \"该市场暂无插件 · esc 返回\".into(),\n Msg::PluginMgrEmptyInstalled => \"暂无已安装插件 · esc 返回\".into(),\n Msg::PluginMgrCloning => \"正在克隆市场…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"正在安装 {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"正在更新 {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc 取消\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ 移除市场\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33m您确定要移除插件市场 '{name}' 吗?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"是,移除\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"否,保留\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ 选择 · Enter 确认 · Esc 取消\".into(),\n Msg::PluginScopeUser => \"为你安装(用户级)\".into(),\nMsg::PluginScopeUserDesc => \"~/.atomcode/plugins — 所有项目可见\".into(),\nMsg::PluginScopeProject => \"为所有协作者安装(项目级)\".into(),\nMsg::PluginScopeProjectDesc => \".atomcode/plugins — 通过 git 共享\".into(),\nMsg::PluginScopeLocal => \"仅在本仓库为你安装(本地级)\".into(),\nMsg::PluginScopeLocalDesc => \".atomcode/plugins/local — 不提交到 git\".into(),\nMsg::PluginScopeHint => \"↑↓ 选择范围 · Enter 确认 · Esc 返回\".into(),\nMsg::PluginScopeUserShort => \"用户级\".into(),\nMsg::PluginScopeProjectShort => \"项目级\".into(),\nMsg::PluginScopeLocalShort => \"本地级\".into(),\nMsg::PluginActionUninstall => \"卸载\".into(),\nMsg::PluginActionUninstallDesc => \"卸载该插件所有的组件与配置\".into(),\nMsg::PluginActionUpdate => \"更新\".into(),\nMsg::PluginActionUpdateDesc => \"重新拉取并安装最新版本\".into(),\nMsg::PluginActionDisable => \"禁用\".into(),\nMsg::PluginActionDisableDesc => \"临时禁用该插件\".into(),\nMsg::PluginActionBack => \"返回到上一级\".into(),\nMsg::PluginActionBackDesc => \"返回已安装插件列表\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"已卸载 `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"卸载失败:{error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"列出插件失败:{error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"插件已重新加载:{skills} 个 skill,{warnings} 个警告\").into(),\n Msg::PluginGitNotFound =>\n \"💡 当前环境未安装 git 或 git 不在 PATH 中,插件市场自动安装和自动更新已禁用。请安装 git(macOS 可执行 `xcode-select --install`,Ubuntu 可执行 `sudo apt install git`)后重启 AtomCode。\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ 已添加 marketplace `{name}`(commit {commit},共 {count} 个插件)\\n \\\n 插件:{plugins} —— 运行 /plugin install <插件名>@{name} 安装后才能使用其命令\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` 已更新至 {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup 完成,已自动刷新:{skills} 个 skill,{warnings} 个警告\").into(),\n\n // ── 命令描述 ──\n Msg::CmdDescWebui => \"启动浏览器 webui(子命令:stop / lan / --host <地址>)\".into(),\nMsg::CmdDescSetup =>\n\"扫描项目、安装种子文件并运行 setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"恢复上次会话\".into(),\n Msg::CmdDescRename => \"重命名当前会话\".into(),\n Msg::CmdDescLogin => \"使用 AtomGit OAuth 登录并领取 CodingPlan 模型\".into(),\n Msg::CmdDescLogout => \"退出 AtomGit 登录\".into(),\n Msg::CmdDescWhoami => \"显示当前登录用户\".into(),\n Msg::CmdDescModel => \"设置默认 Provider / 模型,并切换当前会话\".into(),\n Msg::CmdDescProvider => \"管理 Provider(添加、编辑、删除、设为全局默认)\".into(),\n Msg::CmdDescStatus => \"显示会话状态\".into(),\n Msg::CmdDescConfig => \"显示配置文件路径\".into(),\n Msg::CmdDescReload => \"从磁盘重新加载 $ATOMCODE_HOME/config.toml\".into(),\n Msg::CmdDescCd => \"切换工作目录并开启新建对话\".into(),\nMsg::CmdDescInit => \"分析项目并生成 AGENTS.md\".into(),\nMsg::CmdDescBg => \"后台会话:/bg、/bg list、/bg <N>、/bg drop <N>\".into(),\nMsg::CmdDescBackground => \"在隔离的后台上下文中运行一次性任务(只读工具子集)\".into(),\n Msg::CmdDescDiff => \"显示 git diff\".into(),\n Msg::CmdDescClear => \"清屏\".into(),\n Msg::CmdDescSession => \"开始新会话(清除对话)\".into(),\n Msg::CmdDescCost => \"显示 Token 费用\".into(),\n Msg::CmdDescUsage => \"显示 CodingPlan 用量(标签:当前窗口 / 总览 / 模型)\".into(),\n Msg::CmdDescContext => \"显示上下文预算明细\".into(),\n Msg::CmdDescCompact => \"压缩对话历史\".into(),\n Msg::CmdDescRemember => \"保存记忆(/remember --global 为全局)\".into(),\n Msg::CmdDescForget => \"删除匹配的记忆\".into(),\n Msg::CmdDescMemory => \"显示所有已保存的记忆\".into(),\n Msg::CmdDescMcp => \"显示 MCP 服务器状态(子命令:reload)\".into(),\n Msg::CmdDescUndo => \"撤销:把对话记忆回退一轮(/undo 或 /undo N)\".into(),\n Msg::CmdDescRewind => \"回退:把对话恢复到更早的检查点\".into(),\n Msg::CmdDescWorktree => \"Git 工作树隔离(create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"升级到最新版本(子命令:rollback)\".into(),\n Msg::CmdDescPlan => \"切换到 Plan 模式(只读探索)\".into(),\n Msg::CmdDescBuild => \"切换到 Build 模式(完整执行)\".into(),\n Msg::CmdDescAuto => \"切换到 Auto 模式(所有工具自动批准)\".into(),\n Msg::CmdDescThink => \"深度思考控制(on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek 推理强度控制(high / max / off)\".into(),\n Msg::CmdDescHelp => \"显示帮助\".into(),\n Msg::CmdDescKeys => \"显示键盘快捷键\".into(),\n Msg::CmdDescLanguage => \"切换显示语言\".into(),\n Msg::CmdDescQuit => \"退出 AtomCode\".into(),\n Msg::CmdDescSkills => \"浏览已加载的技能\".into(),\n Msg::CmdDescPlugin => \"插件市场(子命令:marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"从剪贴板粘贴图片(Windows 下 Ctrl+V 被终端拦截时的备用入口)\".into(),\n Msg::CmdDescCopy => \"复制上一条回复里的代码块,或用 /copy msg 复制整条回复(/copy、/copy N、/copy all、/copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"已复制代码块到剪贴板({lines} 行,{chars} 字符)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"已复制回复到剪贴板({lines} 行,{chars} 字符)\").into(),\n Msg::CopyNoCodeBlock => \"上一条回复里没有可复制的代码块\".into(),\n Msg::CopyMsgEmpty => \"上一条回复为空,没有可复制的内容\".into(),\n Msg::CopyBadIndex { count } => format!(\"没有这个代码块——上一条回复共 {count} 个(用 /copy N,范围 1..={count})\").into(),\n Msg::CopyFailed => \"剪贴板不可用——复制失败\".into(),\n Msg::CmdDescSave => \"把当前对话导出为 markdown 文件(/save、/save [文件名])\".into(),\n Msg::SaveOk { path } => format!(\"对话已保存到 {path}\").into(),\n Msg::SaveEmpty => \"当前没有对话内容可导出\".into(),\n Msg::SaveIoError { error } => format!(\"保存对话失败:{error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"路径无效——目录不存在:{path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"目标已存在且非 markdown 文件,已拒绝覆盖(避免误删源码/配置):{path}。请换个 .md 文件名或新路径。\").into(),\n Msg::CodeBlockCopied => \"📋 代码块已复制到剪贴板\".into(),\n Msg::CmdDescGuide => \"向 atomcode-guide 提问使用方法\".into(),\n Msg::CmdDescView => \"在浮层窗口中查看文件内容\".into(),\n Msg::CmdDescApp => \"通过中继将当前会话暴露给手机 App(扫码配对;/app stop 断开)\".into(),\n Msg::CmdDescSync => \"接入实时 webui 会话(/sync off 断开)\".into(),\n Msg::CmdDescReview => \"审查当前代码改动(/review · /review staged · /review <基准>)\".into(),\n Msg::CmdDescGoal => \"设定完成目标(自主循环直到达成)\".into(),\n Msg::CmdDescProxy => \"切换出站代理模式\".into(),\n Msg::CmdDescTodo => \"显示当前任务清单;`/todo add <任务>` 追加一条,`/todo clear` 清空\".into(),\n Msg::CmdDescSchedule => \"查看定时任务列表和下次运行时间\".into(),\n Msg::CmdDescDesktop =>\n \"打开 AtomCode 桌面端(已安装则启动,否则显示下载地址)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"正在打开 {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"未检测到 AtomCode 桌面端。下载安装:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"找到了应用但启动失败:{}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"当前无任务清单(模型尚未创建 todo)。\".into(),\n Msg::TodoListHeader => \"当前任务清单:\".into(),\n Msg::TodoAddUsage => \"用法:/todo add <任务描述>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode 使用指南 — 输入 /guide <问题> 提问\".into(),\n Msg::GuideMenuTopics => \"常用话题:\".into(),\n Msg::GuideMenuGettingStarted => \"怎么开始使用 首次安装、登录、配置\".into(),\n Msg::GuideMenuSwitchModel => \"怎么设置默认模型 /model /provider 操作\".into(),\n Msg::GuideMenuMcp => \"怎么用 MCP MCP 服务器配置与管理\".into(),\n Msg::GuideMenuSkills => \"怎么用技能和插件 /skills /plugin 使用\".into(),\n Msg::GuideMenuMemory => \"怎么用记忆功能 /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"怎么用后台任务 /bg 后台执行\".into(),\n Msg::GuideMenuContext => \"怎么管理上下文 /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"快捷键有哪些 键盘快捷键参考\".into(),\n Msg::GuideMenuConfig => \"怎么配置 config.toml 配置说明\".into(),\n Msg::GuideMenuTip => \"\n 提示:输入 /guide <你的问题> 获取具体回答。\n 例如:/guide 怎么设置默认模型\n\".into(),\n Msg::GuideMenuDocUrl => \" 完整文档:https://atomcode.atomgit.com/docs/zh/\".into(),\n Msg::CmdGuideInstalling => \"正在安装 ask skill,请稍候...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill 未安装,正在自动安装 atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill 安装完成,正在回答: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"安装完成但未找到 ask skill,请运行 /plugin reload 后重试\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"安装 ask skill 失败: {}. 请手动运行 /plugin install atomcode@atomcode-skills\", error).into(),\n Msg::CmdPasteNoImage => \"剪贴板中没有图片。\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"鸿蒙暂不支持读取系统剪贴板图片。请把图片存成文件,然后粘贴/输入它的绝对路径(如 /storage/.../pic.png)来添加图片。\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"当前模型不支持 reasoning_effort(仅对 DeepSeek V4 有效)\".into(),\n\n // ── 配置保存失败 ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"配置保存失败:{}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"第 1/3 步 · 欢迎\".into(),\n Msg::OnboardingStepHeaderLanguage => \"第 2/3 步 · 语言\".into(),\n Msg::OnboardingStepHeaderSetup => \"第 3/3 步 · 配置\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"版本 {v} · 在终端里运行的 AI 编程代理\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• 多步骤 agent loop · 内置代码图工具\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• 兼容所有 OpenAI 风格 API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• 通过 CodingPlan 获取免费额度\".into(),\n Msg::OnboardingIntroPressEnter => \"按 Enter 继续。\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C 可随时退出。\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"在终端里运行的 AI 编程代理。\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"选择界面语言。任何时候都可以用 `/language` 修改。\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"自动检测 (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"想怎么开始?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 选择 · Enter 确认 · ← 返回 · Esc 跳过\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome 会清屏。是否继续?[y/N]\".into(),\n Msg::CmdWelcomeDescription => \"重新运行 onboarding 向导\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL 识别图片成功,返回 {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL 预处理失败:{reason} · 本轮以纯文字继续,图片已恢复可重试\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} 轮 · {tool_call_count} 工具 · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\":{r}\")).unwrap_or_default();\n format!(\"✗ 已中断{cause} · {turn_count} 轮 · {tool_call_count} 工具 · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" 登录 AtomGit — 使用微信扫描下方二维码:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n 或在浏览器打开下方链接:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" 当前终端无法渲染二维码,\\n \\\n 且该平台不支持基于 URL 的登录。\\n \\\n 请改用支持 Unicode 的终端以显示二维码。\".into(),\n Msg::LoginUrlOnly =>\n \" 在浏览器中打开此链接以登录 AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n 按 ESC 取消\\n\".into(),\n Msg::CtxUsageHeader => \"上下文用量\".into(),\n Msg::CtxUsageNoTurns => \"(请至少完成一轮对话 — 统计在每轮结束时记录)\".into(),\n Msg::CtxUsageWaiting => \"(等待首轮完成 — 当前仅为部分统计)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"系统提示\".into(),\n Msg::CtxLabelToolDefs => \"工具定义\".into(),\n Msg::CtxLabelColdZone => \"冷区\".into(),\n Msg::CtxLabelMessages => \"消息\".into(),\n Msg::CtxLabelFree => \"空闲\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"窗口内消息数:{n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== 系统提示 ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(为空 — 完成一轮对话后捕获)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(无需压缩 — 当前对话较短)\\n\".into(),\n Msg::CompactStarting => \"(正在使用 LLM 摘要进行压缩...)\\n\".into(),\n Msg::CompactInterrupted => \"(压缩已中断 — coding runtime 已变更或停止)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"同步模式下 /compact 暂不可用;请先执行 /sync off\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"本地 runtime 尚未恢复最新同步会话,暂不能执行 /compact\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"本地 runtime 正在恢复同步会话,请稍候\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"本地 runtime 恢复超时,已重新接回 Live 同步\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(无需压缩 — 压缩后不会节省 token:{} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } =>\n format!(\"(已压缩 — 丢弃 {} 条消息,{} → {} tokens)\\n\", messages, before, after).into(),\n Msg::Compacting => \"正在压缩…\".into(),\n Msg::CompactingSlow => \"正在压缩…(较慢)\".into(),\n Msg::CompactMarkDrain { messages, before, after } =>\n format!(\"已压缩 · 摘要 {} 条 · ~{}→~{} tok\", messages, before, after).into(),\n Msg::CompactMarkStub { saved } =>\n format!(\"已折叠工具输出 · 节省 ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — 朝着设定的条件自主进行多轮工作。\\n \\\n 用法:\\n \\\n \\u{20}\\u{20}/goal <条件> 设定新目标;智能体循环执行直到评估器判定达成\\n \\\n \\u{20}\\u{20}/goal 显示当前目标状态\\n \\\n \\u{20}\\u{20}/goal status 同上\\n \\\n \\u{20}\\u{20}/goal clear 停止当前目标(别名:stop、off、reset、none、cancel)\\n \\\n \\u{20}\\u{20}/goal help 显示本帮助\\n \\\n 说明:\\n \\\n \\u{20}\\u{20}- 每轮由一个快速模型评估;通过 ~/.atomcode/config.toml 中的 [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider 配置。\\n \\\n \\u{20}\\u{20}- 没有内置的轮次 / 时间上限——请在条件文本中自行表达预算\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}(例如 \\\"或在 20 轮后停止\\\")。Claude Code 的 /goal 也是这样工作的。\\n \\\n \\u{20}\\u{20}- 随时可用 Esc / Ctrl+C 停止目标。\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ 目标:{}\\n 轮次:{}\\n 已用时:{}分 {}秒\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" 当前没有进行中的目标。\\n 用法:/goal <条件> | /goal help\\n\".into(),\n Msg::GoalCleared => \" 已清除目标。\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop:{} · 第 {} 轮 · {}分 {}秒\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" 当前没有进行中的 /loop。\\n 用法:/loop <间隔> <命令> 或 /loop <任务>\\n\".into(),\n Msg::LoopCleared => \" 已停止 /loop。\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop 第 {} 轮 · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop 已停止(达到次数上限)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop 已结束:{reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (提示:重启 / 恢复会话后该 loop 不会保留)\".into(),\n Msg::CmdDescLoop =>\n \"按固定间隔重复执行提示/命令,或让模型自主决定节奏\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"当前模型 \\\"{}\\\" 不支持图片输入,且未配置 vision_preprocessor_provider。\\\n 请用 /model 切换到支持视觉的模型,或在配置中设置 vision_preprocessor_provider。\",\n model\n )\n .into(),\n Msg::VisionPreprocessorUnresolvable { model, provider } => format!(\n \"当前模型 \\\"{}\\\" 不支持图片输入;已配置的 vision_preprocessor_provider \\\"{}\\\" \\\n 无法解析(请检查名称是否与配置中的 provider/model 一致)。\\\n 请修正该名称,或用 /model 切换到支持视觉的模型。\",\n model, provider\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions 已启用:所有工具调用将自动批准(无权限提示)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions:所有工具调用将自动批准\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} 警告:正在以管理员权限运行。\\n 模型可能可以访问系统文件。\\n 建议改用普通权限、并在受限的工作目录中运行 AtomCode。\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] 正在以管理员权限运行 — 模型可能可以访问系统文件。\".into(),\n\n Msg::CtrlCAgainToExit => \" (再次按 Ctrl+C 退出)\\n\".into(),\n Msg::EscAgainToUndo => \" (再次按 Esc 打开回退选择)\\n\".into(),\n Msg::BashInputHint => \"回车执行 bash 命令\".into(),\n Msg::ShellModeHint => \"! 进入 shell 模式\".into(),\n Msg::PendingMessagesTitle =>\n \"将在下一次工具调用后提交的消息(按 Esc 中断并立即发送)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"运行时已停止,{count} 条待处理消息未发送\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} 多行输入:在行尾加 `\\\\` 再按 Enter。\\n \\\n 所有终端均可用。(Shift / Alt / Ctrl + Enter 在部分终端也支持,\\n \\\n 取决于该终端的键盘协议 — 可以试试看。)\\n\\n\"\n .into(),\n\n // ── /bg(后台会话)──\n Msg::BgHelp =>\n \" /bg 将当前会话放到后台,打开新的前台会话\\n /bg list 列出后台会话\\n /bg <N> 恢复第 N 号后台会话\\n /bg drop <N> 丢弃第 N 号后台会话\\n /bg help 显示此帮助\\n\".into(),\n Msg::BgListEmpty => \" 没有后台会话。\\n\".into(),\n Msg::BgListHeader => \" # ID 状态 创建时间 摘要\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"运行中\".into(),\n Msg::BgStateIdle => \"空闲\".into(),\n Msg::BgStateDone => \"已完成\".into(),\n Msg::BgStateCancelled => \"已取消\".into(),\n Msg::BgStateError => \"错误\".into(),\n Msg::BgAgeNow => \"刚刚\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n} 分钟\").into(),\n Msg::BgAgeHours { n } => format!(\"{n} 小时\").into(),\n Msg::BgAgeDays { n } => format!(\"{n} 天\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"后台槽位已达上限({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" 新前台会话 [{new_id}]\\n 后台:[#{slot}] {old_id}(状态:{state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"无效的后台槽位 {slot}(可用:{available})\").into(),\n Msg::BgNoRuntimeClient => \"后台槽位没有运行时客户端\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" 已恢复后台 [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" 原前台会话已移至 [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" 已丢弃后台 [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" 后台:[#{slot}] {short_id}(状态:运行中)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"后台任务超时({secs} 秒)。\").into(),\n Msg::BgTaskError { error } =>\n format!(\"错误:{error}\").into(),\n Msg::BgTaskCancelled => \"已取消。\".into(),\n Msg::BgTaskNoSummary => \"任务完成(无摘要文本)。\".into(),\n // ── CLI atomcode --help i18n ──\n Msg::CliAbout => \"终端中的 AI 编程助手\".into(),\n Msg::CliAboutLogin => \"通过 AtomGit OAuth 登录并领取 CodingPlan 模型\".into(),\n Msg::CliAboutLogout => \"退出 AtomCode 登录\".into(),\n Msg::CliAboutStatus => \"查看当前登录状态\".into(),\n Msg::CliAboutUpgrade => \"就地升级 atomcode 到最新发布版本\".into(),\n Msg::CliAboutRollback => \"回退到上一个版本(与 .bak 交换)\".into(),\n Msg::CliAboutMcp => \"管理 .mcp.json 中的 MCP 服务器配置\".into(),\n Msg::CliAboutDaemon => \"启动用于 IDE 集成的 HTTP 守护进程\".into(),\n Msg::CliAboutWebui => \"启动本地浏览器 webui\".into(),\n Msg::CliAboutTelemetry => \"遥测控制\".into(),\n Msg::CliAboutPlugin => \"管理技能/命令插件\".into(),\n Msg::CliAboutUninstall => \"卸载 AtomCode:移除二进制文件、PATH 编辑和数据\".into(),\n Msg::CliAboutSetup => \"安装种子文件(技能/命令/钩子/MCP)到 ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"管理钩子(列表、测试、启用/禁用)\".into(),\n Msg::CliAboutHooksList => \"列出所有已加载钩子及其状态\".into(),\n Msg::CliAboutHooksTest => \"按名称测试指定钩子\".into(),\n Msg::CliAboutHooksPaths => \"显示钩子配置路径\".into(),\n Msg::CliAboutPluginMarketplace => \"市场注册操作\".into(),\n Msg::CliAboutPluginInstall => \"从已注册的市场安装插件\".into(),\n Msg::CliAboutPluginUninstall => \"卸载已安装的插件\".into(),\n Msg::CliAboutPluginList => \"列出已安装的插件\".into(),\n Msg::CliAboutMarketplaceAdd => \"克隆市场 git 仓库并在本地注册\".into(),\n Msg::CliAboutMarketplaceRemove => \"删除已注册的市场\".into(),\n Msg::CliAboutMarketplaceUpdate => \"重新拉取已注册的市场并刷新插件索引\".into(),\n Msg::CliAboutMarketplaceList => \"列出已注册的市场\".into(),\n Msg::CliAboutMcpAdd => \"添加或替换 stdio MCP 服务器\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"使用 OAuth 添加 GitHub 远程 MCP 服务器\".into(),\n Msg::CliAboutMcpLogin => \"完成远程 MCP 服务器的 OAuth 登录\".into(),\n Msg::CliAboutMcpLogout => \"删除远程 MCP 服务器的已保存 OAuth 凭证\".into(),\n Msg::CliAboutTelemetryStatus => \"查看当前遥测状态和队列统计\".into(),\n Msg::CliAboutTelemetryEnable => \"启用遥测\".into(),\n Msg::CliAboutTelemetryDisable => \"禁用遥测\".into(),\n Msg::CliAboutTelemetryDump => \"打印待发送的队列事件\".into(),\n Msg::CliAboutTelemetryClear => \"清除队列事件\".into(),\n Msg::CliHelpContinue => \"继续上一次会话而不是启动新会话\".into(),\n Msg::CliHelpProvider => \"指定使用的 Provider(覆盖配置默认值)\".into(),\n Msg::CliHelpModel => \"指定使用的模型(覆盖配置中的 Provider 模型)\".into(),\n Msg::CliHelpLang => \"设置界面语言(如 en、zh-CN、zh)\".into(),\n Msg::CliHelpConfig => \"配置文件路径\".into(),\n Msg::CliHelpDir => \"工作目录(默认为当前目录)\".into(),\n Msg::CliHelpPrompt => \"在无头(非交互)模式下运行的提示\".into(),\n Msg::CliHelpPromptFile => \"从文件读取提示\".into(),\n Msg::CliHelpVerbose => \"在 stderr 上显示工具调用、token 用量和回合摘要\".into(),\n Msg::CliHelpDev => \"禁用本次启动的自动更新\".into(),\n Msg::CliHelpNoTelemetry => \"禁用本次调用的遥测\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"跳过所有权限提示 -- 自动批准每个工具调用\".into(),\n Msg::CliHelpForce => \"即使已是最新版本也重新安装\".into(),\n Msg::CliHelpPortDaemon => \"监听端口(默认:13456)\".into(),\n Msg::CliHelpClient => \"遥测客户端标识\".into(),\n Msg::CliHelpIdleTimeout => \"空闲关闭超时(秒);0 禁用\".into(),\n Msg::CliHelpPortWebui => \"端口(默认:13457)\".into(),\n Msg::CliHelpHost => \"绑定地址(默认:127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"跳过提示;使用每组的默认决定\".into(),\n Msg::CliHelpUninstallPurge => \"完全清除 ~/.atomcode/\".into(),\n Msg::CliHelpUninstallKeepData => \"完全保留 ~/.atomcode/\".into(),\n Msg::CliHelpUninstallDryRun => \"仅打印计划;不执行操作\".into(),\n Msg::CliHelpMcpGlobal => \"写入 ~/.atomcode/mcp.json 而非 <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"项目 .mcp.json 的目录\".into(),\n Msg::CliHelpMcpName => \"服务器键名\".into(),\n Msg::CliHelpHooksTestName => \"要测试的钩子名称\".into(),\n Msg::CliHelpPluginSpec => \"如 plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"市场仓库的 Git URL\".into(),\n Msg::CliHelpMarketplaceName => \"市场名称\".into(),\n Msg::CliAboutHelp => \"打印帮助信息\".into(),\n Msg::CliHelpMcpCommand => \"可执行文件及参数\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"当前窗口\".into(),\n Msg::UsageTabOverview => \"总览\".into(),\n Msg::UsageTabModels => \"模型\".into(),\n Msg::UsageCurrentTitle => \"速率限制窗口\".into(),\n Msg::UsageResetsIn { hms } => format!(\"剩余重置时间 {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours} 小时滚动窗口\").into(),\n Msg::UsageWindowUnavailable => \"窗口数据不可用\".into(),\n Msg::UsageStatFavorite => \"最常用模型\".into(),\n Msg::UsageStatTotal => \"总 Token 数\".into(),\n Msg::UsageStatRequests => \"请求次数\".into(),\n Msg::UsageStatActiveDays => \"活跃天数\".into(),\n Msg::UsageStatMostActive => \"最活跃日期\".into(),\n Msg::UsageStatLongestStreak => \"最长连续天数\".into(),\n Msg::UsageStatCurrentStreak => \"当前连续天数\".into(),\n Msg::UsageHeatLess => \"少\".into(),\n Msg::UsageHeatMore => \"多\".into(),\n Msg::UsageModelsTitle => \"各模型用量\".into(),\n Msg::UsageNoData => \"暂无用量数据\".into(),\n Msg::UsageFooterHint => \"← / → 或 Tab 切换 · Ctrl+S 复制 · Esc 关闭\".into(),\n Msg::UsageFetchFailed { error } => format!(\"加载用量失败:{error}\").into(),\n Msg::UsagePlanTitle => \"计划\".into(),\n Msg::UsagePlanActive => \"生效中\".into(),\n Msg::UsagePlanExpired => \"已过期\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"领取 {claimed} · 到期 {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"剩余 {remaining}/{total} 天\").into(),\n Msg::UsageCopied => \"已复制到剪贴板\".into(),\n Msg::UsageCodingPlanOnly =>\n \"使用情况仅 CodingPlan 可用 — 请先 /login。\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"模型初始化失败:{detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"尚未登录,模型暂不可用;运行 /login 后可继续对话。\".into(),\n Msg::ProviderInitSourceBuild =>\n \"当前为源码构建,无法使用 AtomGit 免费网关。请用 /provider 配置一个自带 api_key \\\n 的模型(如 DeepSeek 官方 / GLM / OpenAI),或改用官方发布版。\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url「{base_url}」是 AtomGit 网关,当前构建无法对其鉴权。请使用官方版本,\\\n 或将该 provider 指向带 api_key 的标准 OpenAI 兼容端点。\"\n ).into(),\n Msg::StreamStalled => \"按 esc 可取消\".into(),\n Msg::ConhostScrollHint =>\n \"提示:经典 Windows 控制台功能受限——任务执行中无法上滚查看历史,字符与吉祥物也会降级显示。\\\n 换用 \\x1b[1;96mWindows Terminal\\x1b[0m 体验更佳。\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn zh_official_build_required_mentions_official_and_releases() {\n let s = zh_cn(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"官方\"));\n assert!(s.contains(\"releases\") || s.contains(\"发布\"));\n }\n\n #[test]\n fn zh_stale_clock_mentions_time() {\n let s = zh_cn(Msg::CpSignStaleClockSkew);\n assert!(s.contains(\"时间\") || s.contains(\"时钟\"));\n }\n\n #[test]\n fn zh_replay_persisted_is_non_empty() {\n let s = zh_cn(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn zh_version_too_old_mentions_upgrade() {\n let s = zh_cn(Msg::CpSignVersionTooOld);\n assert!(s.contains(\"升级\") || s.contains(\"更新\"));\n }\n\n #[test]\n fn zh_upgrade_required_is_non_empty() {\n let s = zh_cn(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn zh_conhost_scroll_hint_recommends_windows_terminal() {\n let s = zh_cn(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.contains(\"滚\"));\n }\n}\n"}}}}