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.
1 line
3.1 MiB
Plaintext
1 line
3.1 MiB
Plaintext
{"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"}}}} |