{"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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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} \").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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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 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 \".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget ]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget \".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background \\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 ` 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 \\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 \\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout \\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 [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create [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 [--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/.md or .atomcode/commands/.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

@ | uninstall

@ | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] \".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install or @\".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 or @\".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 \"\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 @{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 )\".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 , /bg drop \".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 )\".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 ` 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 \".into(),\n Msg::ViewUsage => \"Usage: /view \".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide \".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 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 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 | /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 or /loop \\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 Resume background slot N\\n /bg drop 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

/.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": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n
\n v5.0.3 的 Provider 管理\n

推荐直接运行 /provider:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 [providers.*] 配置仍可继续使用,AtomCode 会自动兼容。

\n
\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

网络与代理

\n

AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 [network.proxy] 下配置:

\n
[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\"
\n \n \n \n \n \n \n \n
mode行为
follow_system(默认)采用 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS scutil --proxy)。
default_proxy使用本段里显式的 http / https / all / no_proxy 值。
no_proxy强制直连,忽略环境变量与系统代理。
\n

IPv6 代理主机会自动加方括号(::1 → http://[::1]:8080)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。

\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 /model 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

回合轮数

\n
[coding]\nmax_rounds = 200
\n

达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 0 表示不限制。

\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式管理供应商账号及其模型,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "bacf7b9d53f04fdb9ead8a7c8d8fdf6b1aa0baa5": {"t": 1785761159.8064015, "n": 16081, "h": "31bf04ba5f815987", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n
\n v5.0.3 的 Provider 管理\n

推荐直接运行 /provider:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 [providers.*] 配置仍可继续使用,AtomCode 会自动兼容。

\n
\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

网络与代理

\n

AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 [network.proxy] 下配置:

\n
[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\"
\n \n \n \n \n \n \n \n
mode行为
follow_system(默认)采用 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS scutil --proxy)。
default_proxy使用本段里显式的 http / https / all / no_proxy 值。
no_proxy强制直连,忽略环境变量与系统代理。
\n

IPv6 代理主机会自动加方括号(::1 → http://[::1]:8080)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。

\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 /model 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

回合轮数

\n
[coding]\nmax_rounds = 200
\n

达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 0 表示不限制。

\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式管理供应商账号及其模型,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "2a63b30571e1d2fe5ed3714f454a32b341572bc0": {"t": 1785761161.5752933, "n": 15616, "h": "69ea5ced1d1e5603", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

网络与代理

\n

AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 [network.proxy] 下配置:

\n
[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\"
\n \n \n \n \n \n \n \n
mode行为
follow_system(默认)采用 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS scutil --proxy)。
default_proxy使用本段里显式的 http / https / all / no_proxy 值。
no_proxy强制直连,忽略环境变量与系统代理。
\n

IPv6 代理主机会自动加方括号(::1 → http://[::1]:8080)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。

\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理 provider 条目。用 /model 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "1737a748cfc706db9548e0859a43476728100d2e": {"t": 1785761163.6157043, "n": 15616, "h": "1abf5245bfb931d9", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

网络与代理

\n

AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 [network.proxy] 下配置:

\n
[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\"
\n \n \n \n \n \n \n \n
mode行为
follow_system(默认)采用 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS scutil --proxy)。
default_proxy使用本段里显式的 http / https / all / no_proxy 值。
no_proxy强制直连,忽略环境变量与系统代理。
\n

IPv6 代理主机会自动加方括号(::1 → http://[::1]:8080)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。

\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理 provider 条目。用 /model 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "16c021d52b3be0b44bc9b49b6d647e41dc2c3f2a": {"t": 1785761165.4364398, "n": 15616, "h": "d880097116d3fdc7", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.1\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

网络与代理

\n

AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 [network.proxy] 下配置:

\n
[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\"
\n \n \n \n \n \n \n \n
mode行为
follow_system(默认)采用 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS scutil --proxy)。
default_proxy使用本段里显式的 http / https / all / no_proxy 值。
no_proxy强制直连,忽略环境变量与系统代理。
\n

IPv6 代理主机会自动加方括号(::1 → http://[::1]:8080)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。

\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理 provider 条目。用 /model 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "de1cd59a8d61743707e4904a5375f4413543417c": {"t": 1785761166.6353228, "n": 14210, "h": "93bd270e3417b2a7", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.1\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理 provider 条目。用 /model 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "a28c215857b0e87dba221b4ebebe1d16d00b1fbe": {"t": 1785761168.838292, "n": 14210, "h": "29e29f01c26b3385", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中用 /provider 管理 provider 条目。用 /model 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "ad90366cba07ceab3cfc652f96bdd323e6c0dabc": {"t": 1785761170.2811244, "n": 14189, "h": "5c96f5ae1de67fd0", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。支持明文密钥、环境变量语法(如 $MY_KEY 或 ${MY_KEY:-default})、环境变量名(如 MY_KEY),或留空自动回退标准环境变量(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n

环境变量与 API Key 动态解析

\n

为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 config.toml 中灵活使用环境变量:

\n
    \n
  • 环境变量语法展开 ($VAR / ${VAR}):\n
    [providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"
    \n 支持带默认值的表达式(例:api_key = \"${MY_KEY:-sk-fallback}\")。\n
  • \n
  • 直接填写环境变量名:\n
    [providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取
    \n
  • \n
  • 留空自动回退标准环境变量:当未配置或留空 api_key 时,系统会自动寻找对应 type 的标准环境变量:\n
      \n
    • openai / openai-compat:读取 OPENAI_API_KEY
    • \n
    • claude / anthropic:读取 ANTHROPIC_API_KEY
    • \n
    • ollama:读取 OLLAMA_API_KEY
    • \n
    • 通用后备:读取 ATOMCODE_API_KEY
    • \n
    \n
  • \n
\n

注:配置文件中的 $VAR 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。

\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "6a3a1f6933836f94dd7c26ea75c26645ee0d2489": {"t": 1785761171.5069304, "n": 12713, "h": "640e31487900d887", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v5.0.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "86bbaacbc30b3b5fa3105518b4c4052ffddb71df": {"t": 1785761173.1574996, "n": 12714, "h": "7098a7c445cbe180", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.26.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "17ebf59dcaad723bb94ed01721837618bd2da192": {"t": 1785761175.293464, "n": 12714, "h": "c5c1c3c639db3a40", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.9\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "0fd8208a0fe5d4aa56ed8bb12fe033682ba44847": {"t": 1785761176.662956, "n": 12714, "h": "63a350e67cb1c132", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.8\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "3269260453c27bd434d9d8f4770971f9eb339e5a": {"t": 1785761177.826284, "n": 12714, "h": "78dde1cdccfe41a9", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.7\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "431d0485045fe2e51660484e074ef0f42d9f2f39": {"t": 1785761179.2059317, "n": 12714, "h": "d53b7d9da964f695", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.6\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "6264e177874c3472ece603abac5df936bcccd7d8": {"t": 1785761180.721333, "n": 12714, "h": "d8128617f9f85a40", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.5\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "9bd888fc1ae56f7ebb84e3de26a5e96d3a8ba61e": {"t": 1785761182.2480638, "n": 12714, "h": "6d316d2e3471cd98", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.4\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "578ac062a237ed41e871d3ddfcfa18d3906e6790": {"t": 1785761183.5848343, "n": 12714, "h": "8ceeee4f9e424c27", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "a3ee1baf64fb11b73a84be262119f7090e25dbda": {"t": 1785761184.9288945, "n": 12714, "h": "1c9d27214b5e5d6f", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "fcb5525d2ca5b550483a3f35a3a4f8997a4a120d": {"t": 1785761186.1960986, "n": 12714, "h": "b023fe2802b5f882", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.1\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "de732c5f4d44b7bde84879f38d7cfefdfbae1a2e": {"t": 1785761187.5358357, "n": 12714, "h": "e67ee203e70d0d04", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.25.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "e987bb439507ad0913f8bee478321b140274b839": {"t": 1785761188.6966908, "n": 12714, "h": "a7d7597405541049", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "9eccc8f68beda069fa2526fc8d695deac2547f46": {"t": 1785761190.1039116, "n": 12606, "h": "e1be6b066e9a41fa", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "241659424f17ba76815f4e987533ab3154458ee2": {"t": 1785761191.3511972, "n": 12606, "h": "8b5f102a1033577e", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.1\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "46232b8107bda45bc1f106ccc5f57af73ad20c72": {"t": 1785761192.5472076, "n": 12606, "h": "85e16b411e5ee9d7", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "bfd45aac4384f4df06f25e2bcfe783cb61ca4bb6": {"t": 1785761193.7686107, "n": 12506, "h": "e1435dce7f7cf72f", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "26aa47211d50a2498cf54fa380edd0fce5486bab": {"t": 1785761195.1286917, "n": 12366, "h": "8caef59791dd5e22", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.24.0\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "13c051411f4bfad7d04897f6c150beee2d5e5592": {"t": 1785761196.4307868, "n": 12366, "h": "e9943643bfc5a330", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.23.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /login 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "27ee84b6604aad3ac1924f3f7326a95314b3032f": {"t": 1785761197.9718714, "n": 12371, "h": "3012d9a470236695", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.23.3\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /codingplan 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "350b15d383e1d610825e7bd445a6a162ff8f63f9": {"t": 1785761199.1567595, "n": 12371, "h": "12605d7c79f48806", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.23.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /codingplan 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "91dc4e2e916c55e18264a4e1998e78618e16a026": {"t": 1785761200.3606267, "n": 12721, "h": "9c4fb0037b7c8c0e", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.23.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

UI 偏好

\n

TUI 显示偏好(滚动条开关等)单独存在一个文件,不和 config.toml 混在一起:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/ui-state.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\ui-state.toml
  • \n
\n

由 /scrollbar 等命令自动维护(原子写,崩溃也不会破文件),通常不需要手动编辑。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /codingplan 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\n"}, "3e115c22a6752ac1146d024739f6ef6b2bb06b3a": {"t": 1785761201.789139, "n": 12371, "h": "12605d7c79f48806", "c": "\n\n\n\n\n配置文件 · AtomCode 文档\n\n\n\n\n\n\n\n
\n \n \"AtomCode\"\n AtomCode\n DOCS\n v4.23.2\n \n
\n \n \n \n 仓库 →\n \n
\n
\n\n
\n \n\n
\n

配置文件

\n

AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。

\n\n

配置文件位置

\n

默认路径:

\n
    \n
  • macOS / Linux / HarmonyOS PC:~/.atomcode/config.toml
  • \n
  • Windows:%USERPROFILE%\\.atomcode\\config.toml
  • \n
\n

也可以通过 --config /path/to/config.toml 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。

\n\n

最小示例

\n
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
\n

这个配置已经可以启动 atomcode 并与 DeepSeek 对话。

\n\n

顶层字段

\n \n \n \n \n \n \n \n \n \n \n
字段类型说明
default_providerstring启动时默认使用的 provider 名(必须是 [providers.*] 中定义过的 key)
default_workdirstring?默认工作目录。用 /cd 切换目录时自动写回这里,下次启动恢复
providerstableprovider 名到 ProviderConfig 的映射
vision_preprocessor_providerstring?当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 视觉预处理器
\n\n

ProviderConfig 字段

\n

每一个 [providers.xxx] 表下可用的字段:

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
字段类型必填说明
typestring是provider 协议类型,目前支持 openai、claude、ollama
api_keystring?视情况API Key。ollama 可不填。OAuth 登录的 provider 不含此字段,token 从 ~/.atomcode/auth.toml 自动读取
modelstring是模型名,比如 deepseek-chat、gpt-4o、claude-sonnet-4-6
base_urlstring是API 基地址,需指向实际接口。例:https://api.deepseek.com/v1、http://localhost:11434
context_windowinteger否模型上下文窗口(tokens),默认 64000。ollama 默认 8000
max_tokensinteger?否单次响应的最大输出 token 数。留空则使用 context_window / 4
system_promptstring?否覆盖默认系统提示。大多数场景不需要
user_agentstring?否覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用
\n\n
\n 为什么默认 64K 而不是 128K?\n

即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。

\n
\n\n

常见 provider 配置示例

\n\n

Claude (Anthropic)

\n
[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000
\n\n

OpenAI

\n
[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000
\n\n

DeepSeek

\n
[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000
\n\n

智谱 GLM

\n
[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000
\n\n

通义千问 Qwen

\n
[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000
\n\n

SiliconFlow

\n
[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"
\n\n

Ollama(本地)

\n
[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000
\n
\n 注意\n

Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。

\n
\n\n

多 provider 与快速切换

\n

配置文件允许同时存在任意数量的 provider:

\n
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\"
\n

在 TUI 中可以用 /provider 在这些条目之间切换,用 /model 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:

\n
atomcode --provider deepseek --model deepseek-reasoner
\n\n

视觉预处理器

\n

当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 attach 了图片时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。

\n

启用方式:在配置里指定一个支持图片的 provider key 作为 vision_preprocessor_provider:

\n
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\"
\n
    \n
  • 这里的 qwen-vl 必须是 [providers.*] 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 Qwen3-VL-* / GLM-4V-* / claude-3+ / gpt-4o / gemini-* 等)。
  • \n
  • 当前 active provider 自身就支持图片时,不会走预处理器,直接发原始图片字节。
  • \n
  • /codingplan 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。
  • \n
  • VL 调用使用无进展超时(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。
  • \n
\n

用法层面的细节参见 基本使用 · 图片附件 / 截图。

\n\n

编辑配置的三种方式

\n
    \n
  • 手动编辑 —— 直接用你喜欢的编辑器打开 ~/.atomcode/config.toml。
  • \n
  • TUI 内 /config —— 在 atomcode 内部直接调用默认编辑器打开配置文件。
  • \n
  • TUI 内 /provider —— 交互式地添加、编辑、删除 provider,改动会自动落盘。
  • \n
\n\n

下一步

\n
    \n
  • 登录方式 —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。
  • \n
  • 基本使用 —— 看看 CLI 参数如何一次性覆盖配置。
  • \n
\n\n \n
\n
\n\n
\n
\n
\n
\n \n \n ESC\n
\n
\n
\n
\n\n\n\n\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 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\".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

@ | uninstall

@ | 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 授权。\"\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 、/bg drop \".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 /bg drop 丢弃第 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 而非

/.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"}}}}