{"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"}}}}